new release script

This commit is contained in:
Mark Gregory
2026-10-18 16:06:16 +01:00
parent 73e9a3efe2
commit abd8c59e60
4 changed files with 113 additions and 11 deletions
+86 -8
View File
@@ -455,27 +455,42 @@ final class DatabaseModel: ObservableObject {
}
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 }
let statements = listingDatabases ? [statement] : SQLStatementSplitter.split(statement)
guard !statements.isEmpty else { return }
isRunning = true
if !listingDatabases { queryDurationText = "Running…" }
let queryStarted = Date()
status = "Connecting…"
let activeDB = listingDatabases ? "" : (selectedDatabase.isEmpty ? profile.database : selectedDatabase)
status = "Executing…"
let password = PasswordStore.read(profile.id)
Task {
var activeDB = listingDatabases ? "" : (selectedDatabase.isEmpty ? profile.database : selectedDatabase)
do {
let parsed = try await NativeMySQLRunner.run(profile: profile, database: activeDB, password: password, sql: statement)
var lastResult = QueryResult()
for item in statements {
// Each query is sent separately: COM_QUERY accepts one SQL
// statement, not an entire semicolon-separated editor buffer.
let parsed = try await NativeMySQLRunner.run(
profile: profile, database: activeDB, password: password, sql: item
)
lastResult = parsed
if let usedSchema = SQLStatementSplitter.usedDatabase(item) {
activeDB = usedSchema
selectedDatabase = usedSchema
// Metadata is cached in memory for native SQL completion.
loadTables(database: usedSchema)
}
}
if listingDatabases {
databases = parsed.rows.compactMap { $0.first }
databases = lastResult.rows.compactMap { $0.first }
status = "Connected to \(profile.name)"
} else {
result = parsed
result = lastResult
if let id = activeQueryID,
let index = queryTabs.firstIndex(where: { $0.id == id }) {
queryTabs[index].sql = sql
queryTabs[index].result = parsed
queryTabs[index].result = lastResult
}
status = "Query completed (\(parsed.rows.count) rows)"
status = "Executed \(statements.count) statement(s) (\(lastResult.rows.count) rows)"
recordQuery(statement, profile: profile, database: activeDB, succeeded: true, detail: status)
}
} catch {
@@ -491,9 +506,72 @@ final class DatabaseModel: ObservableObject {
isRunning = false
}
}
}
// Splits editor scripts without splitting semicolons inside quoted values or comments.
private enum SQLStatementSplitter {
static func split(_ source: String) -> [String] {
let chars = Array(source)
var output: [String] = []
var current = ""
var state = 0 // 0 normal, 1 single quote, 2 double quote, 3 identifier, 4 line comment, 5 block comment
var i = 0
while i < chars.count {
let c = chars[i]
let next = i + 1 < chars.count ? chars[i + 1] : "\0"
if state == 4 {
if c == "\n" || c == "\r" { state = 0; current.append(" ") }
i += 1; continue
}
if state == 5 {
if c == "*" && next == "/" { state = 0; i += 2; current.append(" ") }
else { i += 1 }
continue
}
if state == 0 {
if c == "#" || (c == "-" && next == "-" && (i + 2 == chars.count || chars[i + 2].isWhitespace)) {
state = 4; i += c == "#" ? 1 : 2; continue
}
if c == "/" && next == "*" { state = 5; i += 2; continue }
if c == "'" { state = 1 }
else if c == "\"" { state = 2 }
else if c == "`" { state = 3 }
else if c == ";" {
let trimmed = current.trimmingCharacters(in: .whitespacesAndNewlines)
if !trimmed.isEmpty { output.append(trimmed) }
current = ""; i += 1; continue
}
} else {
if c == "\\" && state != 3 && i + 1 < chars.count {
current.append(c); current.append(next); i += 2; continue
}
if (state == 1 && c == "'") || (state == 2 && c == "\"") || (state == 3 && c == "`") {
if next == c { current.append(c); current.append(next); i += 2; continue }
state = 0
}
}
current.append(c); i += 1
}
let trimmed = current.trimmingCharacters(in: .whitespacesAndNewlines)
if !trimmed.isEmpty { output.append(trimmed) }
return output
}
static func usedDatabase(_ statement: String) -> String? {
let pattern = #"(?is)^\s*USE\s+(?:`((?:``|[^`])+)`|([A-Za-z0-9_$]+))\s*$"#
guard let regex = try? NSRegularExpression(pattern: pattern),
let match = regex.firstMatch(in: statement, range: NSRange(statement.startIndex..., in: statement)) else { return nil }
for index in [1, 2] where match.range(at: index).location != NSNotFound {
if let range = Range(match.range(at: index), in: statement) {
return String(statement[range]).replacingOccurrences(of: "``", with: "`")
}
}
return nil
}
}
// MARK: - SQL Keyword Formatter
enum SQLFormatter {