Improved progress display

Previously, the progress bar updated only after completing an entire table.

For large tables such as LIBARC, this could make the application appear to have stopped responding.

Phase 10 updates the interface while rows are being exported.
This commit is contained in:
Mark Gregory
2026-10-16 22:37:26 +01:00
parent 788c3373d8
commit a189d21af9
53 changed files with 1136 additions and 488 deletions
+11
View File
@@ -0,0 +1,11 @@
# Phase 14 — MySQL Account Management
Adds a **Manage Accounts…** sheet under Administration → Users and Privileges.
Operations: create account, change password, lock/unlock account, grant/revoke an individual database privilege. Every operation displays a SQL preview and requires an explicit confirmation. Passwords are masked in previews, never persisted by the account management form, and cleared after execution. Privileges are chosen from an allowlist. User, host, and database inputs use conservative character allowlists.
The existing account list and Show Grants remain available. Auto-refresh is switched off when account management opens, and the account list refreshes on dismissal.
**Limitations:** Requires sufficient MySQL privileges. Only basic ASCII account/host/database names supported. No account deletion, global privileges, roles, or complex grant options. SQL mode NO_BACKSLASH_ESCAPES is rejected for password operations. Password values are sent using the native query protocol, which may expose them in server-side SQL logs; parameterized MySQL account DDL isn't implemented. No automatic rollback for account changes.
**Validation:** Static/source inspection and ZIP integrity only. This environment cannot compile the macOS Xcode target or verify changes against a MySQL server. Test on a disposable MySQL instance before production use.
+15
View File
@@ -0,0 +1,15 @@
# Phase 16 — Native MySQL Diagnostics
Adds five read-only Administration → Performance screens:
- **Slow Query Analysis:** `performance_schema.events_statements_summary_by_digest` sorted by cumulative execution time, with counts, rows examined, and first/last seen timestamps. Times are raw picoseconds, **not** milliseconds.
- **Table I/O Hotspots:** `performance_schema.table_io_waits_summary_by_table` with read/write counts and timings.
- **InnoDB Metrics:** selected `SHOW GLOBAL STATUS` buffer-pool, I/O, lock, and row counters.
- **InnoDB Lock Waits:** current edges in `performance_schema.data_lock_waits`.
- **Connection Statistics:** connection, abort, thread, and query counters.
All screens reuse the existing searchable grid, manual refresh, and optional five-second auto-refresh. SQL is read-only. Some screens require MySQL 8.0+ and enabled Performance Schema instrumentation/privileges; errors are displayed in the existing administration error view. The digest and table I/O summaries are cumulative and may be reset by server restarts or instrumentation changes. This phase does **not** enable slow-query logging or change server configuration.
## Validation
Source inspection and ZIP integrity check performed. **Not compiled with Xcode or tested on a live MySQL server.** Test on a nonproduction MySQL 8.0+ instance first.
+11
View File
@@ -0,0 +1,11 @@
# Phase 17 — SQL Editor Productivity
- Added persisted query execution history (latest 200 statements), including timestamp, connection, schema, success/failure, and error details. Schema-list refreshes are not recorded.
- Added History panel with filtering, reopen in a new tab, and clear history.
- Added a persistent saved SQL query library, with save, filter, open, and delete.
- Added AppKit SQL keyword completion via NSTextViewDelegate's standard completion interface (typically Esc or Option-Esc depending on macOS key bindings). Completions are keyword-based, not live schema-aware.
- Preserved the existing SQL syntax highlighting and query tab handling.
## Caveats
History and saved SQL are stored in local UserDefaults and may contain sensitive SQL text; avoid executing statements containing literal secrets if local history retention is a concern. Results remain subject to the existing protocol row cap. This phase has not been built in Xcode or tested against a live server.
+18
View File
@@ -0,0 +1,18 @@
# Phase 18 — SQL Editor and Result Grid
Built from the Phase 17 project archive and includes the correction for the Phase 17 `localizedCaseInsensitiveHasPrefix` compilation error.
## Changes
- Execute highlighted SQL independently of the complete editor buffer (Run Selection).
- SQL completion includes cached table and view names loaded in the Schemas navigator, alongside SQL keywords. Column-aware suggestions are not yet implemented.
- Case-insensitive local filtering of query result rows.
- Local result sorting by chosen column, ascending or descending.
- CSV export of the currently displayed rows, with RFC-style doubled quotes, UTF-8 BOM and CRLF line endings.
- Query elapsed wall-clock time, shown after execution completes (includes connection overhead).
## Limitations
- Result grid filters/sorts operate only on rows returned by the existing query engine, not the full database table.
- The CSV writer constructs the file in memory and should not be used for huge result sets.
- SQL autocomplete suggestions depend on schemas already loaded; no column metadata is fetched.
- No new SQL error underlining was added.
- Not compiled on macOS or tested against a MySQL server; run xcodebuild on the Mac.
+12
View File
@@ -0,0 +1,12 @@
# Phase 19 — Column-aware SQL completion and incremental CSV writing
- Load column names from `information_schema.COLUMNS` when a schema is expanded/refreshed. Cache column names and `table.column` suggestions for the active schema; metadata errors are nonfatal.
- SQL completion now includes cached column suggestions alongside tables, views and keywords.
- Write CSV results one record at a time to a temporary file, with UTF-8 BOM, CRLF and quote escaping; move into place only on success.
- Retains all Phase 18 features.
## Limitations
- Metadata suggestions require loading the schema navigator first and sufficient `information_schema` visibility.
- CSV export still operates on the existing in-memory query result (subject to query result limits); it does not fetch additional database rows.
- Result-grid filtering/sorting still creates an in-memory sorted list.
- Not compiled on macOS or tested against MySQL. Build and test with Xcode before production use.
+108
View File
@@ -63,9 +63,73 @@ struct WorkbenchQueryTab: Identifiable {
}
}
struct SQLHistoryEntry: Identifiable, Codable {
var id = UUID()
var executedAt = Date()
var connectionName: String
var database: String
var sql: String
var succeeded: Bool
var detail: String
}
struct SavedSQLQuery: Identifiable, Codable {
var id = UUID()
var name: String
var sql: String
}
@MainActor
final class DatabaseModel: ObservableObject {
@Published var profiles: [ConnectionProfile] = []
@Published private(set) var queryHistory: [SQLHistoryEntry] = []
@Published private(set) var savedQueries: [SavedSQLQuery] = []
private let historyKey = "WorkbenchReplacement.queryHistory.v1"
private let savedQueriesKey = "WorkbenchReplacement.savedQueries.v1"
private let maxHistoryEntries = 200
func saveCurrentQuery(named name: String) {
let clean = name.trimmingCharacters(in: .whitespacesAndNewlines)
guard !clean.isEmpty, !sql.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { return }
savedQueries.insert(SavedSQLQuery(name: clean, sql: sql), at: 0)
persistSavedQueries()
}
func openSavedQuery(_ entry: SavedSQLQuery) {
newQueryTab()
sql = entry.sql
if let id = activeQueryID, let index = queryTabs.firstIndex(where: { $0.id == id }) {
queryTabs[index].title = entry.name
queryTabs[index].sql = entry.sql
}
}
func deleteSavedQuery(_ id: UUID) {
savedQueries.removeAll { $0.id == id }
persistSavedQueries()
}
func clearQueryHistory() {
queryHistory.removeAll()
UserDefaults.standard.removeObject(forKey: historyKey)
}
private func persistSavedQueries() {
if let data = try? JSONEncoder().encode(savedQueries) {
UserDefaults.standard.set(data, forKey: savedQueriesKey)
}
}
private func recordQuery(_ statement: String, profile: ConnectionProfile, database: String, succeeded: Bool, detail: String) {
queryHistory.insert(SQLHistoryEntry(connectionName: profile.name, database: database, sql: statement, succeeded: succeeded, detail: detail), at: 0)
if queryHistory.count > maxHistoryEntries {
queryHistory.removeLast(queryHistory.count - maxHistoryEntries)
}
if let data = try? JSONEncoder().encode(queryHistory) {
UserDefaults.standard.set(data, forKey: historyKey)
}
}
@Published var selectedID: UUID?
@Published var queryTabs: [WorkbenchQueryTab] = [
WorkbenchQueryTab(
@@ -79,12 +143,22 @@ final class DatabaseModel: ObservableObject {
@Published var databases: [String] = []
@Published var schemaTables: [String: [String]] = [:]
@Published var schemaViews: [String: [String]] = [:]
@Published var schemaColumns: [String: [String]] = [:]
@Published var selectedDatabase = ""
@Published var queryDurationText = ""
@Published var isRunning = false
@Published var status = "Not connected"
private let storageKey = "WorkbenchReplacement.connectionProfiles.native.v1"
init() {
if let data = UserDefaults.standard.data(forKey: historyKey),
let entries = try? JSONDecoder().decode([SQLHistoryEntry].self, from: data) {
queryHistory = Array(entries.prefix(maxHistoryEntries))
}
if let data = UserDefaults.standard.data(forKey: savedQueriesKey),
let entries = try? JSONDecoder().decode([SavedSQLQuery].self, from: data) {
savedQueries = entries
}
if let data = UserDefaults.standard.data(forKey: storageKey),
let decoded = try? JSONDecoder().decode([ConnectionProfile].self, from: data) { profiles = decoded }
if profiles.isEmpty { profiles = [ConnectionProfile()] }
@@ -250,6 +324,27 @@ final class DatabaseModel: ObservableObject {
schemaTables[database] = tables
schemaViews[database] = views
// Cache column names for SQL completion. The lookup is read-only,
// scoped to the selected schema and uses the current connection.
// Failures (e.g. metadata permissions) do not prevent table browsing.
do {
let quotedSchema = database.replacingOccurrences(of: "'", with: "''")
let columnResult = try await NativeMySQLRunner.run(
profile: profile,
database: database,
password: password,
sql: "SELECT TABLE_NAME, COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = '\(quotedSchema)' ORDER BY TABLE_NAME, ORDINAL_POSITION"
)
var names = Set<String>()
for row in columnResult.rows where row.count >= 2 {
names.insert(row[1])
names.insert("\(row[0]).\(row[1])")
}
schemaColumns[database] = names.sorted()
} catch {
schemaColumns[database] = []
}
status = "Loaded \(tables.count) tables and \(views.count) views from \(database)"
} catch {
status = "Unable to load tables: \(error.localizedDescription)"
@@ -296,10 +391,16 @@ final class DatabaseModel: ObservableObject {
}
execute(sql, listingDatabases: false)
}
func runSQL(statement: String) {
guard !statement.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { return }
execute(statement, listingDatabases: false)
}
private func execute(_ statement: String, listingDatabases: Bool) {
guard let profile = selected else { status = "Select a connection"; return }
guard !statement.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { return }
isRunning = true
if !listingDatabases { queryDurationText = "Running…" }
let queryStarted = Date()
status = "Connecting…"
let activeDB = listingDatabases ? "" : (selectedDatabase.isEmpty ? profile.database : selectedDatabase)
let password = PasswordStore.read(profile.id)
@@ -317,10 +418,17 @@ final class DatabaseModel: ObservableObject {
queryTabs[index].result = parsed
}
status = "Query completed (\(parsed.rows.count) rows)"
recordQuery(statement, profile: profile, database: activeDB, succeeded: true, detail: status)
}
} catch {
status = "Error"
result = QueryResult(message: error.localizedDescription)
if !listingDatabases {
recordQuery(statement, profile: profile, database: activeDB, succeeded: false, detail: error.localizedDescription)
}
}
if !listingDatabases {
queryDurationText = String(format: "%.3f s", Date().timeIntervalSince(queryStarted))
}
isRunning = false
}
+259 -5
View File
@@ -2,6 +2,7 @@
import SwiftUI
import AppKit
import CryptoKit
import UniformTypeIdentifiers
// MARK: - Classic Workbench Colours
@@ -25,8 +26,16 @@ private enum WB {
struct MainView: View {
@EnvironmentObject private var model: DatabaseModel
@State private var selectedSQL = ""
@State private var resultFilter = ""
@State private var resultSortColumn = 0
@State private var resultSortAscending = true
@State private var showingEditor = false
@State private var showingDatabaseTransfer = false
@State private var showingQueryHistory = false
@State private var showingSavedQueries = false
@State private var showingSaveQuery = false
@State private var newSavedQueryName = ""
@State private var editing = ConnectionProfile()
@State private var editingPassword = ""
@@ -74,6 +83,19 @@ struct MainView: View {
model.loadDatabases()
}
}
.sheet(isPresented: $showingQueryHistory) {
SQLHistorySheet(model: model)
}
.sheet(isPresented: $showingSavedQueries) {
SavedSQLQueriesSheet(model: model)
}
.alert("Save SQL Query", isPresented: $showingSaveQuery) {
TextField("Query name", text: $newSavedQueryName)
Button("Save") { model.saveCurrentQuery(named: newSavedQueryName) }
Button("Cancel", role: .cancel) { }
} message: {
Text("Save the current SQL in your local query library.")
}
.sheet(isPresented: $showingDatabaseTransfer) {
WorkbenchDatabaseTransferView(model: model)
}
@@ -650,6 +672,14 @@ struct MainView: View {
}
.help("Execute SQL")
Button {
model.runSQL(statement: selectedSQL)
} label: {
Label("Run Selection", systemImage: "selection.pin.in.out")
}
.disabled(selectedSQL.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
.help("Execute selected SQL without modifying the editor")
Button {
model.formatSQL()
} label: {
@@ -660,6 +690,30 @@ struct MainView: View {
Divider()
.frame(height: 17)
Button {
showingQueryHistory = true
} label: {
Label("History", systemImage: "clock.arrow.circlepath")
}
.help("Query execution history")
Button {
showingSavedQueries = true
} label: {
Label("Saved", systemImage: "bookmark")
}
.help("Open saved SQL queries")
Button {
newSavedQueryName = ""
showingSaveQuery = true
} label: {
Image(systemName: "bookmark.fill")
}
.help("Save current SQL query")
Divider().frame(height: 17)
Text("Default Schema:")
.font(.system(size: 11))
@@ -696,7 +750,7 @@ struct MainView: View {
.fill(WB.border)
.frame(width: 1)
SQLTextView(text: $model.sql)
SQLTextView(text: $model.sql, selectedSQL: $selectedSQL, completions: Array(Set(model.schemaTables.values.flatMap { $0 } + model.schemaViews.values.flatMap { $0 } + (model.schemaColumns[model.selectedDatabase] ?? []))).sorted())
}
.background(Color(nsColor: .textBackgroundColor))
}
@@ -747,6 +801,23 @@ struct MainView: View {
Spacer()
if !model.result.columns.isEmpty {
TextField("Filter rows", text: $resultFilter)
.textFieldStyle(.roundedBorder)
.frame(width: 170)
Picker("Sort", selection: $resultSortColumn) {
ForEach(model.result.columns.indices, id: \.self) { index in
Text(model.result.columns[index]).tag(index)
}
}
.frame(width: 155)
Button(resultSortAscending ? "Ascending" : "Descending") {
resultSortAscending.toggle()
}
Button("Export CSV") { exportResultCSV() }
}
Text(model.queryDurationText)
.foregroundStyle(.secondary)
Text(model.result.message)
.foregroundStyle(.secondary)
}
@@ -772,10 +843,63 @@ struct MainView: View {
private var resultGrid: some View {
NativeResultGrid(
columns: model.result.columns,
rows: model.result.rows
)
NativeResultGrid(columns: model.result.columns, rows: displayedRows)
}
private var displayedRows: [[String]] {
let filtered = resultFilter.isEmpty ? model.result.rows : model.result.rows.filter { row in
row.contains { $0.localizedCaseInsensitiveContains(resultFilter) }
}
guard !model.result.columns.isEmpty else { return filtered }
let index = min(resultSortColumn, model.result.columns.count - 1)
return filtered.sorted { left, right in
let a = index < left.count ? left[index] : ""
let b = index < right.count ? right[index] : ""
let comparison = a.localizedStandardCompare(b)
return resultSortAscending ? comparison == .orderedAscending : comparison == .orderedDescending
}
}
private func exportResultCSV() {
let panel = NSSavePanel()
panel.nameFieldStringValue = "query-results.csv"
panel.allowedContentTypes = [.commaSeparatedText]
guard panel.runModal() == .OK, let url = panel.url else { return }
// Write one CSV record at a time instead of constructing the entire
// document as a single String. Only already-loaded result rows are saved.
func csvField(_ value: String) -> String {
"\"" + value.replacingOccurrences(of: "\"", with: "\"\"") + "\""
}
func csvRecord(_ values: [String]) -> Data {
Data((values.map(csvField).joined(separator: ",") + "\r\n").utf8)
}
let rows = displayedRows
let tempURL = url.deletingLastPathComponent()
.appendingPathComponent(".\(UUID().uuidString).csv.tmp")
do {
guard FileManager.default.createFile(atPath: tempURL.path, contents: nil) else {
throw CocoaError(.fileWriteUnknown)
}
let handle = try FileHandle(forWritingTo: tempURL)
defer { try? handle.close() }
try handle.write(contentsOf: Data([0xEF, 0xBB, 0xBF]))
try handle.write(contentsOf: csvRecord(model.result.columns))
for row in rows {
try handle.write(contentsOf: csvRecord(row))
}
try handle.synchronize()
if FileManager.default.fileExists(atPath: url.path) {
_ = try FileManager.default.replaceItemAt(url, withItemAt: tempURL)
} else {
try FileManager.default.moveItem(at: tempURL, to: url)
}
model.status = "Exported \(rows.count) rows to \(url.lastPathComponent)"
} catch {
try? FileManager.default.removeItem(at: tempURL)
model.status = "CSV export failed: \(error.localizedDescription)"
}
}
private var actionOutput: some View {
@@ -885,10 +1009,107 @@ struct MainView: View {
}
}
// MARK: - Phase 17: Query History and Saved Queries
private struct SQLHistorySheet: View {
@ObservedObject var model: DatabaseModel
@Environment(\.dismiss) private var dismiss
@State private var filter = ""
private var entries: [SQLHistoryEntry] {
model.queryHistory.filter {
filter.isEmpty || $0.sql.localizedCaseInsensitiveContains(filter) ||
$0.connectionName.localizedCaseInsensitiveContains(filter)
}
}
var body: some View {
VStack(alignment: .leading, spacing: 12) {
HStack {
Text("Query History").font(.title2).bold()
Spacer()
Button("Clear History") { model.clearQueryHistory() }
.disabled(model.queryHistory.isEmpty)
Button("Done") { dismiss() }
}
TextField("Filter SQL or connection", text: $filter)
.textFieldStyle(.roundedBorder)
List(entries) { entry in
VStack(alignment: .leading, spacing: 5) {
HStack {
Image(systemName: entry.succeeded ? "checkmark.circle.fill" : "xmark.circle.fill")
.foregroundStyle(entry.succeeded ? .green : .red)
Text(entry.executedAt, style: .date)
Text(entry.executedAt, style: .time)
Text(entry.connectionName).foregroundStyle(.secondary)
Text(entry.database).foregroundStyle(.secondary)
Spacer()
Button("Open in New Tab") {
model.newQueryTab()
model.sql = entry.sql
model.saveActiveQuery()
dismiss()
}
}
Text(entry.sql).font(.system(.body, design: .monospaced))
.lineLimit(3).textSelection(.enabled)
Text(entry.detail).font(.caption).foregroundStyle(.secondary)
}.padding(.vertical, 4)
}
}
.padding(20)
.frame(minWidth: 740, minHeight: 460)
}
}
private struct SavedSQLQueriesSheet: View {
@ObservedObject var model: DatabaseModel
@Environment(\.dismiss) private var dismiss
@State private var filter = ""
var body: some View {
VStack(alignment: .leading, spacing: 12) {
HStack {
Text("Saved SQL Queries").font(.title2).bold()
Spacer()
Button("Done") { dismiss() }
}
TextField("Filter saved queries", text: $filter)
.textFieldStyle(.roundedBorder)
List(model.savedQueries.filter {
filter.isEmpty || $0.name.localizedCaseInsensitiveContains(filter) ||
$0.sql.localizedCaseInsensitiveContains(filter)
}) { entry in
HStack {
VStack(alignment: .leading, spacing: 4) {
Text(entry.name).font(.headline)
Text(entry.sql).font(.system(.caption, design: .monospaced))
.lineLimit(2).foregroundStyle(.secondary)
}
Spacer()
Button("Open") {
model.openSavedQuery(entry)
dismiss()
}
Button(role: .destructive) {
model.deleteSavedQuery(entry.id)
} label: {
Image(systemName: "trash")
}
}.padding(.vertical, 5)
}
}
.padding(20)
.frame(minWidth: 650, minHeight: 420)
}
}
// MARK: - Native SQL Text Editor
struct SQLTextView: NSViewRepresentable {
@Binding var text: String
@Binding var selectedSQL: String
var completions: [String] = []
func makeCoordinator() -> Coordinator {
Coordinator(self)
@@ -961,6 +1182,39 @@ struct SQLTextView: NSViewRepresentable {
self.parent = parent
}
private let completionWords = [
"SELECT", "FROM", "WHERE", "INSERT INTO", "UPDATE", "DELETE FROM",
"CREATE TABLE", "ALTER TABLE", "DROP TABLE", "JOIN", "LEFT JOIN",
"INNER JOIN", "GROUP BY", "ORDER BY", "HAVING", "LIMIT",
"DISTINCT", "COUNT", "SUM", "AVG", "MIN", "MAX", "AS",
"AND", "OR", "NOT", "NULL", "IS NULL", "LIKE", "IN",
"BETWEEN", "EXISTS", "UNION", "WITH", "CASE", "WHEN", "THEN",
"ELSE", "END", "SHOW TABLES", "SHOW DATABASES", "DESCRIBE",
"EXPLAIN", "PRIMARY KEY", "FOREIGN KEY", "INDEX", "VALUES"
]
func textView(_ textView: NSTextView, completions words: [String],
forPartialWordRange charRange: NSRange,
indexOfSelectedItem index: UnsafeMutablePointer<Int>?) -> [String] {
let text = textView.string as NSString
guard charRange.location != NSNotFound,
NSMaxRange(charRange) <= text.length else { return [] }
let prefix = text.substring(with: charRange)
guard !prefix.isEmpty else { return [] }
return Array(Set(completionWords + parent.completions))
.filter { $0.range(of: prefix, options: [.anchored, .caseInsensitive]) != nil }
.sorted()
}
func textViewDidChangeSelection(_ notification: Notification) {
guard let editor = notification.object as? NSTextView else { return }
let range = editor.selectedRange()
let value = editor.string as NSString
let selection = range.length > 0 && NSMaxRange(range) <= value.length
? value.substring(with: range) : ""
if parent.selectedSQL != selection { parent.selectedSQL = selection }
}
func textDidChange(_ notification: Notification) {
guard let editor = notification.object as? NSTextView else {
return
@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
</plist>
@@ -1,49 +0,0 @@
{
"" : {
"diagnostics" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement-primary.dia",
"emit-module-dependencies" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement-primary-emit-module.d",
"emit-module-diagnostics" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement-primary-emit-module.dia",
"pch" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement-primary-Bridging-header.pch",
"swift-dependencies" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement-primary.swiftdeps"
},
"/Volumes/Developer/Applications/WorkbenchReplacement/WorkbenchReplacement/App.swift" : {
"const-values" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/App.swiftconstvalues",
"dependencies" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/App.d",
"diagnostics" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/App.dia",
"index-unit-output-path" : "/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/App.o",
"llvm-bc" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/App.bc",
"object" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/App.o",
"swift-dependencies" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/App.swiftdeps",
"swiftmodule" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/App~partial.swiftmodule"
},
"/Volumes/Developer/Applications/WorkbenchReplacement/WorkbenchReplacement/DatabaseModel.swift" : {
"const-values" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/DatabaseModel.swiftconstvalues",
"dependencies" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/DatabaseModel.d",
"diagnostics" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/DatabaseModel.dia",
"index-unit-output-path" : "/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/DatabaseModel.o",
"llvm-bc" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/DatabaseModel.bc",
"object" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/DatabaseModel.o",
"swift-dependencies" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/DatabaseModel.swiftdeps",
"swiftmodule" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/DatabaseModel~partial.swiftmodule"
},
"/Volumes/Developer/Applications/WorkbenchReplacement/WorkbenchReplacement/MainView.swift" : {
"const-values" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.swiftconstvalues",
"dependencies" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.d",
"diagnostics" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.dia",
"index-unit-output-path" : "/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.o",
"llvm-bc" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.bc",
"object" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.o",
"swift-dependencies" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.swiftdeps",
"swiftmodule" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView~partial.swiftmodule"
},
"/Volumes/Developer/Applications/WorkbenchReplacement/WorkbenchReplacement/MySQLProtocol.swift" : {
"const-values" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MySQLProtocol.swiftconstvalues",
"dependencies" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MySQLProtocol.d",
"diagnostics" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MySQLProtocol.dia",
"index-unit-output-path" : "/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MySQLProtocol.o",
"llvm-bc" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MySQLProtocol.bc",
"object" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MySQLProtocol.o",
"swift-dependencies" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MySQLProtocol.swiftdeps",
"swiftmodule" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MySQLProtocol~partial.swiftmodule"
}
}
@@ -1 +0,0 @@
{"case-sensitive":"false","roots":[],"version":0}
@@ -1 +0,0 @@
["AnyResolverProviding","AppEntity","AppEnum","AppExtension","AppIntent","AppIntentsPackage","AppShortcutProviding","AppShortcutsProvider","AppUnionValue","AppUnionValueCasesProviding","DynamicOptionsProvider","EntityQuery","ExtensionPointDefining","IntentValueQuery","Resolver","TransientEntity","_AssistantIntentsProvider","_GenerativeFunctionExtractable","_IntentValueRepresentable"]
@@ -1,4 +0,0 @@
/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/App.o
/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.o
/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/DatabaseModel.o
/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MySQLProtocol.o
@@ -1,4 +0,0 @@
/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/App.swiftconstvalues
/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.swiftconstvalues
/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/DatabaseModel.swiftconstvalues
/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MySQLProtocol.swiftconstvalues
@@ -1,4 +0,0 @@
/Volumes/Developer/Applications/WorkbenchReplacement/WorkbenchReplacement/App.swift
/Volumes/Developer/Applications/WorkbenchReplacement/WorkbenchReplacement/MainView.swift
/Volumes/Developer/Applications/WorkbenchReplacement/WorkbenchReplacement/DatabaseModel.swift
/Volumes/Developer/Applications/WorkbenchReplacement/WorkbenchReplacement/MySQLProtocol.swift
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.get-task-allow</key>
<true/>
</dict>
</plist>
@@ -1,68 +0,0 @@
{
"buildCommand" : {
"command" : "build",
"skipDependencies" : false,
"style" : "buildOnly"
},
"configuredTargets" : [
{
"guid" : "34f2007b1d0798da0712fb82baec90fb2878cdfbaa5fcb5fa30c8485a83dfb14"
}
],
"containerPath" : "/Volumes/Developer/Applications/WorkbenchReplacement/WorkbenchReplacement.xcodeproj",
"continueBuildingAfterErrors" : false,
"dependencyScope" : "workspace",
"enableIndexBuildArena" : false,
"hideShellScriptEnvironment" : false,
"parameters" : {
"action" : "build",
"activeArchitecture" : "arm64",
"activeRunDestination" : {
"disableOnlyActiveArch" : false,
"hostTargetedPlatform" : null,
"platform" : "macosx",
"sdk" : "macosx27.0",
"sdkVariant" : "macos",
"supportedArchitectures" : [
"arm64e",
"arm64",
"x86_64"
],
"targetArchitecture" : "arm64"
},
"arenaInfo" : {
"buildIntermediatesPath" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex",
"buildProductsPath" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Products",
"derivedDataPath" : "/Volumes/Developer/Applications/WorkbenchReplacement/build",
"indexDataStoreFolderPath" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Index.noindex/DataStore",
"indexEnableDataStore" : true,
"indexPCHPath" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Index.noindex/PrecompiledHeaders",
"pchPath" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/PrecompiledHeaders"
},
"configurationName" : "Debug",
"overrides" : {
"commandLine" : {
"table" : {
}
},
"synthesized" : {
"table" : {
"ACTION" : "build",
"COLOR_DIAGNOSTICS" : "YES",
"diagnostic_message_length" : "162",
"ENABLE_PREVIEWS" : "NO",
"ENABLE_XOJIT_PREVIEWS" : "YES",
"ONLY_ACTIVE_ARCH" : "YES",
"TOOLCHAINS" : "com.apple.dt.toolchain.Metal.32023.921.5 $(inherited)"
}
}
}
},
"schemeCommand" : "launch",
"showNonLoggedProgress" : true,
"useDryRun" : false,
"useImplicitDependencies" : true,
"useLegacyBuildLocations" : false,
"useParallelTargets" : true
}
File diff suppressed because one or more lines are too long
@@ -1,2 +0,0 @@
Target dependency graph (1 target)
Target 'WorkbenchReplacement' in project 'WorkbenchReplacement' (no dependencies)
@@ -3,3 +3,5 @@ fde3a409a1c7aaa1a5eda9dfaf1056e0
1c438df96f25657d77b9e55a3fe47133
120e9a8ae12faa2f3ba3b63c6df49ba3
a06f417aacd1b83f24bd3cc2cf22ad87
7f733ee9c557a22c77228b85d3bb3f5e
a06f417aacd1b83f24bd3cc2cf22ad87
@@ -22,3 +22,6 @@
/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>
<key>cdhash</key>
<data>
/SGo73prsJFq4QMPB62FM4xJVmQ=
crV31c90LJuDZMS/3i2Ag3RbHL0=
</data>
<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>
+136 -10
View File
@@ -6,6 +6,48 @@
<integer>13</integer>
<key>logs</key>
<dict>
<key>04704647-E534-492A-ADC4-87C265C050C8</key>
<dict>
<key>className</key>
<string>IDECommandLineBuildLog</string>
<key>documentTypeString</key>
<string>&lt;nil&gt;</string>
<key>domainType</key>
<string>Xcode.IDEActivityLogDomainType.BuildLog</string>
<key>fileName</key>
<string>04704647-E534-492A-ADC4-87C265C050C8.xcactivitylog</string>
<key>hasPrimaryLog</key>
<true/>
<key>primaryObservable</key>
<dict>
<key>highLevelStatus</key>
<string>E</string>
<key>totalNumberOfAnalyzerIssues</key>
<integer>0</integer>
<key>totalNumberOfErrors</key>
<integer>1</integer>
<key>totalNumberOfTestFailures</key>
<integer>0</integer>
<key>totalNumberOfWarnings</key>
<integer>0</integer>
</dict>
<key>schemeIdentifier-containerName</key>
<string>WorkbenchReplacement project</string>
<key>schemeIdentifier-schemeName</key>
<string>WorkbenchReplacement</string>
<key>schemeIdentifier-sharedScheme</key>
<integer>1</integer>
<key>signature</key>
<string>Building project WorkbenchReplacement with scheme WorkbenchReplacement and configuration Debug</string>
<key>timeStartedRecording</key>
<real>813879039.75688195</real>
<key>timeStoppedRecording</key>
<real>813879050.66503203</real>
<key>title</key>
<string>Building project WorkbenchReplacement with scheme WorkbenchReplacement and configuration Debug</string>
<key>uniqueIdentifier</key>
<string>04704647-E534-492A-ADC4-87C265C050C8</string>
</dict>
<key>0BE3AE4B-CC13-4C5D-9DA7-73BB03242CE6</key>
<dict>
<key>className</key>
@@ -48,7 +90,7 @@
<key>uniqueIdentifier</key>
<string>0BE3AE4B-CC13-4C5D-9DA7-73BB03242CE6</string>
</dict>
<key>A5DCBFE5-D4F5-45BE-8C98-8AC35E432D50</key>
<key>546596EB-C774-4242-A9D1-C86D46C7CAE8</key>
<dict>
<key>className</key>
<string>IDECommandLineBuildLog</string>
@@ -57,7 +99,7 @@
<key>domainType</key>
<string>Xcode.IDEActivityLogDomainType.BuildLog</string>
<key>fileName</key>
<string>A5DCBFE5-D4F5-45BE-8C98-8AC35E432D50.xcactivitylog</string>
<string>546596EB-C774-4242-A9D1-C86D46C7CAE8.xcactivitylog</string>
<key>hasPrimaryLog</key>
<true/>
<key>primaryObservable</key>
@@ -82,15 +124,15 @@
<key>signature</key>
<string>Building project WorkbenchReplacement with scheme WorkbenchReplacement and configuration Debug</string>
<key>timeStartedRecording</key>
<real>813878698.49529397</real>
<real>813879115.19696796</real>
<key>timeStoppedRecording</key>
<real>813878714.73899198</real>
<real>813879130.83529198</real>
<key>title</key>
<string>Building project WorkbenchReplacement with scheme WorkbenchReplacement and configuration Debug</string>
<key>uniqueIdentifier</key>
<string>A5DCBFE5-D4F5-45BE-8C98-8AC35E432D50</string>
<string>546596EB-C774-4242-A9D1-C86D46C7CAE8</string>
</dict>
<key>BE675D45-226B-48D5-9BED-8FCC12B530FF</key>
<key>67C568D7-BB93-4240-AB48-EF7CBFC85D9F</key>
<dict>
<key>className</key>
<string>IDECommandLineBuildLog</string>
@@ -99,7 +141,7 @@
<key>domainType</key>
<string>Xcode.IDEActivityLogDomainType.BuildLog</string>
<key>fileName</key>
<string>BE675D45-226B-48D5-9BED-8FCC12B530FF.xcactivitylog</string>
<string>67C568D7-BB93-4240-AB48-EF7CBFC85D9F.xcactivitylog</string>
<key>hasPrimaryLog</key>
<true/>
<key>primaryObservable</key>
@@ -124,13 +166,97 @@
<key>signature</key>
<string>Building project WorkbenchReplacement with scheme WorkbenchReplacement and configuration Debug</string>
<key>timeStartedRecording</key>
<real>813878365.00770497</real>
<real>813879245.19600403</real>
<key>timeStoppedRecording</key>
<real>813878379.89619505</real>
<real>813879260.71434796</real>
<key>title</key>
<string>Building project WorkbenchReplacement with scheme WorkbenchReplacement and configuration Debug</string>
<key>uniqueIdentifier</key>
<string>BE675D45-226B-48D5-9BED-8FCC12B530FF</string>
<string>67C568D7-BB93-4240-AB48-EF7CBFC85D9F</string>
</dict>
<key>C0104C63-BBE9-4E40-811C-E7DC2E1277F6</key>
<dict>
<key>className</key>
<string>IDECommandLineBuildLog</string>
<key>documentTypeString</key>
<string>&lt;nil&gt;</string>
<key>domainType</key>
<string>Xcode.IDEActivityLogDomainType.BuildLog</string>
<key>fileName</key>
<string>C0104C63-BBE9-4E40-811C-E7DC2E1277F6.xcactivitylog</string>
<key>hasPrimaryLog</key>
<true/>
<key>primaryObservable</key>
<dict>
<key>highLevelStatus</key>
<string>S</string>
<key>totalNumberOfAnalyzerIssues</key>
<integer>0</integer>
<key>totalNumberOfErrors</key>
<integer>0</integer>
<key>totalNumberOfTestFailures</key>
<integer>0</integer>
<key>totalNumberOfWarnings</key>
<integer>0</integer>
</dict>
<key>schemeIdentifier-containerName</key>
<string>WorkbenchReplacement project</string>
<key>schemeIdentifier-schemeName</key>
<string>WorkbenchReplacement</string>
<key>schemeIdentifier-sharedScheme</key>
<integer>1</integer>
<key>signature</key>
<string>Building project WorkbenchReplacement with scheme WorkbenchReplacement and configuration Debug</string>
<key>timeStartedRecording</key>
<real>813879412.71912897</real>
<key>timeStoppedRecording</key>
<real>813879428.48907101</real>
<key>title</key>
<string>Building project WorkbenchReplacement with scheme WorkbenchReplacement and configuration Debug</string>
<key>uniqueIdentifier</key>
<string>C0104C63-BBE9-4E40-811C-E7DC2E1277F6</string>
</dict>
<key>D9A8286B-C5F6-4640-8996-6E78F7FFA481</key>
<dict>
<key>className</key>
<string>IDECommandLineBuildLog</string>
<key>documentTypeString</key>
<string>&lt;nil&gt;</string>
<key>domainType</key>
<string>Xcode.IDEActivityLogDomainType.BuildLog</string>
<key>fileName</key>
<string>D9A8286B-C5F6-4640-8996-6E78F7FFA481.xcactivitylog</string>
<key>hasPrimaryLog</key>
<true/>
<key>primaryObservable</key>
<dict>
<key>highLevelStatus</key>
<string>E</string>
<key>totalNumberOfAnalyzerIssues</key>
<integer>0</integer>
<key>totalNumberOfErrors</key>
<integer>1</integer>
<key>totalNumberOfTestFailures</key>
<integer>0</integer>
<key>totalNumberOfWarnings</key>
<integer>0</integer>
</dict>
<key>schemeIdentifier-containerName</key>
<string>WorkbenchReplacement project</string>
<key>schemeIdentifier-schemeName</key>
<string>WorkbenchReplacement</string>
<key>schemeIdentifier-sharedScheme</key>
<integer>1</integer>
<key>signature</key>
<string>Building project WorkbenchReplacement with scheme WorkbenchReplacement and configuration Debug</string>
<key>timeStartedRecording</key>
<real>813879086.34161401</real>
<key>timeStoppedRecording</key>
<real>813879089.726318</real>
<key>title</key>
<string>Building project WorkbenchReplacement with scheme WorkbenchReplacement and configuration Debug</string>
<key>uniqueIdentifier</key>
<string>D9A8286B-C5F6-4640-8996-6E78F7FFA481</string>
</dict>
</dict>
</dict>
@@ -1 +1 @@
1792186032.67269: Module build session file for module cache at Path(str: "/Volumes/Developer/Applications/WorkbenchReplacement/build/ModuleCache.noindex")
1792186613.153801: 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">
<dict>
<key>LastAccessedDate</key>
<date>2026-10-16T21:27:12Z</date>
<date>2026-10-16T21:36:53Z</date>
<key>WorkspacePath</key>
<string>/Volumes/Developer/Applications/WorkbenchReplacement/WorkbenchReplacement.xcodeproj</string>
</dict>
+11
View File
@@ -0,0 +1,11 @@
# Phase 17 — SQL Editor Productivity
- Added persisted query execution history (latest 200 statements), including timestamp, connection, schema, success/failure, and error details. Schema-list refreshes are not recorded.
- Added History panel with filtering, reopen in a new tab, and clear history.
- Added a persistent saved SQL query library, with save, filter, open, and delete.
- Added AppKit SQL keyword completion via NSTextViewDelegate's standard completion interface (typically Esc or Option-Esc depending on macOS key bindings). Completions are keyword-based, not live schema-aware.
- Preserved the existing SQL syntax highlighting and query tab handling.
## Caveats
History and saved SQL are stored in local UserDefaults and may contain sensitive SQL text; avoid executing statements containing literal secrets if local history retention is a concern. Results remain subject to the existing protocol row cap. This phase has not been built in Xcode or tested against a live server.
+18
View File
@@ -0,0 +1,18 @@
# Phase 18 — SQL Editor and Result Grid
Built from the Phase 17 project archive and includes the correction for the Phase 17 `localizedCaseInsensitiveHasPrefix` compilation error.
## Changes
- Execute highlighted SQL independently of the complete editor buffer (Run Selection).
- SQL completion includes cached table and view names loaded in the Schemas navigator, alongside SQL keywords. Column-aware suggestions are not yet implemented.
- Case-insensitive local filtering of query result rows.
- Local result sorting by chosen column, ascending or descending.
- CSV export of the currently displayed rows, with RFC-style doubled quotes, UTF-8 BOM and CRLF line endings.
- Query elapsed wall-clock time, shown after execution completes (includes connection overhead).
## Limitations
- Result grid filters/sorts operate only on rows returned by the existing query engine, not the full database table.
- The CSV writer constructs the file in memory and should not be used for huge result sets.
- SQL autocomplete suggestions depend on schemas already loaded; no column metadata is fetched.
- No new SQL error underlining was added.
- Not compiled on macOS or tested against a MySQL server; run xcodebuild on the Mac.