added typeaheead and introspetion

This commit is contained in:
Mark Gregory
2026-10-18 16:39:55 +01:00
parent 1ca16eb69a
commit b6123a0043
3 changed files with 226 additions and 66 deletions
+38 -15
View File
@@ -178,8 +178,8 @@ final class DatabaseModel: ObservableObject {
@Published var schemaProcedures: [String: [String]] = [:]
@Published var schemaFunctions: [String: [String]] = [:]
@Published var schemaColumns: [String: [String]] = [:]
// Introspection is a connection-scoped, RAM-only snapshot of table structures.
struct IntrospectedColumn: Identifiable {
// Connection-scoped metadata snapshots persist between launches.
struct IntrospectedColumn: Identifiable, Codable {
var id: String { "\(table).\(name)" }
let table: String
let name: String
@@ -189,7 +189,7 @@ final class DatabaseModel: ObservableObject {
let defaultValue: String?
let extra: String
}
struct SchemaIntrospection {
struct SchemaIntrospection: Codable {
let connectionID: UUID
let database: String
let loadedAt: Date
@@ -198,6 +198,21 @@ final class DatabaseModel: ObservableObject {
let columnsByTable: [String: [IntrospectedColumn]]
}
@Published private(set) var introspections: [String: SchemaIntrospection] = [:]
private let introspectionStorageKey = "DBSyn.introspection.snapshots.v2"
private func persistIntrospections() {
if let data = try? JSONEncoder().encode(introspections) {
UserDefaults.standard.set(data, forKey: introspectionStorageKey)
}
}
private func applyIntrospection(_ snapshot: SchemaIntrospection) {
schemaTables[snapshot.database] = snapshot.tables
schemaViews[snapshot.database] = snapshot.views
schemaColumns[snapshot.database] = Array(Set(snapshot.columnsByTable.values.flatMap { $0 }.flatMap {
[$0.name, "\($0.table).\($0.name)"]
})).sorted()
}
func refreshIntrospection(database: String) { introspect(database: database) }
@Published private(set) var introspectionLoading: Set<String> = []
private func introspectionKey(_ database: String, _ connectionID: UUID) -> String {
"\(connectionID.uuidString):\(database)"
@@ -242,12 +257,9 @@ final class DatabaseModel: ObservableObject {
connectionID: profile.id, database: database,
loadedAt: Date(), tables: tables, views: views, columnsByTable: columns
)
schemaTables[database] = tables
schemaViews[database] = views
schemaColumns[database] = Array(Set(columns.values.flatMap { $0 }.flatMap {
[$0.name, "\($0.table).\($0.name)"]
})).sorted()
status = "Introspection complete: \(tables.count) tables, \(views.count) views, \(columns.values.reduce(0) { $0 + $1.count }) columns in \(database) (RAM)"
if let snapshot = introspections[key] { applyIntrospection(snapshot) }
persistIntrospections()
status = "Introspection refreshed: \(tables.count) tables, \(views.count) views, \(columns.values.reduce(0) { $0 + $1.count }) columns in \(database)"
} catch {
status = "Introspection failed for \(database): \(error.localizedDescription)"
}
@@ -272,7 +284,16 @@ final class DatabaseModel: ObservableObject {
if let data = UserDefaults.standard.data(forKey: storageKey),
let decoded = try? JSONDecoder().decode([ConnectionProfile].self, from: data) { profiles = decoded }
if profiles.isEmpty { profiles = [ConnectionProfile()] }
if let data = UserDefaults.standard.data(forKey: introspectionStorageKey),
let cached = try? JSONDecoder().decode([String: SchemaIntrospection].self, from: data) {
introspections = cached.filter { entry in profiles.contains(where: { $0.id == entry.value.connectionID }) }
}
selectedID = profiles.first?.id
if let id = selectedID {
for snapshot in introspections.values where snapshot.connectionID == id {
applyIntrospection(snapshot)
}
}
activeQueryID = queryTabs.first?.id
}
@@ -434,12 +455,12 @@ final class DatabaseModel: ObservableObject {
}
}
if let snapshot = introspection(for: database) {
schemaTables[database] = snapshot.tables
schemaViews[database] = snapshot.views
} else {
schemaTables[database] = tables
schemaViews[database] = views
schemaTables[database] = tables
schemaViews[database] = views
// Refresh cached structure when the server reports a changed object list.
if let snapshot = introspection(for: database),
(Set(snapshot.tables) != Set(tables) || Set(snapshot.views) != Set(views)) {
introspect(database: database)
}
// Routine metadata is separate from SHOW FULL TABLES.
@@ -484,6 +505,8 @@ final class DatabaseModel: ObservableObject {
}
status = "Loaded \(tables.count) tables and \(views.count) views from \(database)"
// Revalidate column definitions as well as object names on reconnect/refresh.
if introspection(for: database) != nil { introspect(database: database) }
} catch {
status = "Unable to load tables: \(error.localizedDescription)"
result = QueryResult(message: error.localizedDescription)