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