Improved progress display
Previously, the progress bar updated only after completing an entire table. For large tables such as LIBARC, this could make the application appear to have stopped responding. Phase 10 updates the interface while rows are being exported.
This commit is contained in:
@@ -63,9 +63,73 @@ struct WorkbenchQueryTab: Identifiable {
|
||||
}
|
||||
}
|
||||
|
||||
struct SQLHistoryEntry: Identifiable, Codable {
|
||||
var id = UUID()
|
||||
var executedAt = Date()
|
||||
var connectionName: String
|
||||
var database: String
|
||||
var sql: String
|
||||
var succeeded: Bool
|
||||
var detail: String
|
||||
}
|
||||
|
||||
struct SavedSQLQuery: Identifiable, Codable {
|
||||
var id = UUID()
|
||||
var name: String
|
||||
var sql: String
|
||||
}
|
||||
|
||||
@MainActor
|
||||
final class DatabaseModel: ObservableObject {
|
||||
@Published var profiles: [ConnectionProfile] = []
|
||||
@Published private(set) var queryHistory: [SQLHistoryEntry] = []
|
||||
@Published private(set) var savedQueries: [SavedSQLQuery] = []
|
||||
private let historyKey = "WorkbenchReplacement.queryHistory.v1"
|
||||
private let savedQueriesKey = "WorkbenchReplacement.savedQueries.v1"
|
||||
private let maxHistoryEntries = 200
|
||||
|
||||
func saveCurrentQuery(named name: String) {
|
||||
let clean = name.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !clean.isEmpty, !sql.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { return }
|
||||
savedQueries.insert(SavedSQLQuery(name: clean, sql: sql), at: 0)
|
||||
persistSavedQueries()
|
||||
}
|
||||
|
||||
func openSavedQuery(_ entry: SavedSQLQuery) {
|
||||
newQueryTab()
|
||||
sql = entry.sql
|
||||
if let id = activeQueryID, let index = queryTabs.firstIndex(where: { $0.id == id }) {
|
||||
queryTabs[index].title = entry.name
|
||||
queryTabs[index].sql = entry.sql
|
||||
}
|
||||
}
|
||||
|
||||
func deleteSavedQuery(_ id: UUID) {
|
||||
savedQueries.removeAll { $0.id == id }
|
||||
persistSavedQueries()
|
||||
}
|
||||
|
||||
func clearQueryHistory() {
|
||||
queryHistory.removeAll()
|
||||
UserDefaults.standard.removeObject(forKey: historyKey)
|
||||
}
|
||||
|
||||
private func persistSavedQueries() {
|
||||
if let data = try? JSONEncoder().encode(savedQueries) {
|
||||
UserDefaults.standard.set(data, forKey: savedQueriesKey)
|
||||
}
|
||||
}
|
||||
|
||||
private func recordQuery(_ statement: String, profile: ConnectionProfile, database: String, succeeded: Bool, detail: String) {
|
||||
queryHistory.insert(SQLHistoryEntry(connectionName: profile.name, database: database, sql: statement, succeeded: succeeded, detail: detail), at: 0)
|
||||
if queryHistory.count > maxHistoryEntries {
|
||||
queryHistory.removeLast(queryHistory.count - maxHistoryEntries)
|
||||
}
|
||||
if let data = try? JSONEncoder().encode(queryHistory) {
|
||||
UserDefaults.standard.set(data, forKey: historyKey)
|
||||
}
|
||||
}
|
||||
|
||||
@Published var selectedID: UUID?
|
||||
@Published var queryTabs: [WorkbenchQueryTab] = [
|
||||
WorkbenchQueryTab(
|
||||
@@ -80,11 +144,20 @@ final class DatabaseModel: ObservableObject {
|
||||
@Published var schemaTables: [String: [String]] = [:]
|
||||
@Published var schemaViews: [String: [String]] = [:]
|
||||
@Published var selectedDatabase = ""
|
||||
@Published var queryDurationText = ""
|
||||
@Published var isRunning = false
|
||||
@Published var status = "Not connected"
|
||||
private let storageKey = "WorkbenchReplacement.connectionProfiles.native.v1"
|
||||
|
||||
init() {
|
||||
if let data = UserDefaults.standard.data(forKey: historyKey),
|
||||
let entries = try? JSONDecoder().decode([SQLHistoryEntry].self, from: data) {
|
||||
queryHistory = Array(entries.prefix(maxHistoryEntries))
|
||||
}
|
||||
if let data = UserDefaults.standard.data(forKey: savedQueriesKey),
|
||||
let entries = try? JSONDecoder().decode([SavedSQLQuery].self, from: data) {
|
||||
savedQueries = entries
|
||||
}
|
||||
if let data = UserDefaults.standard.data(forKey: storageKey),
|
||||
let decoded = try? JSONDecoder().decode([ConnectionProfile].self, from: data) { profiles = decoded }
|
||||
if profiles.isEmpty { profiles = [ConnectionProfile()] }
|
||||
@@ -296,10 +369,16 @@ final class DatabaseModel: ObservableObject {
|
||||
}
|
||||
execute(sql, listingDatabases: false)
|
||||
}
|
||||
func runSQL(statement: String) {
|
||||
guard !statement.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { return }
|
||||
execute(statement, listingDatabases: false)
|
||||
}
|
||||
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 }
|
||||
isRunning = true
|
||||
if !listingDatabases { queryDurationText = "Running…" }
|
||||
let queryStarted = Date()
|
||||
status = "Connecting…"
|
||||
let activeDB = listingDatabases ? "" : (selectedDatabase.isEmpty ? profile.database : selectedDatabase)
|
||||
let password = PasswordStore.read(profile.id)
|
||||
@@ -317,10 +396,17 @@ final class DatabaseModel: ObservableObject {
|
||||
queryTabs[index].result = parsed
|
||||
}
|
||||
status = "Query completed (\(parsed.rows.count) rows)"
|
||||
recordQuery(statement, profile: profile, database: activeDB, succeeded: true, detail: status)
|
||||
}
|
||||
} catch {
|
||||
status = "Error"
|
||||
result = QueryResult(message: error.localizedDescription)
|
||||
if !listingDatabases {
|
||||
recordQuery(statement, profile: profile, database: activeDB, succeeded: false, detail: error.localizedDescription)
|
||||
}
|
||||
}
|
||||
if !listingDatabases {
|
||||
queryDurationText = String(format: "%.3f s", Date().timeIntervalSince(queryStarted))
|
||||
}
|
||||
isRunning = false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user