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