connection updates

This commit is contained in:
Mark Gregory
2026-10-19 12:17:32 +01:00
parent 7cf013a783
commit 69c8d2c4db
3 changed files with 78 additions and 6 deletions
+19 -6
View File
@@ -362,6 +362,8 @@ final class DatabaseModel: ObservableObject {
}
@Published var selectedID: UUID?
// Invalidate in-flight UI updates when a connection is switched or edited.
private var connectionEpoch: UInt64 = 0
@Published var queryTabs: [WorkbenchQueryTab] = [
WorkbenchQueryTab(
title: "Query 1",
@@ -641,7 +643,7 @@ final class DatabaseModel: ObservableObject {
activateQuery(tab.id)
recordRecentSQLScript(url)
NotificationCenter.default.post(name: .workbenchRevealSQLEditor, object: nil)
status = "Opened \\(url.lastPathComponent)"
status = "Opened \(url.lastPathComponent)"
}
func saveSQLScript(asNewFile: Bool = false) {
@@ -669,9 +671,9 @@ final class DatabaseModel: ObservableObject {
queryTabs[index].fileURL = url
queryTabs[index].title = url.lastPathComponent
recordRecentSQLScript(url)
status = "Saved \\(url.lastPathComponent)"
status = "Saved \(url.lastPathComponent)"
} catch {
status = "Save failed: \\(error.localizedDescription)"
status = "Save failed: \(error.localizedDescription)"
}
}
@@ -681,6 +683,7 @@ final class DatabaseModel: ObservableObject {
guard let profile = profiles.first(where: { $0.id == id }) else { return }
guard selectedID != id else { return }
saveActiveQuery()
connectionEpoch &+= 1
if let oldID = selectedID { NativeMySQLRunner.disconnect(profileID: oldID) }
result = QueryResult()
isRunning = false
@@ -702,6 +705,7 @@ final class DatabaseModel: ObservableObject {
}
func save(_ profile: ConnectionProfile, password: String) {
connectionEpoch &+= 1
if let i = profiles.firstIndex(where: { $0.id == profile.id }) { profiles[i] = profile }
else { profiles.append(profile) }
PasswordStore.save(password, id: profile.id)
@@ -734,6 +738,7 @@ final class DatabaseModel: ObservableObject {
)
status = "Loading tables from \(database)…"
let epoch = connectionEpoch
Task {
do {
@@ -743,6 +748,7 @@ final class DatabaseModel: ObservableObject {
password: password,
sql: "SHOW FULL TABLES FROM `\(escaped)`"
)
guard selectedID == profile.id, connectionEpoch == epoch else { return }
var tables: [String] = []
var views: [String] = []
@@ -776,6 +782,7 @@ final class DatabaseModel: ObservableObject {
profile: profile, database: database, password: password,
sql: "SELECT ROUTINE_NAME, ROUTINE_TYPE FROM information_schema.ROUTINES WHERE ROUTINE_SCHEMA = '\(quotedSchema)' ORDER BY ROUTINE_NAME"
)
guard selectedID == profile.id, connectionEpoch == epoch else { return }
schemaProcedures[database] = routineResult.rows.compactMap { row in
row.count >= 2 && row[1].uppercased() == "PROCEDURE" ? row[0] : nil
}
@@ -783,6 +790,7 @@ final class DatabaseModel: ObservableObject {
row.count >= 2 && row[1].uppercased() == "FUNCTION" ? row[0] : nil
}
} catch {
guard selectedID == profile.id, connectionEpoch == epoch else { return }
schemaProcedures[database] = []
schemaFunctions[database] = []
}
@@ -798,6 +806,7 @@ final class DatabaseModel: ObservableObject {
password: password,
sql: "SELECT TABLE_NAME, COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = '\(quotedSchema)' ORDER BY TABLE_NAME, ORDINAL_POSITION"
)
guard selectedID == profile.id, connectionEpoch == epoch else { return }
var names = Set<String>()
for row in columnResult.rows where row.count >= 2 {
names.insert(row[1])
@@ -805,6 +814,7 @@ final class DatabaseModel: ObservableObject {
}
if introspection(for: database) == nil { schemaColumns[database] = names.sorted() }
} catch {
guard selectedID == profile.id, connectionEpoch == epoch else { return }
if introspection(for: database) == nil { schemaColumns[database] = [] }
}
@@ -812,6 +822,7 @@ final class DatabaseModel: ObservableObject {
// Revalidate column definitions as well as object names on reconnect/refresh.
if introspection(for: database) != nil { introspect(database: database) }
} catch {
guard selectedID == profile.id, connectionEpoch == epoch else { return }
status = "Unable to load tables: \(error.localizedDescription)"
result = QueryResult(message: error.localizedDescription)
}
@@ -869,6 +880,7 @@ final class DatabaseModel: ObservableObject {
let queryStarted = Date()
status = "Executing…"
let password = PasswordStore.read(profile.id)
let epoch = connectionEpoch
Task {
var activeDB = listingDatabases ? "" : (selectedDatabase.isEmpty ? profile.database : selectedDatabase)
do {
@@ -879,6 +891,7 @@ final class DatabaseModel: ObservableObject {
let parsed = try await NativeMySQLRunner.run(
profile: profile, database: activeDB, password: password, sql: item
)
guard selectedID == profile.id, connectionEpoch == epoch else { return }
lastResult = parsed
if let usedSchema = SQLStatementSplitter.usedDatabase(item) {
activeDB = usedSchema
@@ -888,7 +901,7 @@ final class DatabaseModel: ObservableObject {
introspect(database: usedSchema)
}
}
guard selectedID == profile.id else { return }
guard selectedID == profile.id, connectionEpoch == epoch else { return }
if listingDatabases {
databases = lastResult.rows.compactMap { $0.first }
status = "Connected to \(profile.name)"
@@ -905,7 +918,7 @@ final class DatabaseModel: ObservableObject {
} catch {
// An old connection must not overwrite the newly selected
// connection's state when its request finishes later.
guard selectedID == profile.id else { return }
guard selectedID == profile.id, connectionEpoch == epoch else { return }
status = "Error"
result = QueryResult(message: error.localizedDescription)
if !listingDatabases {
@@ -915,7 +928,7 @@ final class DatabaseModel: ObservableObject {
if !listingDatabases {
queryDurationText = String(format: "%.3f s", Date().timeIntervalSince(queryStarted))
}
if selectedID == profile.id { isRunning = false }
if selectedID == profile.id && connectionEpoch == epoch { isRunning = false }
}
}