import Foundation import Security struct ConnectionProfile: Identifiable, Codable, Hashable { var id = UUID() var name = "Local MySQL" var host = "127.0.0.1" var port = 3306 var username = "root" var database = "" } struct QueryResult { var columns: [String] = [] var rows: [[String]] = [] var message = "" } enum PasswordStore { static func read(_ id: UUID) -> String { let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: "WorkbenchReplacement.MySQL", kSecAttrAccount as String: id.uuidString, kSecReturnData as String: true, kSecMatchLimit as String: kSecMatchLimitOne] var value: CFTypeRef? guard SecItemCopyMatching(query as CFDictionary, &value) == errSecSuccess, let data = value as? Data else { return "" } return String(data: data, encoding: .utf8) ?? "" } static func save(_ password: String, id: UUID) { let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: "WorkbenchReplacement.MySQL", kSecAttrAccount as String: id.uuidString] SecItemDelete(query as CFDictionary) guard !password.isEmpty else { return } var attributes = query attributes[kSecValueData as String] = Data(password.utf8) SecItemAdd(attributes as CFDictionary, nil) } } @MainActor final class DatabaseModel: ObservableObject { @Published var profiles: [ConnectionProfile] = [] @Published var selectedID: UUID? @Published var sql = "SELECT VERSION() AS server_version;" @Published var result = QueryResult() @Published var databases: [String] = [] @Published var selectedDatabase = "" @Published var isRunning = false @Published var status = "Not connected" private let storageKey = "WorkbenchReplacement.connectionProfiles.native.v1" init() { if let data = UserDefaults.standard.data(forKey: storageKey), let decoded = try? JSONDecoder().decode([ConnectionProfile].self, from: data) { profiles = decoded } if profiles.isEmpty { profiles = [ConnectionProfile()] } selectedID = profiles.first?.id } var selected: ConnectionProfile? { profiles.first { $0.id == selectedID } } func save(_ profile: ConnectionProfile, password: String) { if let i = profiles.firstIndex(where: { $0.id == profile.id }) { profiles[i] = profile } else { profiles.append(profile) } PasswordStore.save(password, id: profile.id) selectedID = profile.id persist() } private func persist() { if let data = try? JSONEncoder().encode(profiles) { UserDefaults.standard.set(data, forKey: storageKey) } } func loadDatabases() { execute("SHOW DATABASES;", listingDatabases: true) } func runSQL() { execute(sql, 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 status = "Connecting…" let activeDB = listingDatabases ? "" : (selectedDatabase.isEmpty ? profile.database : selectedDatabase) let password = PasswordStore.read(profile.id) Task { do { let parsed = try await NativeMySQLRunner.run(profile: profile, database: activeDB, password: password, sql: statement) if listingDatabases { databases = parsed.rows.compactMap { $0.first } status = "Connected to \(profile.name)" } else { result = parsed status = "Query completed (\(parsed.rows.count) rows)" } } catch { status = "Error" result = QueryResult(message: error.localizedDescription) } isRunning = false } } }