new release script

This commit is contained in:
Mark Gregory
2026-10-18 16:09:06 +01:00
parent abd8c59e60
commit ecd6561831
2 changed files with 82 additions and 1 deletions
+72
View File
@@ -178,6 +178,78 @@ 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 {
var id: String { "\(table).\(name)" }
let table: String
let name: String
let dataType: String
let nullable: Bool
let key: String
let defaultValue: String?
let extra: String
}
struct SchemaIntrospection {
let connectionID: UUID
let database: String
let loadedAt: Date
let tables: [String]
let columnsByTable: [String: [IntrospectedColumn]]
}
@Published private(set) var introspections: [String: SchemaIntrospection] = [:]
@Published private(set) var introspectionLoading: Set<String> = []
private func introspectionKey(_ database: String, _ connectionID: UUID) -> String {
"\(connectionID.uuidString):\(database)"
}
func introspection(for database: String) -> SchemaIntrospection? {
guard let profile = selected else { return nil }
return introspections[introspectionKey(database, profile.id)]
}
func introspect(database: String) {
guard let profile = selected else { status = "Select a connection"; return }
let key = introspectionKey(database, profile.id)
guard !introspectionLoading.contains(key) else { return }
introspectionLoading.insert(key)
status = "Introspecting \(database)…"
let password = PasswordStore.read(profile.id)
let escaped = database.replacingOccurrences(of: "'", with: "''")
Task {
defer { introspectionLoading.remove(key) }
do {
let tableResult = try await NativeMySQLRunner.run(
profile: profile, database: database, password: password,
sql: "SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = '\(escaped)' AND TABLE_TYPE = 'BASE TABLE' ORDER BY TABLE_NAME"
)
let columnResult = try await NativeMySQLRunner.run(
profile: profile, database: database, password: password,
sql: "SELECT TABLE_NAME, COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_KEY, COLUMN_DEFAULT, EXTRA FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = '\(escaped)' ORDER BY TABLE_NAME, ORDINAL_POSITION"
)
let tables = tableResult.rows.compactMap { $0.first }
var columns: [String: [IntrospectedColumn]] = [:]
for row in columnResult.rows where row.count >= 7 {
let column = IntrospectedColumn(
table: row[0], name: row[1], dataType: row[2],
nullable: row[3] == "YES", key: row[4],
defaultValue: row[5] == "NULL" ? nil : row[5], extra: row[6]
)
columns[row[0], default: []].append(column)
}
// Ignore results from a connection that has been removed while loading.
guard profiles.contains(where: { $0.id == profile.id }) else { return }
introspections[key] = SchemaIntrospection(
connectionID: profile.id, database: database,
loadedAt: Date(), tables: tables, columnsByTable: columns
)
schemaTables[database] = tables
schemaColumns[database] = Array(Set(columns.values.flatMap { $0 }.flatMap {
[$0.name, "\($0.table).\($0.name)"]
})).sorted()
status = "Introspection complete: \(tables.count) tables, \(columns.values.reduce(0) { $0 + $1.count }) columns in \(database) (RAM)"
} catch {
status = "Introspection failed for \(database): \(error.localizedDescription)"
}
}
}
@Published var selectedDatabase = ""
@Published var queryDurationText = ""
@Published var isRunning = false
+10 -1
View File
@@ -472,6 +472,15 @@ struct MainView: View {
}
}
.contextMenu {
Button {
model.introspect(database: db)
} label: {
Label("Introspection — Load Table Structures into RAM", systemImage: "memorychip")
}
if let snapshot = model.introspection(for: db) {
Text("Cached: \(snapshot.tables.count) tables, \(snapshot.columnsByTable.values.reduce(0) { $0 + $1.count }) columns")
}
Divider()
Button("Set as Default Schema") { model.selectedDatabase = db }
Button("Filter to This Schema") { schemaFilter = db }
Button("Clear Schema Filter") { schemaFilter = "" }
@@ -963,7 +972,7 @@ struct MainView: View {
.fill(WB.border)
.frame(width: 1)
SQLTextView(text: $model.sql, selectedSQL: $selectedSQL, focusRequest: sqlFocusRequest, completions: Array(Set(model.schemaTables.values.flatMap { $0 } + model.schemaViews.values.flatMap { $0 } + (model.schemaColumns[model.selectedDatabase] ?? []))).sorted())
SQLTextView(text: $model.sql, selectedSQL: $selectedSQL, focusRequest: sqlFocusRequest, completions: Array(Set((model.schemaTables[model.selectedDatabase] ?? []) + (model.schemaViews[model.selectedDatabase] ?? []) + (model.schemaColumns[model.selectedDatabase] ?? []))).sorted())
}
.background(Color(nsColor: .textBackgroundColor))
}