The second file contains a SHA-256 checksum of the SQL backup.

This allows the application to detect whether the backup file has changed or become corrupted.

2. Verify Backup button

The Import/Export window now includes Verify Backup….

Select an exported SQL file, and the application calculates its checksum and compares it with the accompanying checksum file.

3. Automatic verification before import

When importing an SQL file, WorkbenchReplacement checks for an accompanying .sha256 file.

If one exists, its checksum must match before SQL execution begins.

Third-party SQL files without checksum files can still be imported.

4. Large-file handling

Checksum calculation reads files in 1 MB blocks rather than loading the entire file into memory.

This reduces memory usage when verifying large backups.
This commit is contained in:
Mark Gregory
2026-10-16 21:58:41 +01:00
parent 2b6eb249ce
commit d75515ffbc
27 changed files with 399 additions and 191 deletions
+161 -4
View File
@@ -1,6 +1,7 @@
import SwiftUI import SwiftUI
import AppKit import AppKit
import CryptoKit
// MARK: - Classic Workbench Colours // MARK: - Classic Workbench Colours
@@ -1578,6 +1579,8 @@ struct WorkbenchDatabaseTransferView: View {
@State private var exportData = true @State private var exportData = true
@State private var includeDrop = false @State private var includeDrop = false
@State private var includeViews = true @State private var includeViews = true
@State private var includeTriggers = false
@State private var includeRoutines = false
@State private var availableViews: [String] = [] @State private var availableViews: [String] = []
@State private var selectedViews: Set<String> = [] @State private var selectedViews: Set<String> = []
@State private var loadingTables = false @State private var loadingTables = false
@@ -1635,6 +1638,10 @@ struct WorkbenchDatabaseTransferView: View {
.disabled(!exportStructure) .disabled(!exportStructure)
Toggle("Include views (definitions)", isOn: $includeViews) Toggle("Include views (definitions)", isOn: $includeViews)
.disabled(!exportStructure) .disabled(!exportStructure)
Toggle("Include triggers", isOn: $includeTriggers)
.disabled(!exportStructure)
Toggle("Include stored procedures and functions", isOn: $includeRoutines)
.disabled(!exportStructure)
HStack { HStack {
Text("Tables (\(selectedTables.count) selected)") Text("Tables (\(selectedTables.count) selected)")
Spacer() Spacer()
@@ -1674,7 +1681,7 @@ struct WorkbenchDatabaseTransferView: View {
)) ))
} }
} }
Text("Exports base tables and selected views. Triggers, routines, consistent snapshots and guaranteed row ordering are not yet supported.") Text("Triggers and routines require SHOW CREATE privileges. This exporter does not provide transaction-consistent snapshots or guaranteed row ordering.")
.font(.caption).foregroundStyle(.secondary) .font(.caption).foregroundStyle(.secondary)
} }
} }
@@ -1717,6 +1724,13 @@ struct WorkbenchDatabaseTransferView: View {
Spacer() Spacer()
if !isWorking {
Button("Verify Backup…") {
chooseBackupToVerify()
}
.help("Verify an SQL file against its SHA-256 checksum sidecar")
}
if isWorking { if isWorking {
Button("Cancel") { Button("Cancel") {
operationTask?.cancel() operationTask?.cancel()
@@ -1819,6 +1833,28 @@ struct WorkbenchDatabaseTransferView: View {
} }
} }
private func chooseBackupToVerify() {
let panel = NSOpenPanel()
panel.title = "Verify SQL Backup"
panel.canChooseDirectories = false
panel.canChooseFiles = true
guard panel.runModal() == .OK, let url = panel.url else { return }
operationTask = Task {
isWorking = true
progress = 0
defer { isWorking = false; operationTask = nil }
do {
try await Task.detached(priority: .utility) {
try WorkbenchBackupIntegrity.verify(url)
}.value
progress = 1
status = "Verified SHA-256: \(url.lastPathComponent)"
} catch {
status = "Verification failed: \(error.localizedDescription)"
}
}
}
private func chooseImportSource() { private func chooseImportSource() {
let panel = NSOpenPanel() let panel = NSOpenPanel()
panel.title = "Import MySQL SQL File" panel.title = "Import MySQL SQL File"
@@ -1921,7 +1957,7 @@ struct WorkbenchDatabaseTransferView: View {
let views = exportStructure && includeViews let views = exportStructure && includeViews
? availableViews.filter { selectedViews.contains($0) } ? availableViews.filter { selectedViews.contains($0) }
: [] : []
guard !selectedTables.isEmpty || !views.isEmpty else { return } guard !selectedTables.isEmpty || !views.isEmpty || (exportStructure && (includeTriggers || includeRoutines)) else { return }
isWorking = true isWorking = true
progress = 0 progress = 0
let tables = availableTables.filter { selectedTables.contains($0) } let tables = availableTables.filter { selectedTables.contains($0) }
@@ -2010,6 +2046,59 @@ struct WorkbenchDatabaseTransferView: View {
try write(row[1] + ";\n") try write(row[1] + ";\n")
progress = Double(tables.count + index + 1) / Double(max(tables.count + views.count, 1)) progress = Double(tables.count + index + 1) / Double(max(tables.count + views.count, 1))
} }
// Routines and triggers use client-side delimiters. The importer
// strips these directives and sends each definition as one query.
if exportStructure && includeRoutines {
let routines = try await execute("""
SELECT ROUTINE_NAME, ROUTINE_TYPE
FROM information_schema.ROUTINES
WHERE ROUTINE_SCHEMA = \(sqlLiteral(database))
ORDER BY ROUTINE_TYPE, ROUTINE_NAME
""")
for routine in routines.rows {
try Task.checkCancellation()
guard routine.count >= 2 else { continue }
let name = routine[0]
let kind = routine[1].uppercased()
guard kind == "PROCEDURE" || kind == "FUNCTION" else { continue }
status = "Exporting \(kind.lowercased()) \(name)"
let qualified = "\(db).\(quoteIdentifier(name))"
let definition = try await execute("SHOW CREATE \(kind) \(qualified)")
// SHOW CREATE PROCEDURE/FUNCTION: Name, sql_mode, Create ..., ...
guard let row = definition.rows.first, row.count > 2,
!row[2].isEmpty else {
throw WorkbenchTransferError.message("Missing CREATE \(kind) for \(name)")
}
try write("\n-- \(kind) \(qualified)\n")
try write("DELIMITER $$\n")
if includeDrop { try write("DROP \(kind) IF EXISTS \(qualified)$$\n") }
try write(row[2] + "$$\nDELIMITER ;\n")
}
}
if exportStructure && includeTriggers {
let triggers = try await execute("""
SELECT TRIGGER_NAME
FROM information_schema.TRIGGERS
WHERE TRIGGER_SCHEMA = \(sqlLiteral(database))
ORDER BY TRIGGER_NAME
""")
for trigger in triggers.rows {
try Task.checkCancellation()
guard let name = trigger.first else { continue }
status = "Exporting trigger \(name)"
let qualified = "\(db).\(quoteIdentifier(name))"
let definition = try await execute("SHOW CREATE TRIGGER \(qualified)")
// SHOW CREATE TRIGGER: Trigger, sql_mode, SQL Original Statement, ...
guard let row = definition.rows.first, row.count > 2,
!row[2].isEmpty else {
throw WorkbenchTransferError.message("Missing CREATE TRIGGER for \(name)")
}
try write("\n-- Trigger \(qualified)\n")
try write("DELIMITER $$\n")
if includeDrop { try write("DROP TRIGGER IF EXISTS \(qualified)$$\n") }
try write(row[2] + "$$\nDELIMITER ;\n")
}
}
try Task.checkCancellation() try Task.checkCancellation()
try handle?.close() try handle?.close()
handle = nil handle = nil
@@ -2018,8 +2107,16 @@ struct WorkbenchDatabaseTransferView: View {
} else { } else {
try FileManager.default.moveItem(at: tempURL, to: url) try FileManager.default.moveItem(at: tempURL, to: url)
} }
progress = 1 // The SQL file is complete before creating its checksum sidecar.
status = "Export complete: \(tables.count) tables, \(views.count) views" // Sidecar failure is reported; it does not delete a completed export.
do {
try WorkbenchBackupIntegrity.createSidecar(for: url)
progress = 1
status = "Export complete and SHA-256 verified: \(tables.count) tables, \(views.count) views"
} catch {
progress = 1
status = "SQL export complete, but checksum creation failed: \(error.localizedDescription)"
}
} catch is CancellationError { } catch is CancellationError {
status = "Export cancelled; no final backup was written" status = "Export cancelled; no final backup was written"
} catch { } catch {
@@ -2044,6 +2141,15 @@ struct WorkbenchDatabaseTransferView: View {
} }
do { do {
// Refuse a corrupted backup when a checksum sidecar is present.
// Third-party SQL files without a sidecar remain importable.
if WorkbenchBackupIntegrity.hasSidecar(for: url) {
status = "Verifying SQL backup checksum…"
try await Task.detached(priority: .utility) {
try WorkbenchBackupIntegrity.verify(url)
}.value
}
try Task.checkCancellation()
let sql = try String( let sql = try String(
contentsOf: url, contentsOf: url,
encoding: .utf8 encoding: .utf8
@@ -2223,3 +2329,54 @@ enum WorkbenchSQLImportParser {
return statements return statements
} }
} }
// MARK: - Backup integrity (SHA-256)
// A checksum detects accidental changes to a completed SQL dump. It does NOT
// establish transaction consistency, completeness or authenticity.
private enum WorkbenchBackupIntegrity {
static func sidecar(for sqlURL: URL) -> URL {
URL(fileURLWithPath: sqlURL.path + ".sha256")
}
static func hasSidecar(for sqlURL: URL) -> Bool {
FileManager.default.fileExists(atPath: sidecar(for: sqlURL).path)
}
static func digest(_ url: URL) throws -> String {
let file = try FileHandle(forReadingFrom: url)
defer { try? file.close() }
var hasher = SHA256()
while true {
let chunk = try file.read(upToCount: 1024 * 1024) ?? Data()
if chunk.isEmpty { break }
hasher.update(data: chunk)
}
return hasher.finalize().map { String(format: "%02x", $0) }.joined()
}
static func createSidecar(for sqlURL: URL) throws {
let value = try digest(sqlURL)
// Standard sha256sum-compatible format, with filename rather than path.
let line = value + " " + sqlURL.lastPathComponent + "\n"
try line.write(to: sidecar(for: sqlURL), atomically: true, encoding: .utf8)
}
static func verify(_ sqlURL: URL) throws {
let checksumURL = sidecar(for: sqlURL)
guard FileManager.default.fileExists(atPath: checksumURL.path) else {
throw WorkbenchTransferError.message("No checksum file found: \(checksumURL.lastPathComponent)")
}
let contents = try String(contentsOf: checksumURL, encoding: .utf8)
.trimmingCharacters(in: .whitespacesAndNewlines)
let expected = String(contents.prefix(64)).lowercased()
guard expected.count == 64,
expected.utf8.allSatisfy({ (48...57).contains($0) || (97...102).contains($0) }) else {
throw WorkbenchTransferError.message("Invalid SHA-256 checksum file")
}
let actual = try digest(sqlURL)
guard actual == expected else {
throw WorkbenchTransferError.message("SHA-256 mismatch: backup contents have changed")
}
}
}
@@ -7,7 +7,7 @@
"key": "MySQL Workbench", "key": "MySQL Workbench",
"location": { "location": {
"startingColumn": 18, "startingColumn": 18,
"startingLine": 98 "startingLine": 99
} }
}, },
{ {
@@ -15,7 +15,7 @@
"key": "Navigator", "key": "Navigator",
"location": { "location": {
"startingColumn": 20, "startingColumn": 20,
"startingLine": 187 "startingLine": 188
} }
}, },
{ {
@@ -23,7 +23,7 @@
"key": "Administration", "key": "Administration",
"location": { "location": {
"startingColumn": 22, "startingColumn": 22,
"startingLine": 188 "startingLine": 189
} }
}, },
{ {
@@ -31,7 +31,7 @@
"key": "Schemas", "key": "Schemas",
"location": { "location": {
"startingColumn": 22, "startingColumn": 22,
"startingLine": 189 "startingLine": 190
} }
}, },
{ {
@@ -39,7 +39,7 @@
"key": "SCHEMAS", "key": "SCHEMAS",
"location": { "location": {
"startingColumn": 22, "startingColumn": 22,
"startingLine": 209 "startingLine": 210
} }
}, },
{ {
@@ -47,7 +47,7 @@
"key": "Edit Connection", "key": "Edit Connection",
"location": { "location": {
"startingColumn": 36, "startingColumn": 36,
"startingLine": 263 "startingLine": 264
} }
}, },
{ {
@@ -55,7 +55,7 @@
"key": "Set as Default Schema", "key": "Set as Default Schema",
"location": { "location": {
"startingColumn": 24, "startingColumn": 24,
"startingLine": 380 "startingLine": 381
} }
}, },
{ {
@@ -63,7 +63,7 @@
"key": "Refresh Tables", "key": "Refresh Tables",
"location": { "location": {
"startingColumn": 24, "startingColumn": 24,
"startingLine": 384 "startingLine": 385
} }
}, },
{ {
@@ -71,7 +71,7 @@
"key": "Load Tables", "key": "Load Tables",
"location": { "location": {
"startingColumn": 28, "startingColumn": 28,
"startingLine": 454 "startingLine": 455
} }
}, },
{ {
@@ -79,7 +79,7 @@
"key": "Tables", "key": "Tables",
"location": { "location": {
"startingColumn": 22, "startingColumn": 22,
"startingLine": 427 "startingLine": 428
} }
}, },
{ {
@@ -87,7 +87,7 @@
"key": "Select First 100 Rows", "key": "Select First 100 Rows",
"location": { "location": {
"startingColumn": 20, "startingColumn": 20,
"startingLine": 510 "startingLine": 511
} }
}, },
{ {
@@ -95,7 +95,7 @@
"key": "New Query Tab", "key": "New Query Tab",
"location": { "location": {
"startingColumn": 19, "startingColumn": 19,
"startingLine": 631 "startingLine": 632
} }
}, },
{ {
@@ -103,7 +103,7 @@
"key": "Default Schema:", "key": "Default Schema:",
"location": { "location": {
"startingColumn": 18, "startingColumn": 18,
"startingLine": 656 "startingLine": 657
} }
}, },
{ {
@@ -111,7 +111,7 @@
"key": "SQL Editor", "key": "SQL Editor",
"location": { "location": {
"startingColumn": 18, "startingColumn": 18,
"startingLine": 666 "startingLine": 667
} }
}, },
{ {
@@ -119,7 +119,7 @@
"key": "Execute SQL", "key": "Execute SQL",
"location": { "location": {
"startingColumn": 19, "startingColumn": 19,
"startingLine": 644 "startingLine": 645
} }
}, },
{ {
@@ -127,7 +127,7 @@
"key": "Format SQL", "key": "Format SQL",
"location": { "location": {
"startingColumn": 19, "startingColumn": 19,
"startingLine": 651 "startingLine": 652
} }
}, },
{ {
@@ -135,7 +135,7 @@
"key": "%lld", "key": "%lld",
"location": { "location": {
"startingColumn": 26, "startingColumn": 26,
"startingLine": 706 "startingLine": 707
} }
}, },
{ {
@@ -143,7 +143,7 @@
"key": "Result Grid", "key": "Result Grid",
"location": { "location": {
"startingColumn": 22, "startingColumn": 22,
"startingLine": 738 "startingLine": 739
} }
}, },
{ {
@@ -151,7 +151,7 @@
"key": "No query results", "key": "No query results",
"location": { "location": {
"startingColumn": 26, "startingColumn": 26,
"startingLine": 756 "startingLine": 757
} }
}, },
{ {
@@ -159,7 +159,7 @@
"key": "Action Output", "key": "Action Output",
"location": { "location": {
"startingColumn": 22, "startingColumn": 22,
"startingLine": 778 "startingLine": 779
} }
}, },
{ {
@@ -167,7 +167,7 @@
"key": "UTF-8", "key": "UTF-8",
"location": { "location": {
"startingColumn": 18, "startingColumn": 18,
"startingLine": 853 "startingLine": 854
} }
}, },
{ {
@@ -175,7 +175,7 @@
"key": "Connection Settings", "key": "Connection Settings",
"location": { "location": {
"startingColumn": 18, "startingColumn": 18,
"startingLine": 1028 "startingLine": 1029
} }
}, },
{ {
@@ -183,7 +183,7 @@
"key": "Connection Name", "key": "Connection Name",
"location": { "location": {
"startingColumn": 21, "startingColumn": 21,
"startingLine": 1033 "startingLine": 1034
} }
}, },
{ {
@@ -191,7 +191,7 @@
"key": "Hostname", "key": "Hostname",
"location": { "location": {
"startingColumn": 21, "startingColumn": 21,
"startingLine": 1038 "startingLine": 1039
} }
}, },
{ {
@@ -199,7 +199,7 @@
"key": "Port", "key": "Port",
"location": { "location": {
"startingColumn": 21, "startingColumn": 21,
"startingLine": 1043 "startingLine": 1044
} }
}, },
{ {
@@ -207,7 +207,7 @@
"key": "Username", "key": "Username",
"location": { "location": {
"startingColumn": 21, "startingColumn": 21,
"startingLine": 1049 "startingLine": 1050
} }
}, },
{ {
@@ -215,7 +215,7 @@
"key": "Password (macOS Keychain)", "key": "Password (macOS Keychain)",
"location": { "location": {
"startingColumn": 21, "startingColumn": 21,
"startingLine": 1054 "startingLine": 1055
} }
}, },
{ {
@@ -223,7 +223,7 @@
"key": "Default Schema", "key": "Default Schema",
"location": { "location": {
"startingColumn": 21, "startingColumn": 21,
"startingLine": 1059 "startingLine": 1060
} }
}, },
{ {
@@ -231,7 +231,7 @@
"key": "Cancel", "key": "Cancel",
"location": { "location": {
"startingColumn": 24, "startingColumn": 24,
"startingLine": 1067 "startingLine": 1068
} }
}, },
{ {
@@ -239,7 +239,7 @@
"key": "Save", "key": "Save",
"location": { "location": {
"startingColumn": 24, "startingColumn": 24,
"startingLine": 1071 "startingLine": 1072
} }
}, },
{ {
@@ -247,7 +247,7 @@
"key": "The server did not return any columns.", "key": "The server did not return any columns.",
"location": { "location": {
"startingColumn": 25, "startingColumn": 25,
"startingLine": 1393 "startingLine": 1394
} }
}, },
{ {
@@ -255,7 +255,7 @@
"key": "Loading %@…", "key": "Loading %@…",
"location": { "location": {
"startingColumn": 30, "startingColumn": 30,
"startingLine": 1371 "startingLine": 1372
} }
}, },
{ {
@@ -263,7 +263,7 @@
"key": "No Data", "key": "No Data",
"location": { "location": {
"startingColumn": 21, "startingColumn": 21,
"startingLine": 1390 "startingLine": 1391
} }
}, },
{ {
@@ -271,7 +271,7 @@
"key": "Unable to Load %@", "key": "Unable to Load %@",
"location": { "location": {
"startingColumn": 25, "startingColumn": 25,
"startingLine": 1377 "startingLine": 1378
} }
}, },
{ {
@@ -279,7 +279,7 @@
"key": "Retry", "key": "Retry",
"location": { "location": {
"startingColumn": 28, "startingColumn": 28,
"startingLine": 1383 "startingLine": 1384
} }
}, },
{ {
@@ -287,7 +287,7 @@
"key": "Refresh", "key": "Refresh",
"location": { "location": {
"startingColumn": 23, "startingColumn": 23,
"startingLine": 1432 "startingLine": 1433
} }
}, },
{ {
@@ -295,7 +295,7 @@
"key": "SQL Editor", "key": "SQL Editor",
"location": { "location": {
"startingColumn": 23, "startingColumn": 23,
"startingLine": 1439 "startingLine": 1440
} }
}, },
{ {
@@ -303,7 +303,7 @@
"key": "Not loaded", "key": "Not loaded",
"location": { "location": {
"startingColumn": 22, "startingColumn": 22,
"startingLine": 1517 "startingLine": 1518
} }
}, },
{ {
@@ -311,7 +311,7 @@
"key": "Updated %@", "key": "Updated %@",
"location": { "location": {
"startingColumn": 21, "startingColumn": 21,
"startingLine": 1514 "startingLine": 1515
} }
}, },
{ {
@@ -319,7 +319,7 @@
"key": "%lld rows", "key": "%lld rows",
"location": { "location": {
"startingColumn": 18, "startingColumn": 18,
"startingLine": 1522 "startingLine": 1523
} }
}, },
{ {
@@ -327,7 +327,7 @@
"key": "Import SQL into %@?", "key": "Import SQL into %@?",
"location": { "location": {
"startingColumn": 13, "startingColumn": 13,
"startingLine": 1766 "startingLine": 1780
} }
}, },
{ {
@@ -335,7 +335,7 @@
"key": "Operation", "key": "Operation",
"location": { "location": {
"startingColumn": 20, "startingColumn": 20,
"startingLine": 1597 "startingLine": 1600
} }
}, },
{ {
@@ -343,7 +343,7 @@
"key": "Data Export", "key": "Data Export",
"location": { "location": {
"startingColumn": 22, "startingColumn": 22,
"startingLine": 1598 "startingLine": 1601
} }
}, },
{ {
@@ -351,7 +351,7 @@
"key": "Data Import / Restore", "key": "Data Import / Restore",
"location": { "location": {
"startingColumn": 22, "startingColumn": 22,
"startingLine": 1599 "startingLine": 1602
} }
}, },
{ {
@@ -359,7 +359,7 @@
"key": "MySQL Connection", "key": "MySQL Connection",
"location": { "location": {
"startingColumn": 26, "startingColumn": 26,
"startingLine": 1606 "startingLine": 1609
} }
}, },
{ {
@@ -367,7 +367,7 @@
"key": "Export Options", "key": "Export Options",
"location": { "location": {
"startingColumn": 30, "startingColumn": 30,
"startingLine": 1630 "startingLine": 1633
} }
}, },
{ {
@@ -375,7 +375,7 @@
"key": "Import Options", "key": "Import Options",
"location": { "location": {
"startingColumn": 30, "startingColumn": 30,
"startingLine": 1682 "startingLine": 1689
} }
}, },
{ {
@@ -383,7 +383,7 @@
"key": "Progress", "key": "Progress",
"location": { "location": {
"startingColumn": 26, "startingColumn": 26,
"startingLine": 1694 "startingLine": 1701
} }
}, },
{ {
@@ -391,7 +391,7 @@
"key": "Connection", "key": "Connection",
"location": { "location": {
"startingColumn": 36, "startingColumn": 36,
"startingLine": 1608 "startingLine": 1611
} }
}, },
{ {
@@ -399,7 +399,7 @@
"key": "Database", "key": "Database",
"location": { "location": {
"startingColumn": 28, "startingColumn": 28,
"startingLine": 1612 "startingLine": 1615
} }
}, },
{ {
@@ -407,7 +407,7 @@
"key": "Select Database", "key": "Select Database",
"location": { "location": {
"startingColumn": 30, "startingColumn": 30,
"startingLine": 1613 "startingLine": 1616
} }
}, },
{ {
@@ -415,7 +415,7 @@
"key": "Refresh Databases", "key": "Refresh Databases",
"location": { "location": {
"startingColumn": 32, "startingColumn": 32,
"startingLine": 1621 "startingLine": 1624
} }
}, },
{ {
@@ -423,127 +423,143 @@
"key": "No tables found. Select a database or refresh the list.", "key": "No tables found. Select a database or refresh the list.",
"location": { "location": {
"startingColumn": 34, "startingColumn": 34,
"startingLine": 1648 "startingLine": 1655
} }
}, },
{ {
"comment": "", "comment": "",
"key": "Exports base tables and selected views. Triggers, routines, consistent snapshots and guaranteed row ordering are not yet supported.", "key": "Triggers and routines require SHOW CREATE privileges. This exporter does not provide transaction-consistent snapshots or guaranteed row ordering.",
"location": {
"startingColumn": 30,
"startingLine": 1677
}
},
{
"comment": "",
"key": "Table structure",
"location": {
"startingColumn": 32,
"startingLine": 1632
}
},
{
"comment": "",
"key": "Table data",
"location": {
"startingColumn": 32,
"startingLine": 1633
}
},
{
"comment": "",
"key": "Add DROP TABLE IF EXISTS",
"location": {
"startingColumn": 32,
"startingLine": 1634
}
},
{
"comment": "",
"key": "Include views (definitions)",
"location": {
"startingColumn": 32,
"startingLine": 1636
}
},
{
"comment": "",
"key": "Loading tables…",
"location": {
"startingColumn": 57,
"startingLine": 1646
}
},
{
"comment": "",
"key": "Tables (%lld selected)",
"location": {
"startingColumn": 34,
"startingLine": 1639
}
},
{
"comment": "",
"key": "All",
"location": {
"startingColumn": 36,
"startingLine": 1641
}
},
{
"comment": "",
"key": "None",
"location": {
"startingColumn": 36,
"startingLine": 1642
}
},
{
"comment": "",
"key": "Refresh",
"location": {
"startingColumn": 36,
"startingLine": 1643
}
},
{
"comment": "",
"key": "Views (%lld selected)",
"location": {
"startingColumn": 38,
"startingLine": 1662
}
},
{
"comment": "",
"key": "All",
"location": {
"startingColumn": 40,
"startingLine": 1664
}
},
{
"comment": "",
"key": "None",
"location": {
"startingColumn": 40,
"startingLine": 1665
}
},
{
"comment": "",
"key": "Import SQL statements into the selected database.",
"location": { "location": {
"startingColumn": 30, "startingColumn": 30,
"startingLine": 1684 "startingLine": 1684
} }
}, },
{
"comment": "",
"key": "Table structure",
"location": {
"startingColumn": 32,
"startingLine": 1635
}
},
{
"comment": "",
"key": "Table data",
"location": {
"startingColumn": 32,
"startingLine": 1636
}
},
{
"comment": "",
"key": "Add DROP TABLE IF EXISTS",
"location": {
"startingColumn": 32,
"startingLine": 1637
}
},
{
"comment": "",
"key": "Include views (definitions)",
"location": {
"startingColumn": 32,
"startingLine": 1639
}
},
{
"comment": "",
"key": "Include triggers",
"location": {
"startingColumn": 32,
"startingLine": 1641
}
},
{
"comment": "",
"key": "Include stored procedures and functions",
"location": {
"startingColumn": 32,
"startingLine": 1643
}
},
{
"comment": "",
"key": "Loading tables…",
"location": {
"startingColumn": 57,
"startingLine": 1653
}
},
{
"comment": "",
"key": "Tables (%lld selected)",
"location": {
"startingColumn": 34,
"startingLine": 1646
}
},
{
"comment": "",
"key": "All",
"location": {
"startingColumn": 36,
"startingLine": 1648
}
},
{
"comment": "",
"key": "None",
"location": {
"startingColumn": 36,
"startingLine": 1649
}
},
{
"comment": "",
"key": "Refresh",
"location": {
"startingColumn": 36,
"startingLine": 1650
}
},
{
"comment": "",
"key": "Views (%lld selected)",
"location": {
"startingColumn": 38,
"startingLine": 1669
}
},
{
"comment": "",
"key": "All",
"location": {
"startingColumn": 40,
"startingLine": 1671
}
},
{
"comment": "",
"key": "None",
"location": {
"startingColumn": 40,
"startingLine": 1672
}
},
{
"comment": "",
"key": "Import SQL statements into the selected database.",
"location": {
"startingColumn": 30,
"startingLine": 1691
}
},
{ {
"comment": "", "comment": "",
"key": "Supports MySQL DELIMITER directives, triggers and stored routines. Import is not transactional; use a verified backup before restoring.", "key": "Supports MySQL DELIMITER directives, triggers and stored routines. Import is not transactional; use a verified backup before restoring.",
"location": { "location": {
"startingColumn": 30, "startingColumn": 30,
"startingLine": 1687 "startingLine": 1694
} }
}, },
{ {
@@ -551,7 +567,23 @@
"key": "Close", "key": "Close",
"location": { "location": {
"startingColumn": 24, "startingColumn": 24,
"startingLine": 1713 "startingLine": 1720
}
},
{
"comment": "",
"key": "Verify Backup…",
"location": {
"startingColumn": 28,
"startingLine": 1728
}
},
{
"comment": "",
"key": "Verify an SQL file against its SHA-256 checksum sidecar",
"location": {
"startingColumn": 27,
"startingLine": 1731
} }
}, },
{ {
@@ -559,7 +591,7 @@
"key": "Cancel", "key": "Cancel",
"location": { "location": {
"startingColumn": 28, "startingColumn": 28,
"startingLine": 1721 "startingLine": 1735
} }
}, },
{ {
@@ -567,7 +599,7 @@
"key": "Export SQL…", "key": "Export SQL…",
"location": { "location": {
"startingColumn": 31, "startingColumn": 31,
"startingLine": 1728 "startingLine": 1742
} }
}, },
{ {
@@ -575,7 +607,7 @@
"key": "Import SQL…", "key": "Import SQL…",
"location": { "location": {
"startingColumn": 31, "startingColumn": 31,
"startingLine": 1729 "startingLine": 1743
} }
}, },
{ {
@@ -583,7 +615,7 @@
"key": "Import SQL", "key": "Import SQL",
"location": { "location": {
"startingColumn": 20, "startingColumn": 20,
"startingLine": 1769 "startingLine": 1783
} }
}, },
{ {
@@ -591,7 +623,7 @@
"key": "Cancel", "key": "Cancel",
"location": { "location": {
"startingColumn": 20, "startingColumn": 20,
"startingLine": 1777 "startingLine": 1791
} }
}, },
{ {
@@ -599,7 +631,7 @@
"key": "SQL files can modify or delete existing database objects and data. Ensure you have a backup before continuing.", "key": "SQL files can modify or delete existing database objects and data. Ensure you have a backup before continuing.",
"location": { "location": {
"startingColumn": 17, "startingColumn": 17,
"startingLine": 1782 "startingLine": 1796
} }
}, },
{ {
@@ -607,7 +639,7 @@
"key": "Database Import / Export", "key": "Database Import / Export",
"location": { "location": {
"startingColumn": 22, "startingColumn": 22,
"startingLine": 1794 "startingLine": 1808
} }
}, },
{ {
@@ -615,7 +647,7 @@
"key": "Built-in native MySQL database tools", "key": "Built-in native MySQL database tools",
"location": { "location": {
"startingColumn": 22, "startingColumn": 22,
"startingLine": 1797 "startingLine": 1811
} }
} }
] ]
@@ -64,6 +64,11 @@
"moduleName" : "CoreVideo", "moduleName" : "CoreVideo",
"modulePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/prebuilt-modules\/27.0\/CoreVideo.swiftmodule\/arm64e-apple-macos.swiftmodule" "modulePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/prebuilt-modules\/27.0\/CoreVideo.swiftmodule\/arm64e-apple-macos.swiftmodule"
}, },
{
"isFramework" : true,
"moduleName" : "CryptoKit",
"modulePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/prebuilt-modules\/27.0\/CryptoKit.swiftmodule\/arm64e-apple-macos.swiftmodule"
},
{ {
"isFramework" : false, "isFramework" : false,
"moduleName" : "Darwin", "moduleName" : "Darwin",
@@ -99,6 +104,11 @@
"moduleName" : "ImageIO", "moduleName" : "ImageIO",
"modulePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/prebuilt-modules\/27.0\/ImageIO.swiftmodule\/arm64e-apple-macos.swiftmodule" "modulePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/prebuilt-modules\/27.0\/ImageIO.swiftmodule\/arm64e-apple-macos.swiftmodule"
}, },
{
"isFramework" : true,
"moduleName" : "LocalAuthentication",
"modulePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/prebuilt-modules\/27.0\/LocalAuthentication.swiftmodule\/arm64e-apple-macos.swiftmodule"
},
{ {
"isFramework" : false, "isFramework" : false,
"moduleName" : "Metal", "moduleName" : "Metal",
@@ -403,6 +413,13 @@
"isFramework" : false, "isFramework" : false,
"moduleName" : "ImageIO" "moduleName" : "ImageIO"
}, },
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/System\/Library\/Frameworks\/LocalAuthentication.framework\/Modules\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/LocalAuthentication-678PKH1D7S8W88AP15VFRNL98.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "LocalAuthentication"
},
{ {
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/usr\/include\/DarwinBasic.modulemap", "clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/usr\/include\/DarwinBasic.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/MachO-9GGXQQ5TIOCIBWIKR2CCZVSB1.pcm", "clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/MachO-9GGXQQ5TIOCIBWIKR2CCZVSB1.pcm",
@@ -10,3 +10,5 @@
/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
/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
@@ -19,7 +19,7 @@
<dict> <dict>
<key>cdhash</key> <key>cdhash</key>
<data> <data>
cNkEGkXY0XL0979Pg1Y/5+wqs/g= RB/YriyQlwTsASbrsirLbaoN96E=
</data> </data>
<key>requirement</key> <key>requirement</key>
<string>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 */</string> <string>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 */</string>
+10 -10
View File
@@ -6,7 +6,7 @@
<integer>13</integer> <integer>13</integer>
<key>logs</key> <key>logs</key>
<dict> <dict>
<key>20B4B0A0-41A0-44A0-BA02-3E5707BABE60</key> <key>32E9EC2D-B9B8-4CD2-8B3D-CD693A4CBD97</key>
<dict> <dict>
<key>className</key> <key>className</key>
<string>IDECommandLineBuildLog</string> <string>IDECommandLineBuildLog</string>
@@ -15,7 +15,7 @@
<key>domainType</key> <key>domainType</key>
<string>Xcode.IDEActivityLogDomainType.BuildLog</string> <string>Xcode.IDEActivityLogDomainType.BuildLog</string>
<key>fileName</key> <key>fileName</key>
<string>20B4B0A0-41A0-44A0-BA02-3E5707BABE60.xcactivitylog</string> <string>32E9EC2D-B9B8-4CD2-8B3D-CD693A4CBD97.xcactivitylog</string>
<key>hasPrimaryLog</key> <key>hasPrimaryLog</key>
<true/> <true/>
<key>primaryObservable</key> <key>primaryObservable</key>
@@ -40,13 +40,13 @@
<key>signature</key> <key>signature</key>
<string>Building project WorkbenchReplacement with scheme WorkbenchReplacement and configuration Debug</string> <string>Building project WorkbenchReplacement with scheme WorkbenchReplacement and configuration Debug</string>
<key>timeStartedRecording</key> <key>timeStartedRecording</key>
<real>813876442.84578598</real> <real>813876968.59599304</real>
<key>timeStoppedRecording</key> <key>timeStoppedRecording</key>
<real>813876454.95874095</real> <real>813876981.606776</real>
<key>title</key> <key>title</key>
<string>Building project WorkbenchReplacement with scheme WorkbenchReplacement and configuration Debug</string> <string>Building project WorkbenchReplacement with scheme WorkbenchReplacement and configuration Debug</string>
<key>uniqueIdentifier</key> <key>uniqueIdentifier</key>
<string>20B4B0A0-41A0-44A0-BA02-3E5707BABE60</string> <string>32E9EC2D-B9B8-4CD2-8B3D-CD693A4CBD97</string>
</dict> </dict>
<key>3389C95F-527F-43D4-BFD7-803CFE84AD6A</key> <key>3389C95F-527F-43D4-BFD7-803CFE84AD6A</key>
<dict> <dict>
@@ -132,7 +132,7 @@
<key>uniqueIdentifier</key> <key>uniqueIdentifier</key>
<string>705B952B-2A37-40D9-B731-8E2290C15462</string> <string>705B952B-2A37-40D9-B731-8E2290C15462</string>
</dict> </dict>
<key>AF582207-87B3-4A2E-A532-DA0FD8F61FBD</key> <key>921D995A-6441-4831-9E4E-3283B2F38B89</key>
<dict> <dict>
<key>className</key> <key>className</key>
<string>IDECommandLineBuildLog</string> <string>IDECommandLineBuildLog</string>
@@ -141,7 +141,7 @@
<key>domainType</key> <key>domainType</key>
<string>Xcode.IDEActivityLogDomainType.BuildLog</string> <string>Xcode.IDEActivityLogDomainType.BuildLog</string>
<key>fileName</key> <key>fileName</key>
<string>AF582207-87B3-4A2E-A532-DA0FD8F61FBD.xcactivitylog</string> <string>921D995A-6441-4831-9E4E-3283B2F38B89.xcactivitylog</string>
<key>hasPrimaryLog</key> <key>hasPrimaryLog</key>
<true/> <true/>
<key>primaryObservable</key> <key>primaryObservable</key>
@@ -166,13 +166,13 @@
<key>signature</key> <key>signature</key>
<string>Building project WorkbenchReplacement with scheme WorkbenchReplacement and configuration Debug</string> <string>Building project WorkbenchReplacement with scheme WorkbenchReplacement and configuration Debug</string>
<key>timeStartedRecording</key> <key>timeStartedRecording</key>
<real>813876270.55029094</real> <real>813877067.82482302</real>
<key>timeStoppedRecording</key> <key>timeStoppedRecording</key>
<real>813876283.37291896</real> <real>813877081.68267095</real>
<key>title</key> <key>title</key>
<string>Building project WorkbenchReplacement with scheme WorkbenchReplacement and configuration Debug</string> <string>Building project WorkbenchReplacement with scheme WorkbenchReplacement and configuration Debug</string>
<key>uniqueIdentifier</key> <key>uniqueIdentifier</key>
<string>AF582207-87B3-4A2E-A532-DA0FD8F61FBD</string> <string>921D995A-6441-4831-9E4E-3283B2F38B89</string>
</dict> </dict>
</dict> </dict>
</dict> </dict>
@@ -1 +1 @@
1792184043.218933: Module build session file for module cache at Path(str: "/Volumes/Developer/Applications/WorkbenchReplacement/build/ModuleCache.noindex") 1792184268.211045: Module build session file for module cache at Path(str: "/Volumes/Developer/Applications/WorkbenchReplacement/build/ModuleCache.noindex")
+1 -1
View File
@@ -3,7 +3,7 @@
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>LastAccessedDate</key> <key>LastAccessedDate</key>
<date>2026-10-16T20:54:03Z</date> <date>2026-10-16T20:57:48Z</date>
<key>WorkspacePath</key> <key>WorkspacePath</key>
<string>/Volumes/Developer/Applications/WorkbenchReplacement/WorkbenchReplacement.xcodeproj</string> <string>/Volumes/Developer/Applications/WorkbenchReplacement/WorkbenchReplacement.xcodeproj</string>
</dict> </dict>