diff --git a/PHASE23-NOTES.md b/PHASE23-NOTES.md new file mode 100644 index 0000000..f1975b9 --- /dev/null +++ b/PHASE23-NOTES.md @@ -0,0 +1,7 @@ +# Phase 23 — Query Plan History and Comparison + +Adds locally saved EXPLAIN snapshots (Table or JSON), history scoped to the same connection name, database, and SQL statement, baseline comparison, and deletion. Table-plan comparison includes a sum of optimizer-estimated rows across steps, not measured execution times. Snapshots are stored in UserDefaults (max 40, max encoded size 2 MB). + +The existing EXPLAIN inspector and query plan observations remain unchanged. No SQL statements are executed beyond EXPLAIN. + +**Validation:** Source changes and archive structure inspected; no macOS/Xcode build or live MySQL tests performed. diff --git a/WorkbenchReplacement/MainView.swift b/WorkbenchReplacement/MainView.swift index 9a4c4a9..6410ecc 100644 --- a/WorkbenchReplacement/MainView.swift +++ b/WorkbenchReplacement/MainView.swift @@ -1344,6 +1344,43 @@ struct SQLTextView: NSViewRepresentable { // MARK: - Connection Editor +// MARK: - Phase 23: Locally stored EXPLAIN snapshots + +private struct WorkbenchPlanSnapshot: Identifiable, Codable { + var id = UUID() + var capturedAt = Date() + var connectionName: String + var database: String + var statement: String + var format: String + var columnNames: [String] + var resultRows: [[String]] + var elapsedSeconds: Double + + var label: String { + "\(capturedAt.formatted(date: .abbreviated, time: .shortened)) · \(format) · \(database)" + } +} + +private enum WorkbenchPlanArchive { + private static let key = "WorkbenchReplacement.explainSnapshots.v1" + + static func load() -> [WorkbenchPlanSnapshot] { + guard let data = UserDefaults.standard.data(forKey: key), + let snapshots = try? JSONDecoder().decode([WorkbenchPlanSnapshot].self, from: data) + else { return [] } + return snapshots + } + + static func save(_ snapshots: [WorkbenchPlanSnapshot]) { + // Cap both snapshot count and size so EXPLAIN JSON cannot grow preferences without bound. + let trimmed = Array(snapshots.prefix(40)) + if let data = try? JSONEncoder().encode(trimmed), data.count <= 2_000_000 { + UserDefaults.standard.set(data, forKey: key) + } + } +} + // MARK: - Phase 21: Native MySQL query plan inspector private struct WorkbenchExplainView: View { @@ -1356,6 +1393,44 @@ private struct WorkbenchExplainView: View { @State private var isLoading = false @State private var useJSON = false @State private var duration: TimeInterval? + @State private var snapshots: [WorkbenchPlanSnapshot] = WorkbenchPlanArchive.load() + @State private var baselineID: UUID? + @State private var showHistory = false + + private var matchingSnapshots: [WorkbenchPlanSnapshot] { + snapshots.filter { + $0.statement == normalizedStatement && + $0.database == (model.selectedDatabase.isEmpty ? (model.selected?.database ?? "") : model.selectedDatabase) && + $0.connectionName == (model.selected?.name ?? "") + } + } + + private var baseline: WorkbenchPlanSnapshot? { + matchingSnapshots.first { $0.id == baselineID } + } + + private var comparisonText: String? { + guard let baseline, !plan.columns.isEmpty else { return nil } + guard baseline.format == (useJSON ? "JSON" : "Table") else { + return "Baseline format differs from the current plan. Select a matching format." + } + if useJSON { + return baseline.resultRows == plan.rows ? "JSON plan unchanged from baseline." : "JSON plan differs from baseline. Inspect the plan text for details." + } + func estimatedRows(_ columns: [String], _ rows: [[String]]) -> Int? { + guard let index = columns.firstIndex(where: { $0.lowercased() == "rows" }) else { return nil } + let values = rows.compactMap { index < $0.count ? Int($0[index]) : nil } + return values.isEmpty ? nil : values.reduce(0, +) + } + let oldEstimate = estimatedRows(baseline.columnNames, baseline.resultRows) + let newEstimate = estimatedRows(plan.columns, plan.rows) + let changed = baseline.columnNames != plan.columns || baseline.resultRows != plan.rows + var message = changed ? "Plan changed since baseline." : "Plan unchanged from baseline." + if let oldEstimate, let newEstimate { + message += " Estimated rows across steps: \(oldEstimate) → \(newEstimate)." + } + return message + " Estimates are not measured query timings." + } private var normalizedStatement: String { var value = statement.trimmingCharacters(in: .whitespacesAndNewlines) @@ -1443,6 +1518,15 @@ private struct WorkbenchExplainView: View { .frame(width: 240) Button("Run EXPLAIN") { Task { await loadPlan() } } .disabled(isLoading || !eligible || model.selected == nil) + Button("Save Snapshot") { saveSnapshot() } + .disabled(plan.columns.isEmpty || isLoading) + Button("History") { showHistory.toggle() } + if let baseline { + Text("Comparing with \(baseline.capturedAt.formatted(date: .abbreviated, time: .shortened))") + .font(.caption) + .foregroundStyle(.secondary) + Button("Clear Baseline") { baselineID = nil } + } Button("Copy Plan") { NSPasteboard.general.clearContents() NSPasteboard.general.setString(copyablePlan, forType: .string) @@ -1476,6 +1560,38 @@ private struct WorkbenchExplainView: View { } else { ContentUnavailableView("No plan loaded", systemImage: "chart.bar.doc.horizontal") } + if showHistory { + DisclosureGroup("Saved plans for this query (\(matchingSnapshots.count))", isExpanded: $showHistory) { + if matchingSnapshots.isEmpty { + Text("No saved plans for this connection, schema and SQL statement.") + .foregroundStyle(.secondary) + } else { + ScrollView { + VStack(alignment: .leading, spacing: 6) { + ForEach(matchingSnapshots) { snapshot in + HStack { + Text(snapshot.label) + .font(.callout) + Spacer() + Button("Compare") { baselineID = snapshot.id } + Button("Delete", role: .destructive) { + snapshots.removeAll { $0.id == snapshot.id } + if baselineID == snapshot.id { baselineID = nil } + WorkbenchPlanArchive.save(snapshots) + } + } + } + } + } + .frame(maxHeight: 130) + } + } + } + if let comparisonText { + Label(comparisonText, systemImage: "arrow.left.arrow.right") + .font(.callout) + .textSelection(.enabled) + } if !planInsights.isEmpty { DisclosureGroup("Plan observations (\(planInsights.count))") { ScrollView { @@ -1505,6 +1621,24 @@ private struct WorkbenchExplainView: View { .task { await loadPlan() } } + @MainActor + private func saveSnapshot() { + guard let profile = model.selected, !plan.columns.isEmpty else { return } + let database = model.selectedDatabase.isEmpty ? profile.database : model.selectedDatabase + let snapshot = WorkbenchPlanSnapshot( + connectionName: profile.name, + database: database, + statement: normalizedStatement, + format: useJSON ? "JSON" : "Table", + columnNames: plan.columns, + resultRows: plan.rows, + elapsedSeconds: duration ?? 0 + ) + snapshots.insert(snapshot, at: 0) + WorkbenchPlanArchive.save(snapshots) + showHistory = true + } + @MainActor private func loadPlan() async { guard eligible, let profile = model.selected, !isLoading else { return } diff --git a/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.o b/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.o index 650a97c..7cf6ba5 100644 Binary files a/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.o and b/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.o differ diff --git a/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.stringsdata b/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.stringsdata index 1d0b408..b4a64fb 100644 --- a/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.stringsdata +++ b/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.stringsdata @@ -447,7 +447,7 @@ "key": "Uses MySQL EXPLAIN to inspect the optimizer's estimated plan without executing the SELECT statement itself.", "location": { "startingColumn": 18, - "startingLine": 1427 + "startingLine": 1502 } }, { @@ -455,7 +455,7 @@ "key": "Select a SELECT or WITH query in the SQL editor before opening EXPLAIN.", "location": { "startingColumn": 124, - "startingLine": 1459 + "startingLine": 1543 } }, { @@ -463,7 +463,7 @@ "key": "EXPLAIN shows estimates, not actual query execution times. Observations are heuristics, not index recommendations. Confirm with representative data before changing production indexes.", "location": { "startingColumn": 18, - "startingLine": 1494 + "startingLine": 1610 } }, { @@ -471,7 +471,7 @@ "key": "SELECT query required", "location": { "startingColumn": 40, - "startingLine": 1459 + "startingLine": 1543 } }, { @@ -479,7 +479,15 @@ "key": "No plan loaded", "location": { "startingColumn": 40, - "startingLine": 1477 + "startingLine": 1561 + } + }, + { + "comment": "", + "key": "Saved plans for this query (%lld)", + "location": { + "startingColumn": 33, + "startingLine": 1564 } }, { @@ -487,7 +495,7 @@ "key": "Plan observations (%lld)", "location": { "startingColumn": 33, - "startingLine": 1480 + "startingLine": 1596 } }, { @@ -495,7 +503,7 @@ "key": "Query Execution Plan", "location": { "startingColumn": 23, - "startingLine": 1422 + "startingLine": 1497 } }, { @@ -503,7 +511,15 @@ "key": "Close", "location": { "startingColumn": 24, - "startingLine": 1425 + "startingLine": 1500 + } + }, + { + "comment": "", + "key": "Comparing with %@", + "location": { + "startingColumn": 26, + "startingLine": 1525 } }, { @@ -511,7 +527,7 @@ "key": "Plan format", "location": { "startingColumn": 24, - "startingLine": 1438 + "startingLine": 1513 } }, { @@ -519,7 +535,31 @@ "key": "Run EXPLAIN", "location": { "startingColumn": 24, - "startingLine": 1444 + "startingLine": 1519 + } + }, + { + "comment": "", + "key": "Save Snapshot", + "location": { + "startingColumn": 24, + "startingLine": 1521 + } + }, + { + "comment": "", + "key": "History", + "location": { + "startingColumn": 24, + "startingLine": 1523 + } + }, + { + "comment": "", + "key": "Clear Baseline", + "location": { + "startingColumn": 28, + "startingLine": 1528 } }, { @@ -527,7 +567,7 @@ "key": "Copy Plan", "location": { "startingColumn": 24, - "startingLine": 1446 + "startingLine": 1530 } }, { @@ -535,7 +575,7 @@ "key": "Table", "location": { "startingColumn": 26, - "startingLine": 1439 + "startingLine": 1514 } }, { @@ -543,7 +583,31 @@ "key": "JSON", "location": { "startingColumn": 26, - "startingLine": 1440 + "startingLine": 1515 + } + }, + { + "comment": "", + "key": "No saved plans for this connection, schema and SQL statement.", + "location": { + "startingColumn": 30, + "startingLine": 1566 + } + }, + { + "comment": "", + "key": "Compare", + "location": { + "startingColumn": 48, + "startingLine": 1576 + } + }, + { + "comment": "", + "key": "Delete", + "location": { + "startingColumn": 48, + "startingLine": 1577 } }, { @@ -551,7 +615,7 @@ "key": "Connection Settings", "location": { "startingColumn": 18, - "startingLine": 1544 + "startingLine": 1678 } }, { @@ -559,7 +623,7 @@ "key": "Connection Name", "location": { "startingColumn": 21, - "startingLine": 1549 + "startingLine": 1683 } }, { @@ -567,7 +631,7 @@ "key": "Hostname", "location": { "startingColumn": 21, - "startingLine": 1554 + "startingLine": 1688 } }, { @@ -575,7 +639,7 @@ "key": "Port", "location": { "startingColumn": 21, - "startingLine": 1559 + "startingLine": 1693 } }, { @@ -583,7 +647,7 @@ "key": "Username", "location": { "startingColumn": 21, - "startingLine": 1565 + "startingLine": 1699 } }, { @@ -591,7 +655,7 @@ "key": "Password (macOS Keychain)", "location": { "startingColumn": 21, - "startingLine": 1570 + "startingLine": 1704 } }, { @@ -599,7 +663,7 @@ "key": "Default Schema", "location": { "startingColumn": 21, - "startingLine": 1575 + "startingLine": 1709 } }, { @@ -607,7 +671,7 @@ "key": "Cancel", "location": { "startingColumn": 24, - "startingLine": 1583 + "startingLine": 1717 } }, { @@ -615,7 +679,7 @@ "key": "Save", "location": { "startingColumn": 24, - "startingLine": 1587 + "startingLine": 1721 } }, { @@ -623,7 +687,7 @@ "key": "Terminate MySQL session?", "location": { "startingColumn": 13, - "startingLine": 2082 + "startingLine": 2216 } }, { @@ -631,7 +695,7 @@ "key": "The server did not return any columns.", "location": { "startingColumn": 25, - "startingLine": 2015 + "startingLine": 2149 } }, { @@ -639,7 +703,7 @@ "key": "Loading %@…", "location": { "startingColumn": 30, - "startingLine": 1993 + "startingLine": 2127 } }, { @@ -647,7 +711,7 @@ "key": "No Data", "location": { "startingColumn": 21, - "startingLine": 2012 + "startingLine": 2146 } }, { @@ -655,7 +719,7 @@ "key": "Unable to Load %@", "location": { "startingColumn": 25, - "startingLine": 1999 + "startingLine": 2133 } }, { @@ -663,7 +727,7 @@ "key": "Retry", "location": { "startingColumn": 28, - "startingLine": 2005 + "startingLine": 2139 } }, { @@ -671,7 +735,7 @@ "key": "Changes require confirmation", "location": { "startingColumn": 26, - "startingLine": 2059 + "startingLine": 2193 } }, { @@ -679,7 +743,7 @@ "key": "Account", "location": { "startingColumn": 28, - "startingLine": 2038 + "startingLine": 2172 } }, { @@ -687,7 +751,7 @@ "key": "Show Grants", "location": { "startingColumn": 28, - "startingLine": 2049 + "startingLine": 2183 } }, { @@ -695,7 +759,7 @@ "key": "Manage Accounts…", "location": { "startingColumn": 28, - "startingLine": 2053 + "startingLine": 2187 } }, { @@ -703,7 +767,7 @@ "key": "Select account", "location": { "startingColumn": 30, - "startingLine": 2039 + "startingLine": 2173 } }, { @@ -711,7 +775,7 @@ "key": "%@@%@", "location": { "startingColumn": 38, - "startingLine": 2043 + "startingLine": 2177 } }, { @@ -719,7 +783,7 @@ "key": "%@ session %@", "location": { "startingColumn": 24, - "startingLine": 2090 + "startingLine": 2224 } }, { @@ -727,7 +791,7 @@ "key": "Cancel", "location": { "startingColumn": 20, - "startingLine": 2095 + "startingLine": 2229 } }, { @@ -735,7 +799,7 @@ "key": "This operation affects a live MySQL session. Terminating a connection can interrupt transactions and roll back uncommitted work. Verify the session ID before proceeding.", "location": { "startingColumn": 18, - "startingLine": 2097 + "startingLine": 2231 } }, { @@ -743,7 +807,7 @@ "key": "Grants: %@", "location": { "startingColumn": 22, - "startingLine": 2111 + "startingLine": 2245 } }, { @@ -751,7 +815,7 @@ "key": "Close", "location": { "startingColumn": 24, - "startingLine": 2123 + "startingLine": 2257 } }, { @@ -759,7 +823,7 @@ "key": "Requires PROCESS privilege to see other users' sessions and appropriate privileges to terminate them.", "location": { "startingColumn": 18, - "startingLine": 2153 + "startingLine": 2287 } }, { @@ -767,7 +831,7 @@ "key": "Session", "location": { "startingColumn": 24, - "startingLine": 2133 + "startingLine": 2267 } }, { @@ -775,7 +839,7 @@ "key": "Stop Query", "location": { "startingColumn": 24, - "startingLine": 2140 + "startingLine": 2274 } }, { @@ -783,7 +847,7 @@ "key": "Disconnect Session", "location": { "startingColumn": 24, - "startingLine": 2142 + "startingLine": 2276 } }, { @@ -791,7 +855,7 @@ "key": "Select a session", "location": { "startingColumn": 26, - "startingLine": 2134 + "startingLine": 2268 } }, { @@ -799,7 +863,7 @@ "key": "ID %@", "location": { "startingColumn": 30, - "startingLine": 2136 + "startingLine": 2270 } }, { @@ -807,7 +871,7 @@ "key": "Filter results", "location": { "startingColumn": 27, - "startingLine": 2218 + "startingLine": 2352 } }, { @@ -815,7 +879,7 @@ "key": "Auto-refresh", "location": { "startingColumn": 20, - "startingLine": 2223 + "startingLine": 2357 } }, { @@ -823,7 +887,7 @@ "key": "Refresh every five seconds", "location": { "startingColumn": 23, - "startingLine": 2225 + "startingLine": 2359 } }, { @@ -831,7 +895,7 @@ "key": "Refresh", "location": { "startingColumn": 23, - "startingLine": 2230 + "startingLine": 2364 } }, { @@ -839,7 +903,7 @@ "key": "SQL Editor", "location": { "startingColumn": 23, - "startingLine": 2237 + "startingLine": 2371 } }, { @@ -847,7 +911,7 @@ "key": "Not loaded", "location": { "startingColumn": 22, - "startingLine": 2315 + "startingLine": 2449 } }, { @@ -855,7 +919,7 @@ "key": "Updated %@", "location": { "startingColumn": 21, - "startingLine": 2312 + "startingLine": 2446 } }, { @@ -863,7 +927,7 @@ "key": "%lld rows", "location": { "startingColumn": 18, - "startingLine": 2320 + "startingLine": 2454 } }, { @@ -871,7 +935,7 @@ "key": "Import SQL into %@?", "location": { "startingColumn": 13, - "startingLine": 2627 + "startingLine": 2761 } }, { @@ -879,7 +943,7 @@ "key": "Operation", "location": { "startingColumn": 20, - "startingLine": 2430 + "startingLine": 2564 } }, { @@ -887,7 +951,7 @@ "key": "Data Export", "location": { "startingColumn": 22, - "startingLine": 2431 + "startingLine": 2565 } }, { @@ -895,7 +959,7 @@ "key": "Data Import / Restore", "location": { "startingColumn": 22, - "startingLine": 2432 + "startingLine": 2566 } }, { @@ -903,7 +967,7 @@ "key": "MySQL Connection", "location": { "startingColumn": 26, - "startingLine": 2439 + "startingLine": 2573 } }, { @@ -911,7 +975,7 @@ "key": "Export Options", "location": { "startingColumn": 30, - "startingLine": 2463 + "startingLine": 2597 } }, { @@ -919,7 +983,7 @@ "key": "Import Options", "location": { "startingColumn": 30, - "startingLine": 2519 + "startingLine": 2653 } }, { @@ -927,7 +991,7 @@ "key": "Progress", "location": { "startingColumn": 26, - "startingLine": 2531 + "startingLine": 2665 } }, { @@ -935,7 +999,7 @@ "key": "Connection", "location": { "startingColumn": 36, - "startingLine": 2441 + "startingLine": 2575 } }, { @@ -943,7 +1007,7 @@ "key": "Database", "location": { "startingColumn": 28, - "startingLine": 2445 + "startingLine": 2579 } }, { @@ -951,7 +1015,7 @@ "key": "Select Database", "location": { "startingColumn": 30, - "startingLine": 2446 + "startingLine": 2580 } }, { @@ -959,7 +1023,7 @@ "key": "Refresh Databases", "location": { "startingColumn": 32, - "startingLine": 2454 + "startingLine": 2588 } }, { @@ -967,7 +1031,7 @@ "key": "No tables found. Select a database or refresh the list.", "location": { "startingColumn": 34, - "startingLine": 2485 + "startingLine": 2619 } }, { @@ -975,7 +1039,7 @@ "key": "InnoDB data is exported using a consistent snapshot and a single streamed query per table. Tables without unique indexes are supported.", "location": { "startingColumn": 30, - "startingLine": 2514 + "startingLine": 2648 } }, { @@ -983,7 +1047,7 @@ "key": "Table structure", "location": { "startingColumn": 32, - "startingLine": 2465 + "startingLine": 2599 } }, { @@ -991,7 +1055,7 @@ "key": "Table data", "location": { "startingColumn": 32, - "startingLine": 2466 + "startingLine": 2600 } }, { @@ -999,7 +1063,7 @@ "key": "Add DROP TABLE IF EXISTS", "location": { "startingColumn": 32, - "startingLine": 2467 + "startingLine": 2601 } }, { @@ -1007,7 +1071,7 @@ "key": "Include views (definitions)", "location": { "startingColumn": 32, - "startingLine": 2469 + "startingLine": 2603 } }, { @@ -1015,7 +1079,7 @@ "key": "Include triggers", "location": { "startingColumn": 32, - "startingLine": 2471 + "startingLine": 2605 } }, { @@ -1023,7 +1087,7 @@ "key": "Include stored procedures and functions", "location": { "startingColumn": 32, - "startingLine": 2473 + "startingLine": 2607 } }, { @@ -1031,7 +1095,7 @@ "key": "Loading tables…", "location": { "startingColumn": 57, - "startingLine": 2483 + "startingLine": 2617 } }, { @@ -1039,7 +1103,7 @@ "key": "Tables (%lld selected)", "location": { "startingColumn": 34, - "startingLine": 2476 + "startingLine": 2610 } }, { @@ -1047,7 +1111,7 @@ "key": "All", "location": { "startingColumn": 36, - "startingLine": 2478 + "startingLine": 2612 } }, { @@ -1055,7 +1119,7 @@ "key": "None", "location": { "startingColumn": 36, - "startingLine": 2479 + "startingLine": 2613 } }, { @@ -1063,7 +1127,7 @@ "key": "Refresh", "location": { "startingColumn": 36, - "startingLine": 2480 + "startingLine": 2614 } }, { @@ -1071,7 +1135,7 @@ "key": "Views (%lld selected)", "location": { "startingColumn": 38, - "startingLine": 2499 + "startingLine": 2633 } }, { @@ -1079,7 +1143,7 @@ "key": "All", "location": { "startingColumn": 40, - "startingLine": 2501 + "startingLine": 2635 } }, { @@ -1087,7 +1151,7 @@ "key": "None", "location": { "startingColumn": 40, - "startingLine": 2502 + "startingLine": 2636 } }, { @@ -1095,7 +1159,7 @@ "key": "Import SQL statements into the selected database.", "location": { "startingColumn": 30, - "startingLine": 2521 + "startingLine": 2655 } }, { @@ -1103,7 +1167,7 @@ "key": "Supports MySQL DELIMITER directives, triggers and stored routines. Import is not transactional; use a verified backup before restoring.", "location": { "startingColumn": 30, - "startingLine": 2524 + "startingLine": 2658 } }, { @@ -1111,7 +1175,7 @@ "key": "Current table: %@ / %@ rows · Total exported: %@", "location": { "startingColumn": 30, - "startingLine": 2548 + "startingLine": 2682 } }, { @@ -1119,7 +1183,7 @@ "key": "Close", "location": { "startingColumn": 24, - "startingLine": 2562 + "startingLine": 2696 } }, { @@ -1127,7 +1191,7 @@ "key": "Verify Backup…", "location": { "startingColumn": 28, - "startingLine": 2570 + "startingLine": 2704 } }, { @@ -1135,7 +1199,7 @@ "key": "Verify an SQL file against its SHA-256 checksum sidecar", "location": { "startingColumn": 27, - "startingLine": 2573 + "startingLine": 2707 } }, { @@ -1143,7 +1207,7 @@ "key": "Verify Restored Database…", "location": { "startingColumn": 28, - "startingLine": 2574 + "startingLine": 2708 } }, { @@ -1151,7 +1215,7 @@ "key": "Compare restored table row counts and object names with the export manifest", "location": { "startingColumn": 27, - "startingLine": 2578 + "startingLine": 2712 } }, { @@ -1159,7 +1223,7 @@ "key": "Cancel", "location": { "startingColumn": 28, - "startingLine": 2582 + "startingLine": 2716 } }, { @@ -1167,7 +1231,7 @@ "key": "Export SQL…", "location": { "startingColumn": 31, - "startingLine": 2589 + "startingLine": 2723 } }, { @@ -1175,7 +1239,7 @@ "key": "Import SQL…", "location": { "startingColumn": 31, - "startingLine": 2590 + "startingLine": 2724 } }, { @@ -1183,7 +1247,7 @@ "key": "Import SQL", "location": { "startingColumn": 20, - "startingLine": 2630 + "startingLine": 2764 } }, { @@ -1191,7 +1255,7 @@ "key": "Cancel", "location": { "startingColumn": 20, - "startingLine": 2638 + "startingLine": 2772 } }, { @@ -1199,7 +1263,7 @@ "key": "SQL files can modify or delete existing database objects and data. Ensure you have a backup before continuing.", "location": { "startingColumn": 17, - "startingLine": 2643 + "startingLine": 2777 } }, { @@ -1207,7 +1271,7 @@ "key": "Database Import / Export", "location": { "startingColumn": 22, - "startingLine": 2655 + "startingLine": 2789 } }, { @@ -1215,7 +1279,7 @@ "key": "Built-in native MySQL database tools", "location": { "startingColumn": 22, - "startingLine": 2658 + "startingLine": 2792 } }, { @@ -1223,7 +1287,7 @@ "key": "Confirm %@", "location": { "startingColumn": 16, - "startingLine": 3709 + "startingLine": 3843 } }, { @@ -1231,7 +1295,7 @@ "key": "Manage MySQL Accounts", "location": { "startingColumn": 18, - "startingLine": 3659 + "startingLine": 3793 } }, { @@ -1239,7 +1303,7 @@ "key": "Account changes run on %@. Review the SQL before applying it.", "location": { "startingColumn": 18, - "startingLine": 3660 + "startingLine": 3794 } }, { @@ -1247,7 +1311,7 @@ "key": "Operation", "location": { "startingColumn": 24, - "startingLine": 3663 + "startingLine": 3797 } }, { @@ -1255,7 +1319,7 @@ "key": "Username", "location": { "startingColumn": 27, - "startingLine": 3668 + "startingLine": 3802 } }, { @@ -1263,7 +1327,7 @@ "key": "Host", "location": { "startingColumn": 27, - "startingLine": 3670 + "startingLine": 3804 } }, { @@ -1271,7 +1335,7 @@ "key": "Password", "location": { "startingColumn": 33, - "startingLine": 3672 + "startingLine": 3806 } }, { @@ -1279,7 +1343,7 @@ "key": "Database", "location": { "startingColumn": 31, - "startingLine": 3675 + "startingLine": 3809 } }, { @@ -1287,7 +1351,7 @@ "key": "Privilege", "location": { "startingColumn": 28, - "startingLine": 3676 + "startingLine": 3810 } }, { @@ -1295,7 +1359,7 @@ "key": "Close", "location": { "startingColumn": 24, - "startingLine": 3691 + "startingLine": 3825 } }, { @@ -1303,7 +1367,7 @@ "key": "Review SQL…", "location": { "startingColumn": 24, - "startingLine": 3693 + "startingLine": 3827 } }, { @@ -1311,7 +1375,7 @@ "key": "Cancel", "location": { "startingColumn": 20, - "startingLine": 3710 + "startingLine": 3844 } }, { @@ -1319,7 +1383,7 @@ "key": "Apply Change", "location": { "startingColumn": 20, - "startingLine": 3711 + "startingLine": 3845 } }, { @@ -1327,7 +1391,7 @@ "key": "Run this operation against %@?\n\n%@", "location": { "startingColumn": 18, - "startingLine": 3716 + "startingLine": 3850 } } ] diff --git a/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.swiftdeps b/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.swiftdeps index 68c6b91..2d00f7a 100644 Binary files a/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.swiftdeps and b/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.swiftdeps differ diff --git a/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement-primary.priors b/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement-primary.priors index 5188bd7..1943904 100644 Binary files a/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement-primary.priors and b/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement-primary.priors differ diff --git a/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement.swiftmodule b/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement.swiftmodule index bdd390d..bb888bf 100644 Binary files a/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement.swiftmodule and b/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement.swiftmodule differ diff --git a/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement.swiftsourceinfo b/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement.swiftsourceinfo index 3f7f3ac..8738761 100644 Binary files a/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement.swiftsourceinfo and b/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement.swiftsourceinfo differ diff --git a/build/Build/Intermediates.noindex/XCBuildData/build.db b/build/Build/Intermediates.noindex/XCBuildData/build.db index 4331b1c..53fc312 100644 Binary files a/build/Build/Intermediates.noindex/XCBuildData/build.db and b/build/Build/Intermediates.noindex/XCBuildData/build.db differ diff --git a/build/Build/Intermediates.noindex/XCBuildData/registered-launchservices.txt b/build/Build/Intermediates.noindex/XCBuildData/registered-launchservices.txt index 4fd065f..c53346e 100644 --- a/build/Build/Intermediates.noindex/XCBuildData/registered-launchservices.txt +++ b/build/Build/Intermediates.noindex/XCBuildData/registered-launchservices.txt @@ -28,3 +28,4 @@ /Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Products/Debug/WorkbenchReplacement.app /Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Products/Debug/WorkbenchReplacement.app /Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Products/Debug/WorkbenchReplacement.app +/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Products/Debug/WorkbenchReplacement.app diff --git a/build/Build/Products/Debug/WorkbenchReplacement.app/Contents/MacOS/WorkbenchReplacement b/build/Build/Products/Debug/WorkbenchReplacement.app/Contents/MacOS/WorkbenchReplacement index 8bff9e1..14fe12f 100755 Binary files a/build/Build/Products/Debug/WorkbenchReplacement.app/Contents/MacOS/WorkbenchReplacement and b/build/Build/Products/Debug/WorkbenchReplacement.app/Contents/MacOS/WorkbenchReplacement differ diff --git a/build/Build/Products/Debug/WorkbenchReplacement.app/Contents/MacOS/WorkbenchReplacement.debug.dylib b/build/Build/Products/Debug/WorkbenchReplacement.app/Contents/MacOS/WorkbenchReplacement.debug.dylib index 8dd6e16..c98db95 100755 Binary files a/build/Build/Products/Debug/WorkbenchReplacement.app/Contents/MacOS/WorkbenchReplacement.debug.dylib and b/build/Build/Products/Debug/WorkbenchReplacement.app/Contents/MacOS/WorkbenchReplacement.debug.dylib differ diff --git a/build/Build/Products/Debug/WorkbenchReplacement.app/Contents/MacOS/__preview.dylib b/build/Build/Products/Debug/WorkbenchReplacement.app/Contents/MacOS/__preview.dylib index 7dc74f5..f1c34e7 100755 Binary files a/build/Build/Products/Debug/WorkbenchReplacement.app/Contents/MacOS/__preview.dylib and b/build/Build/Products/Debug/WorkbenchReplacement.app/Contents/MacOS/__preview.dylib differ diff --git a/build/Build/Products/Debug/WorkbenchReplacement.app/Contents/_CodeSignature/CodeResources b/build/Build/Products/Debug/WorkbenchReplacement.app/Contents/_CodeSignature/CodeResources index f9bdf12..2414e8d 100644 --- a/build/Build/Products/Debug/WorkbenchReplacement.app/Contents/_CodeSignature/CodeResources +++ b/build/Build/Products/Debug/WorkbenchReplacement.app/Contents/_CodeSignature/CodeResources @@ -19,7 +19,7 @@ cdhash - PVnMv6+daaZqstZMQ+mKbqwrMxo= + 4cHefiu8TlGXWe220IW6MEbRNgU= requirement identifier "WorkbenchReplacement.debug" and anchor apple generic and certificate leaf[subject.CN] = "Apple Development: Mark Gregory (FLBRJ3BYDD)" and certificate 1[field.1.2.840.113635.100.6.2.1] /* exists */ diff --git a/build/Build/Products/Debug/WorkbenchReplacement.swiftmodule/Project/arm64-apple-macos.swiftsourceinfo b/build/Build/Products/Debug/WorkbenchReplacement.swiftmodule/Project/arm64-apple-macos.swiftsourceinfo index 3f7f3ac..8738761 100644 Binary files a/build/Build/Products/Debug/WorkbenchReplacement.swiftmodule/Project/arm64-apple-macos.swiftsourceinfo and b/build/Build/Products/Debug/WorkbenchReplacement.swiftmodule/Project/arm64-apple-macos.swiftsourceinfo differ diff --git a/build/Build/Products/Debug/WorkbenchReplacement.swiftmodule/arm64-apple-macos.swiftmodule b/build/Build/Products/Debug/WorkbenchReplacement.swiftmodule/arm64-apple-macos.swiftmodule index bdd390d..bb888bf 100644 Binary files a/build/Build/Products/Debug/WorkbenchReplacement.swiftmodule/arm64-apple-macos.swiftmodule and b/build/Build/Products/Debug/WorkbenchReplacement.swiftmodule/arm64-apple-macos.swiftmodule differ diff --git a/build/Index.noindex/DataStore/v5/units/MainView.o-CNVK7PE7JUX3 b/build/Index.noindex/DataStore/v5/units/MainView.o-CNVK7PE7JUX3 index 747b123..36fd591 100644 Binary files a/build/Index.noindex/DataStore/v5/units/MainView.o-CNVK7PE7JUX3 and b/build/Index.noindex/DataStore/v5/units/MainView.o-CNVK7PE7JUX3 differ diff --git a/build/Logs/Build/LogStoreManifest.plist b/build/Logs/Build/LogStoreManifest.plist index de8d537..1adb7e7 100644 --- a/build/Logs/Build/LogStoreManifest.plist +++ b/build/Logs/Build/LogStoreManifest.plist @@ -48,6 +48,48 @@ uniqueIdentifier 0C437F7E-D2AF-4660-9194-7E7E610BB7CE + 0D84D8B7-4EBD-46B6-9E1E-5318CA005DB1 + + className + IDECommandLineBuildLog + documentTypeString + <nil> + domainType + Xcode.IDEActivityLogDomainType.BuildLog + fileName + 0D84D8B7-4EBD-46B6-9E1E-5318CA005DB1.xcactivitylog + hasPrimaryLog + + primaryObservable + + highLevelStatus + S + totalNumberOfAnalyzerIssues + 0 + totalNumberOfErrors + 0 + totalNumberOfTestFailures + 0 + totalNumberOfWarnings + 0 + + schemeIdentifier-containerName + WorkbenchReplacement project + schemeIdentifier-schemeName + WorkbenchReplacement + schemeIdentifier-sharedScheme + 1 + signature + Building project WorkbenchReplacement with scheme WorkbenchReplacement and configuration Debug + timeStartedRecording + 813880069.98529804 + timeStoppedRecording + 813880086.21717203 + title + Building project WorkbenchReplacement with scheme WorkbenchReplacement and configuration Debug + uniqueIdentifier + 0D84D8B7-4EBD-46B6-9E1E-5318CA005DB1 + 27B7943C-710F-4B49-802F-4D4346522832 className @@ -132,48 +174,6 @@ uniqueIdentifier 9B404BEF-D5CC-4B4B-93CF-3A0C95C748D2 - C0104C63-BBE9-4E40-811C-E7DC2E1277F6 - - className - IDECommandLineBuildLog - documentTypeString - <nil> - domainType - Xcode.IDEActivityLogDomainType.BuildLog - fileName - C0104C63-BBE9-4E40-811C-E7DC2E1277F6.xcactivitylog - hasPrimaryLog - - primaryObservable - - highLevelStatus - S - totalNumberOfAnalyzerIssues - 0 - totalNumberOfErrors - 0 - totalNumberOfTestFailures - 0 - totalNumberOfWarnings - 0 - - schemeIdentifier-containerName - WorkbenchReplacement project - schemeIdentifier-schemeName - WorkbenchReplacement - schemeIdentifier-sharedScheme - 1 - signature - Building project WorkbenchReplacement with scheme WorkbenchReplacement and configuration Debug - timeStartedRecording - 813879412.71912897 - timeStoppedRecording - 813879428.48907101 - title - Building project WorkbenchReplacement with scheme WorkbenchReplacement and configuration Debug - uniqueIdentifier - C0104C63-BBE9-4E40-811C-E7DC2E1277F6 - FE752EBA-0994-486D-9A0C-1DA5C49E3451 className diff --git a/build/ModuleCache.noindex/Session.modulevalidation b/build/ModuleCache.noindex/Session.modulevalidation index 2486e5a..305518f 100644 --- a/build/ModuleCache.noindex/Session.modulevalidation +++ b/build/ModuleCache.noindex/Session.modulevalidation @@ -1 +1 @@ -1792187127.0273461: Module build session file for module cache at Path(str: "/Volumes/Developer/Applications/WorkbenchReplacement/build/ModuleCache.noindex") +1792187270.412821: Module build session file for module cache at Path(str: "/Volumes/Developer/Applications/WorkbenchReplacement/build/ModuleCache.noindex") diff --git a/build/SDKStatCaches.noindex/macosx27.0-26A425-e829b21a6f8c9b70876ba0858053c0447231f6df4def33e022ae2eb8f782df1a.sdkstatcache b/build/SDKStatCaches.noindex/macosx27.0-26A425-e829b21a6f8c9b70876ba0858053c0447231f6df4def33e022ae2eb8f782df1a.sdkstatcache index 462f1c3..8999d90 100644 Binary files a/build/SDKStatCaches.noindex/macosx27.0-26A425-e829b21a6f8c9b70876ba0858053c0447231f6df4def33e022ae2eb8f782df1a.sdkstatcache and b/build/SDKStatCaches.noindex/macosx27.0-26A425-e829b21a6f8c9b70876ba0858053c0447231f6df4def33e022ae2eb8f782df1a.sdkstatcache differ diff --git a/build/info.plist b/build/info.plist index 13cd219..cc19de9 100644 --- a/build/info.plist +++ b/build/info.plist @@ -3,7 +3,7 @@ LastAccessedDate - 2026-10-16T21:45:26Z + 2026-10-16T21:47:50Z WorkspacePath /Volumes/Developer/Applications/WorkbenchReplacement/WorkbenchReplacement.xcodeproj