diff --git a/PHASE21-NOTES.md b/PHASE21-NOTES.md new file mode 100644 index 0000000..e919c8f --- /dev/null +++ b/PHASE21-NOTES.md @@ -0,0 +1,17 @@ +# Phase 21 — SQL Query Plan Inspector + +Based on Phase 20. + +## Added +- **Explain** button in SQL editor toolbar. Uses selected SQL when present, otherwise editor SQL. +- Separate query-plan window showing MySQL `EXPLAIN` in table format or `EXPLAIN FORMAT=JSON`. +- Shows optimizer plan columns in the existing native result grid; JSON format is selectable and copyable. +- Displays diagnostic query duration and server errors. +- Limits analysis to SELECT/WITH statements, does not run `EXPLAIN ANALYZE` (which executes the query). +- Does not replace editor query results or add EXPLAIN to normal query history. + +## Notes +- Requires an active connection and MySQL permissions to explain the query. +- `EXPLAIN` gives estimated plans, not actual execution metrics. +- Only a single SQL statement should be selected; multi-statement SQL is not supported in the plan inspector. +- Not built in Xcode or tested against a live MySQL server in this environment. diff --git a/WorkbenchReplacement/MainView.swift b/WorkbenchReplacement/MainView.swift index 1e04081..36bda32 100644 --- a/WorkbenchReplacement/MainView.swift +++ b/WorkbenchReplacement/MainView.swift @@ -35,6 +35,7 @@ struct MainView: View { private let resultPageSize = 1_000 @State private var showingEditor = false @State private var showingDatabaseTransfer = false + @State private var showingQueryPlan = false @State private var showingQueryHistory = false @State private var showingSavedQueries = false @State private var showingSaveQuery = false @@ -86,6 +87,9 @@ struct MainView: View { model.loadDatabases() } } + .sheet(isPresented: $showingQueryPlan) { + WorkbenchExplainView(model: model, statement: selectedSQL.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ? model.sql : selectedSQL) + } .sheet(isPresented: $showingQueryHistory) { SQLHistorySheet(model: model) } @@ -683,6 +687,14 @@ struct MainView: View { .disabled(selectedSQL.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty) .help("Execute selected SQL without modifying the editor") + Button { + showingQueryPlan = true + } label: { + Label("Explain", systemImage: "chart.bar.doc.horizontal") + } + .disabled(model.isRunning || model.selected == nil) + .help("Inspect the optimizer plan for the selected SQL or editor contents") + Button { model.formatSQL() } label: { @@ -1317,6 +1329,130 @@ struct SQLTextView: NSViewRepresentable { // MARK: - Connection Editor + +// MARK: - Phase 21: Native MySQL query plan inspector + +private struct WorkbenchExplainView: View { + @Environment(\.dismiss) private var dismiss + @ObservedObject var model: DatabaseModel + let statement: String + + @State private var plan = QueryResult() + @State private var errorText: String? + @State private var isLoading = false + @State private var useJSON = false + @State private var duration: TimeInterval? + + private var normalizedStatement: String { + var value = statement.trimmingCharacters(in: .whitespacesAndNewlines) + if value.hasSuffix(";") { value.removeLast() } + return value.trimmingCharacters(in: .whitespacesAndNewlines) + } + + private var eligible: Bool { + // Deliberately restrict this inspector to SELECT queries. EXPLAIN for + // UPDATE/DELETE is supported by MySQL but not offered here to keep + // the first diagnostic release conservative. + let upper = normalizedStatement.uppercased() + return upper.hasPrefix("SELECT ") || upper.hasPrefix("SELECT\n") || + upper.hasPrefix("WITH ") || upper.hasPrefix("WITH\n") + } + + var body: some View { + VStack(alignment: .leading, spacing: 12) { + HStack { + Label("Query Execution Plan", systemImage: "chart.bar.doc.horizontal") + .font(.title2.bold()) + Spacer() + Button("Close") { dismiss() } + } + Text("Uses MySQL EXPLAIN to inspect the optimizer's estimated plan without executing the SELECT statement itself.") + .font(.callout) + .foregroundStyle(.secondary) + Text(normalizedStatement) + .font(.system(.callout, design: .monospaced)) + .lineLimit(4) + .textSelection(.enabled) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(8) + .background(Color(nsColor: .controlBackgroundColor)) + HStack { + Picker("Plan format", selection: $useJSON) { + Text("Table").tag(false) + Text("JSON").tag(true) + } + .pickerStyle(.segmented) + .frame(width: 240) + Button("Run EXPLAIN") { Task { await loadPlan() } } + .disabled(isLoading || !eligible || model.selected == nil) + if isLoading { ProgressView().controlSize(.small) } + if let duration { + Text(String(format: "%.3f s", duration)) + .foregroundStyle(.secondary) + } + Spacer() + } + if !eligible { + ContentUnavailableView("SELECT query required", systemImage: "exclamationmark.triangle", description: Text("Select a SELECT or WITH query in the SQL editor before opening EXPLAIN.")) + } else if let errorText { + ScrollView { + Text(errorText) + .foregroundStyle(.red) + .textSelection(.enabled) + .frame(maxWidth: .infinity, alignment: .leading) + } + } else if useJSON, let json = plan.rows.first?.first { + ScrollView([.vertical, .horizontal]) { + Text(json) + .font(.system(.body, design: .monospaced)) + .textSelection(.enabled) + .frame(maxWidth: .infinity, alignment: .leading) + } + } else if !plan.columns.isEmpty { + NativeResultGrid(columns: plan.columns, rows: plan.rows) + } else { + ContentUnavailableView("No plan loaded", systemImage: "chart.bar.doc.horizontal") + } + Text("EXPLAIN shows estimates, not actual query execution times. The plan depends on the server's current statistics and configuration.") + .font(.caption) + .foregroundStyle(.secondary) + } + .padding(16) + .frame(minWidth: 760, minHeight: 490) + .onChange(of: useJSON) { _, _ in + plan = QueryResult() + errorText = nil + Task { await loadPlan() } + } + .task { await loadPlan() } + } + + @MainActor + private func loadPlan() async { + guard eligible, let profile = model.selected, !isLoading else { return } + isLoading = true + errorText = nil + plan = QueryResult() + let started = Date() + defer { + duration = Date().timeIntervalSince(started) + isLoading = false + } + let database = model.selectedDatabase.isEmpty ? profile.database : model.selectedDatabase + let query = (useJSON ? "EXPLAIN FORMAT=JSON " : "EXPLAIN ") + normalizedStatement + do { + plan = try await NativeMySQLRunner.run( + profile: profile, + database: database, + password: PasswordStore.read(profile.id), + sql: query + ) + } catch { + errorText = "EXPLAIN failed: " + error.localizedDescription + } + } +} + struct ConnectionEditor: View { @Binding var profile: ConnectionProfile @Binding var password: String diff --git a/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/App.o b/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/App.o index 37579cf..135ec37 100644 Binary files a/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/App.o and b/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/App.o differ diff --git a/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/App.swiftdeps b/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/App.swiftdeps index 265fa65..e135ec8 100644 Binary files a/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/App.swiftdeps and b/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/App.swiftdeps differ 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 17d8b18..946163a 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 683b07f..d2b1ced 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 @@ -7,7 +7,7 @@ "key": "Save SQL Query", "location": { "startingColumn": 16, - "startingLine": 95 + "startingLine": 99 } }, { @@ -15,7 +15,7 @@ "key": "Query name", "location": { "startingColumn": 23, - "startingLine": 96 + "startingLine": 100 } }, { @@ -23,7 +23,7 @@ "key": "Save", "location": { "startingColumn": 20, - "startingLine": 97 + "startingLine": 101 } }, { @@ -31,7 +31,7 @@ "key": "Cancel", "location": { "startingColumn": 20, - "startingLine": 98 + "startingLine": 102 } }, { @@ -39,7 +39,7 @@ "key": "Save the current SQL in your local query library.", "location": { "startingColumn": 18, - "startingLine": 100 + "startingLine": 104 } }, { @@ -47,7 +47,7 @@ "key": "MySQL Workbench", "location": { "startingColumn": 18, - "startingLine": 124 + "startingLine": 128 } }, { @@ -55,7 +55,7 @@ "key": "Navigator", "location": { "startingColumn": 20, - "startingLine": 213 + "startingLine": 217 } }, { @@ -63,7 +63,7 @@ "key": "Administration", "location": { "startingColumn": 22, - "startingLine": 214 + "startingLine": 218 } }, { @@ -71,7 +71,7 @@ "key": "Schemas", "location": { "startingColumn": 22, - "startingLine": 215 + "startingLine": 219 } }, { @@ -79,7 +79,7 @@ "key": "SCHEMAS", "location": { "startingColumn": 22, - "startingLine": 235 + "startingLine": 239 } }, { @@ -87,7 +87,7 @@ "key": "Edit Connection", "location": { "startingColumn": 36, - "startingLine": 289 + "startingLine": 293 } }, { @@ -95,7 +95,7 @@ "key": "Set as Default Schema", "location": { "startingColumn": 24, - "startingLine": 412 + "startingLine": 416 } }, { @@ -103,7 +103,7 @@ "key": "Refresh Tables", "location": { "startingColumn": 24, - "startingLine": 416 + "startingLine": 420 } }, { @@ -111,7 +111,7 @@ "key": "Load Tables", "location": { "startingColumn": 28, - "startingLine": 486 + "startingLine": 490 } }, { @@ -119,7 +119,7 @@ "key": "Tables", "location": { "startingColumn": 22, - "startingLine": 459 + "startingLine": 463 } }, { @@ -127,7 +127,7 @@ "key": "Select First 100 Rows", "location": { "startingColumn": 20, - "startingLine": 542 + "startingLine": 546 } }, { @@ -135,7 +135,7 @@ "key": "New Query Tab", "location": { "startingColumn": 19, - "startingLine": 663 + "startingLine": 667 } }, { @@ -143,7 +143,7 @@ "key": "Default Schema:", "location": { "startingColumn": 18, - "startingLine": 720 + "startingLine": 732 } }, { @@ -151,7 +151,7 @@ "key": "SQL Editor", "location": { "startingColumn": 18, - "startingLine": 730 + "startingLine": 742 } }, { @@ -159,7 +159,7 @@ "key": "Execute SQL", "location": { "startingColumn": 19, - "startingLine": 676 + "startingLine": 680 } }, { @@ -167,7 +167,15 @@ "key": "Execute selected SQL without modifying the editor", "location": { "startingColumn": 19, - "startingLine": 684 + "startingLine": 688 + } + }, + { + "comment": "", + "key": "Inspect the optimizer plan for the selected SQL or editor contents", + "location": { + "startingColumn": 19, + "startingLine": 696 } }, { @@ -175,7 +183,7 @@ "key": "Format SQL", "location": { "startingColumn": 19, - "startingLine": 691 + "startingLine": 703 } }, { @@ -183,7 +191,7 @@ "key": "Query execution history", "location": { "startingColumn": 19, - "startingLine": 701 + "startingLine": 713 } }, { @@ -191,7 +199,7 @@ "key": "Open saved SQL queries", "location": { "startingColumn": 19, - "startingLine": 708 + "startingLine": 720 } }, { @@ -199,7 +207,7 @@ "key": "Save current SQL query", "location": { "startingColumn": 19, - "startingLine": 716 + "startingLine": 728 } }, { @@ -207,7 +215,15 @@ "key": "Run Selection", "location": { "startingColumn": 23, - "startingLine": 681 + "startingLine": 685 + } + }, + { + "comment": "", + "key": "Explain", + "location": { + "startingColumn": 23, + "startingLine": 693 } }, { @@ -215,7 +231,7 @@ "key": "History", "location": { "startingColumn": 23, - "startingLine": 699 + "startingLine": 711 } }, { @@ -223,7 +239,7 @@ "key": "Saved", "location": { "startingColumn": 23, - "startingLine": 706 + "startingLine": 718 } }, { @@ -231,7 +247,7 @@ "key": "%lld", "location": { "startingColumn": 26, - "startingLine": 770 + "startingLine": 782 } }, { @@ -239,7 +255,7 @@ "key": "Result Grid", "location": { "startingColumn": 22, - "startingLine": 802 + "startingLine": 814 } }, { @@ -247,7 +263,7 @@ "key": "Filter rows", "location": { "startingColumn": 31, - "startingLine": 808 + "startingLine": 820 } }, { @@ -255,7 +271,7 @@ "key": "Sort", "location": { "startingColumn": 28, - "startingLine": 811 + "startingLine": 823 } }, { @@ -263,7 +279,7 @@ "key": "Ascending", "location": { "startingColumn": 50, - "startingLine": 817 + "startingLine": 829 } }, { @@ -271,7 +287,7 @@ "key": "Descending", "location": { "startingColumn": 64, - "startingLine": 817 + "startingLine": 829 } }, { @@ -279,7 +295,7 @@ "key": "Columns", "location": { "startingColumn": 26, - "startingLine": 820 + "startingLine": 832 } }, { @@ -287,7 +303,7 @@ "key": "Export CSV", "location": { "startingColumn": 28, - "startingLine": 835 + "startingLine": 847 } }, { @@ -295,7 +311,7 @@ "key": "Show All Columns", "location": { "startingColumn": 32, - "startingLine": 821 + "startingLine": 833 } }, { @@ -303,7 +319,7 @@ "key": "No query results", "location": { "startingColumn": 26, - "startingLine": 852 + "startingLine": 864 } }, { @@ -311,7 +327,7 @@ "key": "%lld matching rows · showing %lld–%lld", "location": { "startingColumn": 26, - "startingLine": 859 + "startingLine": 871 } }, { @@ -319,7 +335,7 @@ "key": "Page %lld / %lld", "location": { "startingColumn": 26, - "startingLine": 864 + "startingLine": 876 } }, { @@ -327,7 +343,7 @@ "key": "Previous", "location": { "startingColumn": 28, - "startingLine": 862 + "startingLine": 874 } }, { @@ -335,7 +351,7 @@ "key": "Next", "location": { "startingColumn": 28, - "startingLine": 865 + "startingLine": 877 } }, { @@ -343,7 +359,7 @@ "key": "Action Output", "location": { "startingColumn": 22, - "startingLine": 950 + "startingLine": 962 } }, { @@ -351,7 +367,7 @@ "key": "UTF-8", "location": { "startingColumn": 18, - "startingLine": 1025 + "startingLine": 1037 } }, { @@ -359,7 +375,7 @@ "key": "Filter SQL or connection", "location": { "startingColumn": 23, - "startingLine": 1076 + "startingLine": 1088 } }, { @@ -367,7 +383,7 @@ "key": "Query History", "location": { "startingColumn": 22, - "startingLine": 1070 + "startingLine": 1082 } }, { @@ -375,7 +391,7 @@ "key": "Clear History", "location": { "startingColumn": 24, - "startingLine": 1072 + "startingLine": 1084 } }, { @@ -383,7 +399,7 @@ "key": "Done", "location": { "startingColumn": 24, - "startingLine": 1074 + "startingLine": 1086 } }, { @@ -391,7 +407,7 @@ "key": "Open in New Tab", "location": { "startingColumn": 32, - "startingLine": 1088 + "startingLine": 1100 } }, { @@ -399,7 +415,7 @@ "key": "Filter saved queries", "location": { "startingColumn": 23, - "startingLine": 1118 + "startingLine": 1130 } }, { @@ -407,7 +423,7 @@ "key": "Saved SQL Queries", "location": { "startingColumn": 22, - "startingLine": 1114 + "startingLine": 1126 } }, { @@ -415,7 +431,7 @@ "key": "Done", "location": { "startingColumn": 24, - "startingLine": 1116 + "startingLine": 1128 } }, { @@ -423,7 +439,95 @@ "key": "Open", "location": { "startingColumn": 28, - "startingLine": 1131 + "startingLine": 1143 + } + }, + { + "comment": "", + "key": "Uses MySQL EXPLAIN to inspect the optimizer's estimated plan without executing the SELECT statement itself.", + "location": { + "startingColumn": 18, + "startingLine": 1369 + } + }, + { + "comment": "", + "key": "Select a SELECT or WITH query in the SQL editor before opening EXPLAIN.", + "location": { + "startingColumn": 124, + "startingLine": 1396 + } + }, + { + "comment": "", + "key": "EXPLAIN shows estimates, not actual query execution times. The plan depends on the server's current statistics and configuration.", + "location": { + "startingColumn": 18, + "startingLine": 1416 + } + }, + { + "comment": "", + "key": "SELECT query required", + "location": { + "startingColumn": 40, + "startingLine": 1396 + } + }, + { + "comment": "", + "key": "No plan loaded", + "location": { + "startingColumn": 40, + "startingLine": 1414 + } + }, + { + "comment": "", + "key": "Query Execution Plan", + "location": { + "startingColumn": 23, + "startingLine": 1364 + } + }, + { + "comment": "", + "key": "Close", + "location": { + "startingColumn": 24, + "startingLine": 1367 + } + }, + { + "comment": "", + "key": "Plan format", + "location": { + "startingColumn": 24, + "startingLine": 1380 + } + }, + { + "comment": "", + "key": "Run EXPLAIN", + "location": { + "startingColumn": 24, + "startingLine": 1386 + } + }, + { + "comment": "", + "key": "Table", + "location": { + "startingColumn": 26, + "startingLine": 1381 + } + }, + { + "comment": "", + "key": "JSON", + "location": { + "startingColumn": 26, + "startingLine": 1382 } }, { @@ -431,7 +535,7 @@ "key": "Connection Settings", "location": { "startingColumn": 18, - "startingLine": 1330 + "startingLine": 1466 } }, { @@ -439,7 +543,7 @@ "key": "Connection Name", "location": { "startingColumn": 21, - "startingLine": 1335 + "startingLine": 1471 } }, { @@ -447,7 +551,7 @@ "key": "Hostname", "location": { "startingColumn": 21, - "startingLine": 1340 + "startingLine": 1476 } }, { @@ -455,7 +559,7 @@ "key": "Port", "location": { "startingColumn": 21, - "startingLine": 1345 + "startingLine": 1481 } }, { @@ -463,7 +567,7 @@ "key": "Username", "location": { "startingColumn": 21, - "startingLine": 1351 + "startingLine": 1487 } }, { @@ -471,7 +575,7 @@ "key": "Password (macOS Keychain)", "location": { "startingColumn": 21, - "startingLine": 1356 + "startingLine": 1492 } }, { @@ -479,7 +583,7 @@ "key": "Default Schema", "location": { "startingColumn": 21, - "startingLine": 1361 + "startingLine": 1497 } }, { @@ -487,7 +591,7 @@ "key": "Cancel", "location": { "startingColumn": 24, - "startingLine": 1369 + "startingLine": 1505 } }, { @@ -495,7 +599,7 @@ "key": "Save", "location": { "startingColumn": 24, - "startingLine": 1373 + "startingLine": 1509 } }, { @@ -503,7 +607,7 @@ "key": "Terminate MySQL session?", "location": { "startingColumn": 13, - "startingLine": 1868 + "startingLine": 2004 } }, { @@ -511,7 +615,7 @@ "key": "The server did not return any columns.", "location": { "startingColumn": 25, - "startingLine": 1801 + "startingLine": 1937 } }, { @@ -519,7 +623,7 @@ "key": "Loading %@…", "location": { "startingColumn": 30, - "startingLine": 1779 + "startingLine": 1915 } }, { @@ -527,7 +631,7 @@ "key": "No Data", "location": { "startingColumn": 21, - "startingLine": 1798 + "startingLine": 1934 } }, { @@ -535,7 +639,7 @@ "key": "Unable to Load %@", "location": { "startingColumn": 25, - "startingLine": 1785 + "startingLine": 1921 } }, { @@ -543,7 +647,7 @@ "key": "Retry", "location": { "startingColumn": 28, - "startingLine": 1791 + "startingLine": 1927 } }, { @@ -551,7 +655,7 @@ "key": "Changes require confirmation", "location": { "startingColumn": 26, - "startingLine": 1845 + "startingLine": 1981 } }, { @@ -559,7 +663,7 @@ "key": "Account", "location": { "startingColumn": 28, - "startingLine": 1824 + "startingLine": 1960 } }, { @@ -567,7 +671,7 @@ "key": "Show Grants", "location": { "startingColumn": 28, - "startingLine": 1835 + "startingLine": 1971 } }, { @@ -575,7 +679,7 @@ "key": "Manage Accounts…", "location": { "startingColumn": 28, - "startingLine": 1839 + "startingLine": 1975 } }, { @@ -583,7 +687,7 @@ "key": "Select account", "location": { "startingColumn": 30, - "startingLine": 1825 + "startingLine": 1961 } }, { @@ -591,7 +695,7 @@ "key": "%@@%@", "location": { "startingColumn": 38, - "startingLine": 1829 + "startingLine": 1965 } }, { @@ -599,7 +703,7 @@ "key": "%@ session %@", "location": { "startingColumn": 24, - "startingLine": 1876 + "startingLine": 2012 } }, { @@ -607,7 +711,7 @@ "key": "Cancel", "location": { "startingColumn": 20, - "startingLine": 1881 + "startingLine": 2017 } }, { @@ -615,7 +719,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": 1883 + "startingLine": 2019 } }, { @@ -623,7 +727,7 @@ "key": "Grants: %@", "location": { "startingColumn": 22, - "startingLine": 1897 + "startingLine": 2033 } }, { @@ -631,7 +735,7 @@ "key": "Close", "location": { "startingColumn": 24, - "startingLine": 1909 + "startingLine": 2045 } }, { @@ -639,7 +743,7 @@ "key": "Requires PROCESS privilege to see other users' sessions and appropriate privileges to terminate them.", "location": { "startingColumn": 18, - "startingLine": 1939 + "startingLine": 2075 } }, { @@ -647,7 +751,7 @@ "key": "Session", "location": { "startingColumn": 24, - "startingLine": 1919 + "startingLine": 2055 } }, { @@ -655,7 +759,7 @@ "key": "Stop Query", "location": { "startingColumn": 24, - "startingLine": 1926 + "startingLine": 2062 } }, { @@ -663,7 +767,7 @@ "key": "Disconnect Session", "location": { "startingColumn": 24, - "startingLine": 1928 + "startingLine": 2064 } }, { @@ -671,7 +775,7 @@ "key": "Select a session", "location": { "startingColumn": 26, - "startingLine": 1920 + "startingLine": 2056 } }, { @@ -679,7 +783,7 @@ "key": "ID %@", "location": { "startingColumn": 30, - "startingLine": 1922 + "startingLine": 2058 } }, { @@ -687,7 +791,7 @@ "key": "Filter results", "location": { "startingColumn": 27, - "startingLine": 2004 + "startingLine": 2140 } }, { @@ -695,7 +799,7 @@ "key": "Auto-refresh", "location": { "startingColumn": 20, - "startingLine": 2009 + "startingLine": 2145 } }, { @@ -703,7 +807,7 @@ "key": "Refresh every five seconds", "location": { "startingColumn": 23, - "startingLine": 2011 + "startingLine": 2147 } }, { @@ -711,7 +815,7 @@ "key": "Refresh", "location": { "startingColumn": 23, - "startingLine": 2016 + "startingLine": 2152 } }, { @@ -719,7 +823,7 @@ "key": "SQL Editor", "location": { "startingColumn": 23, - "startingLine": 2023 + "startingLine": 2159 } }, { @@ -727,7 +831,7 @@ "key": "Not loaded", "location": { "startingColumn": 22, - "startingLine": 2101 + "startingLine": 2237 } }, { @@ -735,7 +839,7 @@ "key": "Updated %@", "location": { "startingColumn": 21, - "startingLine": 2098 + "startingLine": 2234 } }, { @@ -743,7 +847,7 @@ "key": "%lld rows", "location": { "startingColumn": 18, - "startingLine": 2106 + "startingLine": 2242 } }, { @@ -751,7 +855,7 @@ "key": "Import SQL into %@?", "location": { "startingColumn": 13, - "startingLine": 2413 + "startingLine": 2549 } }, { @@ -759,7 +863,7 @@ "key": "Operation", "location": { "startingColumn": 20, - "startingLine": 2216 + "startingLine": 2352 } }, { @@ -767,7 +871,7 @@ "key": "Data Export", "location": { "startingColumn": 22, - "startingLine": 2217 + "startingLine": 2353 } }, { @@ -775,7 +879,7 @@ "key": "Data Import / Restore", "location": { "startingColumn": 22, - "startingLine": 2218 + "startingLine": 2354 } }, { @@ -783,7 +887,7 @@ "key": "MySQL Connection", "location": { "startingColumn": 26, - "startingLine": 2225 + "startingLine": 2361 } }, { @@ -791,7 +895,7 @@ "key": "Export Options", "location": { "startingColumn": 30, - "startingLine": 2249 + "startingLine": 2385 } }, { @@ -799,7 +903,7 @@ "key": "Import Options", "location": { "startingColumn": 30, - "startingLine": 2305 + "startingLine": 2441 } }, { @@ -807,7 +911,7 @@ "key": "Progress", "location": { "startingColumn": 26, - "startingLine": 2317 + "startingLine": 2453 } }, { @@ -815,7 +919,7 @@ "key": "Connection", "location": { "startingColumn": 36, - "startingLine": 2227 + "startingLine": 2363 } }, { @@ -823,7 +927,7 @@ "key": "Database", "location": { "startingColumn": 28, - "startingLine": 2231 + "startingLine": 2367 } }, { @@ -831,7 +935,7 @@ "key": "Select Database", "location": { "startingColumn": 30, - "startingLine": 2232 + "startingLine": 2368 } }, { @@ -839,7 +943,7 @@ "key": "Refresh Databases", "location": { "startingColumn": 32, - "startingLine": 2240 + "startingLine": 2376 } }, { @@ -847,7 +951,7 @@ "key": "No tables found. Select a database or refresh the list.", "location": { "startingColumn": 34, - "startingLine": 2271 + "startingLine": 2407 } }, { @@ -855,7 +959,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": 2300 + "startingLine": 2436 } }, { @@ -863,7 +967,7 @@ "key": "Table structure", "location": { "startingColumn": 32, - "startingLine": 2251 + "startingLine": 2387 } }, { @@ -871,7 +975,7 @@ "key": "Table data", "location": { "startingColumn": 32, - "startingLine": 2252 + "startingLine": 2388 } }, { @@ -879,7 +983,7 @@ "key": "Add DROP TABLE IF EXISTS", "location": { "startingColumn": 32, - "startingLine": 2253 + "startingLine": 2389 } }, { @@ -887,7 +991,7 @@ "key": "Include views (definitions)", "location": { "startingColumn": 32, - "startingLine": 2255 + "startingLine": 2391 } }, { @@ -895,7 +999,7 @@ "key": "Include triggers", "location": { "startingColumn": 32, - "startingLine": 2257 + "startingLine": 2393 } }, { @@ -903,7 +1007,7 @@ "key": "Include stored procedures and functions", "location": { "startingColumn": 32, - "startingLine": 2259 + "startingLine": 2395 } }, { @@ -911,7 +1015,7 @@ "key": "Loading tables…", "location": { "startingColumn": 57, - "startingLine": 2269 + "startingLine": 2405 } }, { @@ -919,7 +1023,7 @@ "key": "Tables (%lld selected)", "location": { "startingColumn": 34, - "startingLine": 2262 + "startingLine": 2398 } }, { @@ -927,7 +1031,7 @@ "key": "All", "location": { "startingColumn": 36, - "startingLine": 2264 + "startingLine": 2400 } }, { @@ -935,7 +1039,7 @@ "key": "None", "location": { "startingColumn": 36, - "startingLine": 2265 + "startingLine": 2401 } }, { @@ -943,7 +1047,7 @@ "key": "Refresh", "location": { "startingColumn": 36, - "startingLine": 2266 + "startingLine": 2402 } }, { @@ -951,7 +1055,7 @@ "key": "Views (%lld selected)", "location": { "startingColumn": 38, - "startingLine": 2285 + "startingLine": 2421 } }, { @@ -959,7 +1063,7 @@ "key": "All", "location": { "startingColumn": 40, - "startingLine": 2287 + "startingLine": 2423 } }, { @@ -967,7 +1071,7 @@ "key": "None", "location": { "startingColumn": 40, - "startingLine": 2288 + "startingLine": 2424 } }, { @@ -975,7 +1079,7 @@ "key": "Import SQL statements into the selected database.", "location": { "startingColumn": 30, - "startingLine": 2307 + "startingLine": 2443 } }, { @@ -983,7 +1087,7 @@ "key": "Supports MySQL DELIMITER directives, triggers and stored routines. Import is not transactional; use a verified backup before restoring.", "location": { "startingColumn": 30, - "startingLine": 2310 + "startingLine": 2446 } }, { @@ -991,7 +1095,7 @@ "key": "Current table: %@ / %@ rows · Total exported: %@", "location": { "startingColumn": 30, - "startingLine": 2334 + "startingLine": 2470 } }, { @@ -999,7 +1103,7 @@ "key": "Close", "location": { "startingColumn": 24, - "startingLine": 2348 + "startingLine": 2484 } }, { @@ -1007,7 +1111,7 @@ "key": "Verify Backup…", "location": { "startingColumn": 28, - "startingLine": 2356 + "startingLine": 2492 } }, { @@ -1015,7 +1119,7 @@ "key": "Verify an SQL file against its SHA-256 checksum sidecar", "location": { "startingColumn": 27, - "startingLine": 2359 + "startingLine": 2495 } }, { @@ -1023,7 +1127,7 @@ "key": "Verify Restored Database…", "location": { "startingColumn": 28, - "startingLine": 2360 + "startingLine": 2496 } }, { @@ -1031,7 +1135,7 @@ "key": "Compare restored table row counts and object names with the export manifest", "location": { "startingColumn": 27, - "startingLine": 2364 + "startingLine": 2500 } }, { @@ -1039,7 +1143,7 @@ "key": "Cancel", "location": { "startingColumn": 28, - "startingLine": 2368 + "startingLine": 2504 } }, { @@ -1047,7 +1151,7 @@ "key": "Export SQL…", "location": { "startingColumn": 31, - "startingLine": 2375 + "startingLine": 2511 } }, { @@ -1055,7 +1159,7 @@ "key": "Import SQL…", "location": { "startingColumn": 31, - "startingLine": 2376 + "startingLine": 2512 } }, { @@ -1063,7 +1167,7 @@ "key": "Import SQL", "location": { "startingColumn": 20, - "startingLine": 2416 + "startingLine": 2552 } }, { @@ -1071,7 +1175,7 @@ "key": "Cancel", "location": { "startingColumn": 20, - "startingLine": 2424 + "startingLine": 2560 } }, { @@ -1079,7 +1183,7 @@ "key": "SQL files can modify or delete existing database objects and data. Ensure you have a backup before continuing.", "location": { "startingColumn": 17, - "startingLine": 2429 + "startingLine": 2565 } }, { @@ -1087,7 +1191,7 @@ "key": "Database Import / Export", "location": { "startingColumn": 22, - "startingLine": 2441 + "startingLine": 2577 } }, { @@ -1095,7 +1199,7 @@ "key": "Built-in native MySQL database tools", "location": { "startingColumn": 22, - "startingLine": 2444 + "startingLine": 2580 } }, { @@ -1103,7 +1207,7 @@ "key": "Confirm %@", "location": { "startingColumn": 16, - "startingLine": 3495 + "startingLine": 3631 } }, { @@ -1111,7 +1215,7 @@ "key": "Manage MySQL Accounts", "location": { "startingColumn": 18, - "startingLine": 3445 + "startingLine": 3581 } }, { @@ -1119,7 +1223,7 @@ "key": "Account changes run on %@. Review the SQL before applying it.", "location": { "startingColumn": 18, - "startingLine": 3446 + "startingLine": 3582 } }, { @@ -1127,7 +1231,7 @@ "key": "Operation", "location": { "startingColumn": 24, - "startingLine": 3449 + "startingLine": 3585 } }, { @@ -1135,7 +1239,7 @@ "key": "Username", "location": { "startingColumn": 27, - "startingLine": 3454 + "startingLine": 3590 } }, { @@ -1143,7 +1247,7 @@ "key": "Host", "location": { "startingColumn": 27, - "startingLine": 3456 + "startingLine": 3592 } }, { @@ -1151,7 +1255,7 @@ "key": "Password", "location": { "startingColumn": 33, - "startingLine": 3458 + "startingLine": 3594 } }, { @@ -1159,7 +1263,7 @@ "key": "Database", "location": { "startingColumn": 31, - "startingLine": 3461 + "startingLine": 3597 } }, { @@ -1167,7 +1271,7 @@ "key": "Privilege", "location": { "startingColumn": 28, - "startingLine": 3462 + "startingLine": 3598 } }, { @@ -1175,7 +1279,7 @@ "key": "Close", "location": { "startingColumn": 24, - "startingLine": 3477 + "startingLine": 3613 } }, { @@ -1183,7 +1287,7 @@ "key": "Review SQL…", "location": { "startingColumn": 24, - "startingLine": 3479 + "startingLine": 3615 } }, { @@ -1191,7 +1295,7 @@ "key": "Cancel", "location": { "startingColumn": 20, - "startingLine": 3496 + "startingLine": 3632 } }, { @@ -1199,7 +1303,7 @@ "key": "Apply Change", "location": { "startingColumn": 20, - "startingLine": 3497 + "startingLine": 3633 } }, { @@ -1207,7 +1311,7 @@ "key": "Run this operation against %@?\n\n%@", "location": { "startingColumn": 18, - "startingLine": 3502 + "startingLine": 3638 } } ] 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 f81cfee..31f9b51 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 3fcd562..955ed4f 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 5e5d6ee..066cc3e 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 c1a0bbe..265e392 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 677f35f..2a7e471 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 239b15a..1e9cdf9 100644 --- a/build/Build/Intermediates.noindex/XCBuildData/registered-launchservices.txt +++ b/build/Build/Intermediates.noindex/XCBuildData/registered-launchservices.txt @@ -26,3 +26,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 c2a05f8..4252617 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 31f1bde..eb1109b 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 6e1ba55..0c0fede 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 d182441..70bd4df 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 - /Hh7cyggIpVXXYLygkR1SnAFcAM= + gxPm9V32PbdbRR8yOyYXPS9q9hU= 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 c1a0bbe..265e392 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 5e5d6ee..066cc3e 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 5abe022..14f87b5 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 439a771..fe286c4 100644 --- a/build/Logs/Build/LogStoreManifest.plist +++ b/build/Logs/Build/LogStoreManifest.plist @@ -6,48 +6,6 @@ 13 logs - 04704647-E534-492A-ADC4-87C265C050C8 - - className - IDECommandLineBuildLog - documentTypeString - <nil> - domainType - Xcode.IDEActivityLogDomainType.BuildLog - fileName - 04704647-E534-492A-ADC4-87C265C050C8.xcactivitylog - hasPrimaryLog - - primaryObservable - - highLevelStatus - E - totalNumberOfAnalyzerIssues - 0 - totalNumberOfErrors - 1 - 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 - 813879039.75688195 - timeStoppedRecording - 813879050.66503203 - title - Building project WorkbenchReplacement with scheme WorkbenchReplacement and configuration Debug - uniqueIdentifier - 04704647-E534-492A-ADC4-87C265C050C8 - 0C437F7E-D2AF-4660-9194-7E7E610BB7CE className @@ -216,6 +174,48 @@ uniqueIdentifier 67C568D7-BB93-4240-AB48-EF7CBFC85D9F + 9B404BEF-D5CC-4B4B-93CF-3A0C95C748D2 + + className + IDECommandLineBuildLog + documentTypeString + <nil> + domainType + Xcode.IDEActivityLogDomainType.BuildLog + fileName + 9B404BEF-D5CC-4B4B-93CF-3A0C95C748D2.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 + 813879702.54720795 + timeStoppedRecording + 813879718.93544304 + title + Building project WorkbenchReplacement with scheme WorkbenchReplacement and configuration Debug + uniqueIdentifier + 9B404BEF-D5CC-4B4B-93CF-3A0C95C748D2 + C0104C63-BBE9-4E40-811C-E7DC2E1277F6 className diff --git a/build/ModuleCache.noindex/Session.modulevalidation b/build/ModuleCache.noindex/Session.modulevalidation index 51b84f1..ef88f85 100644 --- a/build/ModuleCache.noindex/Session.modulevalidation +++ b/build/ModuleCache.noindex/Session.modulevalidation @@ -1 +1 @@ -1792186785.4944181: Module build session file for module cache at Path(str: "/Volumes/Developer/Applications/WorkbenchReplacement/build/ModuleCache.noindex") +1792186902.93557: 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 84a6b72..2ac346d 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 07969ec..e27536f 100644 --- a/build/info.plist +++ b/build/info.plist @@ -3,7 +3,7 @@ LastAccessedDate - 2026-10-16T21:39:45Z + 2026-10-16T21:41:42Z WorkspacePath /Volumes/Developer/Applications/WorkbenchReplacement/WorkbenchReplacement.xcodeproj