diff --git a/Plugins/BigQueryDriverPlugin/BigQueryAuth.swift b/Plugins/BigQueryDriverPlugin/BigQueryAuth.swift index 0c7a5bae8..2cc2be4f5 100644 --- a/Plugins/BigQueryDriverPlugin/BigQueryAuth.swift +++ b/Plugins/BigQueryDriverPlugin/BigQueryAuth.swift @@ -548,7 +548,6 @@ private final class ImpersonatedServiceAccountDelegate: @unchecked Sendable, Big } private func fetchImpersonatedToken() async throws -> String { - // Get source token let sourceToken = try await sourceProvider.accessToken() // Exchange for impersonated token @@ -649,7 +648,6 @@ internal final class OAuthBrowserAuthProvider: @unchecked Sendable, BigQueryAuth let redirectUri = "http://127.0.0.1:\(port)" - // Build authorization URL var components = URLComponents(string: Self.authEndpoint) components?.queryItems = [ URLQueryItem(name: "client_id", value: clientId), @@ -665,7 +663,6 @@ internal final class OAuthBrowserAuthProvider: @unchecked Sendable, BigQueryAuth throw BigQueryError.authFailed("Failed to build OAuth authorization URL") } - // Open browser Self.logger.info("Opening browser for OAuth authorization") await NSWorkspace.shared.open(authUrl) @@ -680,13 +677,10 @@ internal final class OAuthBrowserAuthProvider: @unchecked Sendable, BigQueryAuth Self.logger.info("Received OAuth authorization code") - // Exchange auth code for tokens let tokens = try await exchangeAuthCode(code, redirectUri: redirectUri) - // Store refresh token lock.withLock { _refreshToken = tokens.refreshToken } - // Cache access token let newToken = CachedToken( token: tokens.accessToken, expiresAt: Date().addingTimeInterval(Double(tokens.expiresIn)) diff --git a/Plugins/BigQueryDriverPlugin/BigQueryConnection.swift b/Plugins/BigQueryDriverPlugin/BigQueryConnection.swift index 45d774f39..88b6ac323 100644 --- a/Plugins/BigQueryDriverPlugin/BigQueryConnection.swift +++ b/Plugins/BigQueryDriverPlugin/BigQueryConnection.swift @@ -317,7 +317,6 @@ internal final class BigQueryConnection: @unchecked Sendable { _session = urlSession } - // Test connectivity do { _ = try await executeQuery("SELECT 1") } catch { @@ -428,7 +427,6 @@ internal final class BigQueryConnection: @unchecked Sendable { _currentJobLocation = jobRef.location } - // Poll for completion if not done let finalJobResponse: BQJobResponse if let state = jobResponse.status?.state, state != "DONE" { finalJobResponse = try await pollJobCompletion( @@ -441,7 +439,6 @@ internal final class BigQueryConnection: @unchecked Sendable { finalJobResponse = jobResponse } - // Extract DML affected rows from job statistics let dmlAffectedRows: Int if let numStr = finalJobResponse.statistics?.query?.numDmlAffectedRows { dmlAffectedRows = Int(numStr) ?? 0 @@ -453,7 +450,6 @@ internal final class BigQueryConnection: @unchecked Sendable { let totalBytesBilled = finalJobResponse.statistics?.query?.totalBytesBilled let cacheHit = finalJobResponse.statistics?.query?.cacheHit - // Fetch first page of results let firstPage = try await getQueryResults( jobId: jobId, location: jobRef.location, auth: auth, session: session ) diff --git a/Plugins/BigQueryDriverPlugin/BigQueryOAuthServer.swift b/Plugins/BigQueryDriverPlugin/BigQueryOAuthServer.swift index 3344d793f..648e00c6c 100644 --- a/Plugins/BigQueryDriverPlugin/BigQueryOAuthServer.swift +++ b/Plugins/BigQueryDriverPlugin/BigQueryOAuthServer.swift @@ -35,7 +35,6 @@ internal final class BigQueryOAuthServer: @unchecked Sendable { func waitForAuthCode() async throws -> String { try await withCheckedThrowingContinuation { cont in lock.withLock { continuation = cont } - // Start 2-minute timeout let task = Task { try? await Task.sleep(nanoseconds: 120_000_000_000) self.lock.withLock { diff --git a/Plugins/BigQueryDriverPlugin/BigQueryPluginDriver.swift b/Plugins/BigQueryDriverPlugin/BigQueryPluginDriver.swift index 010b693b2..e450743f4 100644 --- a/Plugins/BigQueryDriverPlugin/BigQueryPluginDriver.swift +++ b/Plugins/BigQueryDriverPlugin/BigQueryPluginDriver.swift @@ -208,12 +208,10 @@ internal final class BigQueryPluginDriver: PluginDatabaseDriver, @unchecked Send ) } - // Tagged browsing queries if BigQueryQueryBuilder.isTaggedQuery(trimmed) { return try await executeTaggedQuery(trimmed, conn: conn, startTime: startTime) } - // Regular GoogleSQL let dataset = lock.withLock { _currentDataset } let result: BQExecuteResult do { @@ -871,7 +869,6 @@ internal final class BigQueryPluginDriver: PluginDatabaseDriver, @unchecked Send let typeNames = BigQueryTypeMapper.columnTypeNames(from: schema) let rows = BigQueryTypeMapper.flattenRows(from: result.queryResponse, schema: schema) - // Update column cache lock.withLock { _columnCache["\(params.dataset).\(params.table)"] = colNames } return PluginQueryResult( diff --git a/Plugins/BigQueryDriverPlugin/BigQueryQueryBuilder.swift b/Plugins/BigQueryDriverPlugin/BigQueryQueryBuilder.swift index adffeee8f..f8a425def 100644 --- a/Plugins/BigQueryDriverPlugin/BigQueryQueryBuilder.swift +++ b/Plugins/BigQueryDriverPlugin/BigQueryQueryBuilder.swift @@ -173,7 +173,6 @@ internal struct BigQueryQueryBuilder { var sql = "SELECT * FROM \(fqTable)" var whereClauses: [String] = [] - // Filters if let filters = params.filters, !filters.isEmpty { let rawMode = params.logicMode ?? "AND" let logicMode = (rawMode.uppercased() == "OR") ? "OR" : "AND" @@ -183,7 +182,6 @@ internal struct BigQueryQueryBuilder { } } - // Search if let searchText = params.searchText, !searchText.isEmpty { let searchCols = params.searchColumns?.isEmpty == false ? params.searchColumns! @@ -201,7 +199,6 @@ internal struct BigQueryQueryBuilder { sql += " WHERE " + whereClauses.joined(separator: " AND ") } - // Sort if let sortColumns = params.sortColumns, !sortColumns.isEmpty { let orderClauses = sortColumns.compactMap { sort -> String? in guard sort.columnIndex < columns.count else { return nil } diff --git a/Plugins/CassandraDriverPlugin/CassandraConnection.swift b/Plugins/CassandraDriverPlugin/CassandraConnection.swift index f3124bacc..7fdf64d5b 100644 --- a/Plugins/CassandraDriverPlugin/CassandraConnection.swift +++ b/Plugins/CassandraDriverPlugin/CassandraConnection.swift @@ -288,7 +288,6 @@ actor CassandraConnectionActor { let startTime = Date() - // Prepare let prepareFuture = cass_session_prepare(session, cql) guard let prepareFuture else { throw CassandraPluginError.queryFailed("Failed to prepare statement") @@ -307,7 +306,6 @@ actor CassandraConnectionActor { } defer { cass_prepared_free(prepared) } - // Bind parameters let statement = cass_prepared_bind(prepared) guard let statement else { throw CassandraPluginError.queryFailed("Failed to bind prepared statement") @@ -331,7 +329,6 @@ actor CassandraConnectionActor { } } - // Execute let future = cass_session_execute(session, statement) guard let future else { throw CassandraPluginError.queryFailed("Failed to execute prepared statement") diff --git a/Plugins/CassandraDriverPlugin/CassandraPlugin.swift b/Plugins/CassandraDriverPlugin/CassandraPlugin.swift index 0113b6e55..4feeedf65 100644 --- a/Plugins/CassandraDriverPlugin/CassandraPlugin.swift +++ b/Plugins/CassandraDriverPlugin/CassandraPlugin.swift @@ -304,7 +304,6 @@ internal final class CassandraPluginDriver: PluginDatabaseDriver, @unchecked Sen """ let result = try await execute(query: query) - // Parse and sort by kind order then position before mapping to PluginColumnInfo struct RawColumn { let name: String let dataType: String @@ -390,7 +389,6 @@ internal final class CassandraPluginDriver: PluginDatabaseDriver, @unchecked Sen let kind = row[safe: 1]?.asText ?? "COMPOSITES" let options = row[safe: 2]?.asText ?? "" - // Extract target column from options map var targetColumns: [String] = [] if let targetRange = options.range(of: "target: ") { let target = String(options[targetRange.upperBound...]) @@ -419,7 +417,6 @@ internal final class CassandraPluginDriver: PluginDatabaseDriver, @unchecked Sen func fetchTableDDL(table: String, schema: String?) async throws -> String { let ks = resolveKeyspace(schema) - // Build DDL from schema metadata let columns = try await fetchColumns(table: table, schema: ks) let partitionKeys = columns.filter(\.isPrimaryKey) diff --git a/Plugins/DuckDBDriverPlugin/DuckDBPlugin.swift b/Plugins/DuckDBDriverPlugin/DuckDBPlugin.swift index 574e04c53..69054dc98 100644 --- a/Plugins/DuckDBDriverPlugin/DuckDBPlugin.swift +++ b/Plugins/DuckDBDriverPlugin/DuckDBPlugin.swift @@ -616,7 +616,6 @@ final class DuckDBPluginDriver: PluginDatabaseDriver, @unchecked Sendable { if let firstRow = nativeResult.rows.first, let sql = firstRow[0].asText { var ddl = sql.hasSuffix(";") ? sql : sql + ";" - // Append index definitions let indexes = try await fetchIndexes(table: table, schema: schemaName) for index in indexes where !index.isPrimary { let uniqueStr = index.isUnique ? "UNIQUE " : "" diff --git a/Plugins/DynamoDBDriverPlugin/DynamoDBItemFlattener.swift b/Plugins/DynamoDBDriverPlugin/DynamoDBItemFlattener.swift index 39bb40203..617fc8494 100644 --- a/Plugins/DynamoDBDriverPlugin/DynamoDBItemFlattener.swift +++ b/Plugins/DynamoDBDriverPlugin/DynamoDBItemFlattener.swift @@ -32,7 +32,6 @@ struct DynamoDBItemFlattener { } } - // Collect all other attribute names across all items var remaining = Set() for item in items { for key in item.keys where !seen.contains(key) { @@ -40,7 +39,6 @@ struct DynamoDBItemFlattener { } } - // Append remaining sorted alphabetically ordered.append(contentsOf: remaining.sorted()) return ordered diff --git a/Plugins/DynamoDBDriverPlugin/DynamoDBPartiQLParser.swift b/Plugins/DynamoDBDriverPlugin/DynamoDBPartiQLParser.swift index 31c48921f..13e20550e 100644 --- a/Plugins/DynamoDBDriverPlugin/DynamoDBPartiQLParser.swift +++ b/Plugins/DynamoDBDriverPlugin/DynamoDBPartiQLParser.swift @@ -53,24 +53,20 @@ internal struct DynamoDBPartiQLParser { switch firstUpper { case "SELECT": - // Find FROM keyword and take the next token if let fromIndex = tokens.firstIndex(where: { $0.uppercased() == "FROM" }), fromIndex + 1 < tokens.count { return normalizeIdentifierToken(tokens[fromIndex + 1]) } case "INSERT": - // INSERT INTO "table" ... if tokens.count >= 3, tokens[1].uppercased() == "INTO" { return normalizeIdentifierToken(tokens[2]) } case "UPDATE": - // UPDATE "table" ... if tokens.count >= 2 { return normalizeIdentifierToken(tokens[1]) } case "DELETE": - // DELETE FROM "table" ... if tokens.count >= 3, tokens[1].uppercased() == "FROM" { return normalizeIdentifierToken(tokens[2]) } diff --git a/Plugins/DynamoDBDriverPlugin/DynamoDBPluginDriver.swift b/Plugins/DynamoDBDriverPlugin/DynamoDBPluginDriver.swift index 17423515a..30a9cda5c 100644 --- a/Plugins/DynamoDBDriverPlugin/DynamoDBPluginDriver.swift +++ b/Plugins/DynamoDBDriverPlugin/DynamoDBPluginDriver.swift @@ -119,12 +119,10 @@ internal final class DynamoDBPluginDriver: PluginDatabaseDriver, @unchecked Send ) } - // Check for tagged browsing queries if DynamoDBQueryBuilder.isTaggedQuery(trimmed) { return try await executeTaggedQuery(trimmed, conn: conn, startTime: startTime) } - // Execute as PartiQL return try await executePartiQL(trimmed, conn: conn, startTime: startTime) } @@ -137,12 +135,10 @@ internal final class DynamoDBPluginDriver: PluginDatabaseDriver, @unchecked Send let trimmed = query.trimmingCharacters(in: .whitespacesAndNewlines) - // If no parameters, fall back to regular execute guard !parameters.isEmpty else { return try await execute(query: trimmed) } - // Convert parameters to DynamoDB attribute value dictionaries let dynamoParams: [[String: Any]] = parameters.map { param -> [String: Any] in switch param { case .null: @@ -278,7 +274,6 @@ internal final class DynamoDBPluginDriver: PluginDatabaseDriver, @unchecked Send let tableDesc = try await cachedDescribeTable(table, conn: conn) var indexes: [PluginIndexInfo] = [] - // Primary key if let keySchema = tableDesc.KeySchema { let columns = keySchema.map(\.AttributeName) indexes.append(PluginIndexInfo( @@ -290,7 +285,6 @@ internal final class DynamoDBPluginDriver: PluginDatabaseDriver, @unchecked Send )) } - // Global Secondary Indexes if let gsis = tableDesc.GlobalSecondaryIndexes { for gsi in gsis { let columns = (gsi.KeySchema ?? []).map(\.AttributeName) @@ -305,7 +299,6 @@ internal final class DynamoDBPluginDriver: PluginDatabaseDriver, @unchecked Send } } - // Local Secondary Indexes if let lsis = tableDesc.LocalSecondaryIndexes { for lsi in lsis { let columns = (lsi.KeySchema ?? []).map(\.AttributeName) @@ -353,7 +346,6 @@ internal final class DynamoDBPluginDriver: PluginDatabaseDriver, @unchecked Send lines.append("ARN: \(arn)") } - // Key Schema if let keySchema = tableDesc.KeySchema { lines.append("") lines.append("Key Schema:") @@ -365,7 +357,6 @@ internal final class DynamoDBPluginDriver: PluginDatabaseDriver, @unchecked Send } } - // Attribute Definitions if let attrs = tableDesc.AttributeDefinitions { lines.append("") lines.append("Attribute Definitions:") @@ -374,7 +365,6 @@ internal final class DynamoDBPluginDriver: PluginDatabaseDriver, @unchecked Send } } - // Billing Mode let billingMode = tableDesc.BillingModeSummary?.BillingMode ?? "PROVISIONED" lines.append("") lines.append("Billing Mode: \(billingMode)") @@ -384,7 +374,6 @@ internal final class DynamoDBPluginDriver: PluginDatabaseDriver, @unchecked Send lines.append("Write Capacity: \(throughput.WriteCapacityUnits ?? 0)") } - // Item Count and Size if let itemCount = tableDesc.ItemCount { lines.append("") lines.append("Item Count: \(itemCount)") @@ -393,7 +382,6 @@ internal final class DynamoDBPluginDriver: PluginDatabaseDriver, @unchecked Send lines.append("Table Size: \(formatBytes(sizeBytes))") } - // Global Secondary Indexes if let gsis = tableDesc.GlobalSecondaryIndexes, !gsis.isEmpty { lines.append("") lines.append("Global Secondary Indexes:") @@ -407,7 +395,6 @@ internal final class DynamoDBPluginDriver: PluginDatabaseDriver, @unchecked Send } } - // Local Secondary Indexes if let lsis = tableDesc.LocalSecondaryIndexes, !lsis.isEmpty { lines.append("") lines.append("Local Secondary Indexes:") diff --git a/Plugins/EtcdDriverPlugin/EtcdPluginDriver.swift b/Plugins/EtcdDriverPlugin/EtcdPluginDriver.swift index 2ccdf799b..57efc970c 100644 --- a/Plugins/EtcdDriverPlugin/EtcdPluginDriver.swift +++ b/Plugins/EtcdDriverPlugin/EtcdPluginDriver.swift @@ -131,7 +131,6 @@ final class EtcdPluginDriver: PluginDatabaseDriver, @unchecked Sendable { ) } - // Check for tagged browsing queries if EtcdQueryBuilder.isTaggedQuery(trimmed) { return try await executeTaggedQuery(trimmed, client: client, startTime: startTime) } @@ -927,7 +926,6 @@ final class EtcdPluginDriver: PluginDatabaseDriver, @unchecked Sendable { } } - // Apply pagination let total = kvs.count guard offset < total else { return emptyResult(startTime: startTime) diff --git a/Plugins/MSSQLDriverPlugin/MSSQLPlugin.swift b/Plugins/MSSQLDriverPlugin/MSSQLPlugin.swift index 83bf5566f..a123d8301 100644 --- a/Plugins/MSSQLDriverPlugin/MSSQLPlugin.swift +++ b/Plugins/MSSQLDriverPlugin/MSSQLPlugin.swift @@ -531,7 +531,6 @@ final class MSSQLPluginDriver: PluginDatabaseDriver, @unchecked Sendable { query: query, parameters: parameters.map { $0.asText } ) - // If no placeholders were found, execute the query as-is guard !paramDecls.isEmpty else { return try await execute(query: query) } diff --git a/Plugins/RedisDriverPlugin/RedisCommandParser.swift b/Plugins/RedisDriverPlugin/RedisCommandParser.swift index 21d360a9c..11625d437 100644 --- a/Plugins/RedisDriverPlugin/RedisCommandParser.swift +++ b/Plugins/RedisDriverPlugin/RedisCommandParser.swift @@ -608,7 +608,6 @@ struct RedisCommandParser { if knownFlags.contains(upper) { flags.append(upper) if upper == "LIMIT" { - // LIMIT requires offset and count guard i + 2 < args.count else { throw RedisParseError.missingArgument("LIMIT requires offset and count") } diff --git a/Plugins/RedisDriverPlugin/RedisPluginDriver.swift b/Plugins/RedisDriverPlugin/RedisPluginDriver.swift index b3d902a53..14140cd7c 100644 --- a/Plugins/RedisDriverPlugin/RedisPluginDriver.swift +++ b/Plugins/RedisDriverPlugin/RedisPluginDriver.swift @@ -127,7 +127,6 @@ final class RedisPluginDriver: PluginDatabaseDriver, @unchecked Sendable { throw RedisPluginError.notConnected } - // Parse key counts from INFO keyspace let result = try await conn.executeCommand(["INFO", "keyspace"]) var keyCounts: [String: Int] = [:] if let info = result.stringValue { @@ -149,7 +148,6 @@ final class RedisPluginDriver: PluginDatabaseDriver, @unchecked Sendable { } } - // Get total database count from CONFIG GET databases let configResult = try await conn.executeCommand(["CONFIG", "GET", "databases"]) var maxDatabases = 16 if let array = configResult.arrayValue, array.count >= 2, let count = Int(redisReplyToString(array[1])) { diff --git a/Plugins/RedisDriverPlugin/RedisStatementGenerator.swift b/Plugins/RedisDriverPlugin/RedisStatementGenerator.swift index 5d6a95e80..fa2407440 100644 --- a/Plugins/RedisDriverPlugin/RedisStatementGenerator.swift +++ b/Plugins/RedisDriverPlugin/RedisStatementGenerator.swift @@ -65,7 +65,6 @@ struct RedisStatementGenerator { } } - // Batch deletes into a single DEL command if !deleteKeys.isEmpty { let keyList = deleteKeys.map { escapeArgument($0) }.joined(separator: " ") let cmd = "DEL \(keyList)" @@ -168,7 +167,6 @@ struct RedisStatementGenerator { var statements: [(statement: String, parameters: [PluginCellValue])] = [] - // Check for key rename if let keyChange = change.cellChanges.first(where: { $0.columnName == "Key" }), let newKey = keyChange.newValue.asText, newKey != key { let renameCmd = "RENAME \(escapeArgument(key)) \(escapeArgument(newKey))" @@ -183,7 +181,6 @@ struct RedisStatementGenerator { return key }() - // Determine the Redis type from the original row data let redisType: String? = { guard let ti = typeColumnIndex, let originalRow = change.originalRow, diff --git a/Plugins/TableProPluginKit/DriverPlugin.swift b/Plugins/TableProPluginKit/DriverPlugin.swift index 26ae86e7b..9cf396e89 100644 --- a/Plugins/TableProPluginKit/DriverPlugin.swift +++ b/Plugins/TableProPluginKit/DriverPlugin.swift @@ -59,7 +59,6 @@ public protocol DriverPlugin: TableProPlugin { static var parameterStyle: ParameterStyle { get } static var supportsDropDatabase: Bool { get } - // Schema editing granularity static var supportsAddColumn: Bool { get } static var supportsModifyColumn: Bool { get } static var supportsDropColumn: Bool { get } @@ -132,7 +131,6 @@ public extension DriverPlugin { static var postConnectActions: [PostConnectAction] { [] } static var supportsDropDatabase: Bool { false } - // Schema editing granularity static var supportsAddColumn: Bool { true } static var supportsModifyColumn: Bool { true } static var supportsDropColumn: Bool { true } diff --git a/Plugins/TableProPluginKit/PluginDatabaseDriver.swift b/Plugins/TableProPluginKit/PluginDatabaseDriver.swift index 5272c3149..10cb23b91 100644 --- a/Plugins/TableProPluginKit/PluginDatabaseDriver.swift +++ b/Plugins/TableProPluginKit/PluginDatabaseDriver.swift @@ -74,16 +74,13 @@ public struct PluginRowChange: Sendable { public protocol PluginDatabaseDriver: AnyObject, Sendable { var capabilities: PluginCapabilities { get } - // Connection func connect() async throws func disconnect() func ping() async throws - // Queries func execute(query: String) async throws -> PluginQueryResult func executeUserQuery(query: String, rowCap: Int?, parameters: [PluginCellValue]?) async throws -> PluginQueryResult - // Schema func fetchTables(schema: String?) async throws -> [PluginTableInfo] func fetchColumns(table: String, schema: String?) async throws -> [PluginColumnInfo] func fetchIndexes(table: String, schema: String?) async throws -> [PluginIndexInfo] @@ -95,19 +92,16 @@ public protocol PluginDatabaseDriver: AnyObject, Sendable { func fetchDatabases() async throws -> [String] func fetchDatabaseMetadata(_ database: String) async throws -> PluginDatabaseMetadata - // Schema navigation var supportsSchemas: Bool { get } func fetchSchemas() async throws -> [String] func switchSchema(to schema: String) async throws var currentSchema: String? { get } - // Transactions var supportsTransactions: Bool { get } func beginTransaction() async throws func commitTransaction() async throws func rollbackTransaction() async throws - // Execution control func cancelQuery() throws func applyQueryTimeout(_ seconds: Int) async throws var serverVersion: String? { get } @@ -115,7 +109,6 @@ public protocol PluginDatabaseDriver: AnyObject, Sendable { var requiresBackslashEscapingInLiterals: Bool { get } - // Batch operations func fetchApproximateRowCount(table: String, schema: String?) async throws -> Int? func fetchAllColumns(schema: String?) async throws -> [String: [PluginColumnInfo]] func fetchAllForeignKeys(schema: String?) async throws -> [String: [PluginForeignKeyInfo]] @@ -174,10 +167,8 @@ public protocol PluginDatabaseDriver: AnyObject, Sendable { // EXPLAIN query building (optional) func buildExplainQuery(_ sql: String) -> String? - // Identifier quoting func quoteIdentifier(_ name: String) -> String - // String escaping func escapeStringLiteral(_ value: String) -> String func createViewTemplate() -> String? diff --git a/Plugins/XLSXExportPlugin/XLSXWriter.swift b/Plugins/XLSXExportPlugin/XLSXWriter.swift index eb939d53c..8a1034c65 100644 --- a/Plugins/XLSXExportPlugin/XLSXWriter.swift +++ b/Plugins/XLSXExportPlugin/XLSXWriter.swift @@ -50,7 +50,6 @@ final class XLSXWriter { currentRowNumber = 0 currentSheetHasHeader = includeHeader - // Pre-cache column letters let maxCols = max(columns.count, columnLetterCache.count) if maxCols > columnLetterCache.count { for i in columnLetterCache.count..\n") d.appendUTF8("") - // Write header row if requested if includeHeader { let headerCells: [CellValue] = columns.map { .string($0) } appendRow(headerCells, isHeader: true, to: &d) diff --git a/TablePro/Core/AI/AISchemaContext.swift b/TablePro/Core/AI/AISchemaContext.swift index f921e450e..8e2ccec10 100644 --- a/TablePro/Core/AI/AISchemaContext.swift +++ b/TablePro/Core/AI/AISchemaContext.swift @@ -119,7 +119,6 @@ struct AISchemaContext { } lines.append(tableLine) - // Add columns if let columns = columnsByTable[table.name] { for column in columns { var colDesc = " - \(column.name) \(column.dataType)" @@ -132,7 +131,6 @@ struct AISchemaContext { } } - // Add foreign keys if let fks = foreignKeys[table.name], !fks.isEmpty { for fk in fks { lines.append( diff --git a/TablePro/Core/AI/Copilot/CopilotBinaryManager.swift b/TablePro/Core/AI/Copilot/CopilotBinaryManager.swift index a24553445..ae4d326ed 100644 --- a/TablePro/Core/AI/Copilot/CopilotBinaryManager.swift +++ b/TablePro/Core/AI/Copilot/CopilotBinaryManager.swift @@ -103,7 +103,6 @@ actor CopilotBinaryManager { try? FileManager.default.removeItem(at: tempTar) - // Verify extraction; try to find binary if not at expected path if !FileManager.default.fileExists(atPath: binaryExecutablePath) { let enumerator = FileManager.default.enumerator(at: baseDirectory, includingPropertiesForKeys: nil) while let fileURL = enumerator?.nextObject() as? URL { diff --git a/TablePro/Core/AI/Copilot/CopilotService.swift b/TablePro/Core/AI/Copilot/CopilotService.swift index 79f6e398e..245c28456 100644 --- a/TablePro/Core/AI/Copilot/CopilotService.swift +++ b/TablePro/Core/AI/Copilot/CopilotService.swift @@ -91,7 +91,6 @@ final class CopilotService { guard generation == serverGeneration else { return } - // Register notification handlers await client.onNotification(method: "didChangeStatus") { [weak self] data in Task { @MainActor [weak self] in self?.handleStatusNotification(data) diff --git a/TablePro/Core/AI/InlineSuggestion/AIChatInlineSource.swift b/TablePro/Core/AI/InlineSuggestion/AIChatInlineSource.swift index 0f3a23e2a..bebf0c009 100644 --- a/TablePro/Core/AI/InlineSuggestion/AIChatInlineSource.swift +++ b/TablePro/Core/AI/InlineSuggestion/AIChatInlineSource.swift @@ -90,7 +90,6 @@ final class AIChatInlineSource: InlineSuggestionSource { while result.first?.isNewline == true { result.removeFirst() } - // Strip trailing whitespace and newlines while result.last?.isWhitespace == true { result.removeLast() } diff --git a/TablePro/Core/AI/OllamaDetector.swift b/TablePro/Core/AI/OllamaDetector.swift index fa7a27d89..0badbf835 100644 --- a/TablePro/Core/AI/OllamaDetector.swift +++ b/TablePro/Core/AI/OllamaDetector.swift @@ -18,12 +18,10 @@ enum OllamaDetector { let settings = AppSettingsManager.shared.ai guard settings.enabled else { return } - // Skip if an Ollama provider already exists if settings.providers.contains(where: { $0.type == .ollama }) { return } - // Try to fetch models from local Ollama guard let models = await fetchOllamaModels(), !models.isEmpty else { return } diff --git a/TablePro/Core/Autocomplete/CompletionEngine.swift b/TablePro/Core/Autocomplete/CompletionEngine.swift index 883622f23..23cc497e8 100644 --- a/TablePro/Core/Autocomplete/CompletionEngine.swift +++ b/TablePro/Core/Autocomplete/CompletionEngine.swift @@ -128,14 +128,12 @@ final class CompletionEngine { let adjustedCursor = cursorPosition - windowOffset - // Get completions from provider (uses the potentially windowed text) let (items, context) = await provider.getCompletions( text: analysisText, cursorPosition: adjustedCursor, forcedTableReferences: forcedTableReferences ) - // Don't return empty results guard !items.isEmpty else { return nil } @@ -184,7 +182,6 @@ final class CompletionEngine { let textLength = nsText.length let radius = Self.localWindowRadius - // Raw window bounds var windowStart = max(0, cursorPosition - radius) let windowEnd = min(textLength, cursorPosition + radius) diff --git a/TablePro/Core/Autocomplete/SQLCompletionItem.swift b/TablePro/Core/Autocomplete/SQLCompletionItem.swift index d1555271a..d86a08cc6 100644 --- a/TablePro/Core/Autocomplete/SQLCompletionItem.swift +++ b/TablePro/Core/Autocomplete/SQLCompletionItem.swift @@ -10,14 +10,14 @@ import Foundation /// Category of completion item enum SQLCompletionKind: String, CaseIterable { - case keyword // SELECT, FROM, WHERE, etc. - case table // Database tables - case view // Database views - case column // Table columns - case function // SQL functions (COUNT, SUM, NOW, etc.) - case schema // Database/schema names - case alias // Table aliases - case `operator` // Operators (=, <>, LIKE, etc.) + case keyword + case table + case view + case column + case function + case schema + case alias + case `operator` case favorite // Saved SQL favorite (keyword expansion) /// SF Symbol for display @@ -69,7 +69,7 @@ enum SQLCompletionKind: String, CaseIterable { /// A single completion suggestion struct SQLCompletionItem: Identifiable, Hashable { let id: UUID - let label: String // Display text + let label: String let kind: SQLCompletionKind let insertText: String // Text to insert (may differ from label) let detail: String? // Type info, e.g., "VARCHAR(255)" @@ -247,7 +247,6 @@ extension SQLCompletionItem { if let dataType { detailParts.append(dataType) } let detail = detailParts.isEmpty ? nil : detailParts.joined(separator: " · ") - // Build documentation var docParts: [String] = [] if let tableName { docParts.append("Column from \(tableName)") } if let defaultValue { docParts.append("Default: \(defaultValue)") } diff --git a/TablePro/Core/Autocomplete/SQLCompletionProvider.swift b/TablePro/Core/Autocomplete/SQLCompletionProvider.swift index 40dfe0060..a94621f00 100644 --- a/TablePro/Core/Autocomplete/SQLCompletionProvider.swift +++ b/TablePro/Core/Autocomplete/SQLCompletionProvider.swift @@ -76,30 +76,24 @@ final class SQLCompletionProvider { cursorPosition: Int, forcedTableReferences: [TableReference]? = nil ) async -> (items: [SQLCompletionItem], context: SQLContext) { - // Analyze context var context = contextAnalyzer.analyze(query: text, cursorPosition: cursorPosition) if let forcedTableReferences { context = context.replacingTableReferences(forcedTableReferences) } - // Don't complete inside strings or comments if context.isInsideString || context.isInsideComment { return ([], context) } - // Get candidates based on context var candidates = await getCandidates(for: context) - // Filter by prefix and compute match highlight ranges if !context.prefix.isEmpty { candidates = filterByPrefix(candidates, prefix: context.prefix) populateMatchRanges(&candidates, prefix: context.prefix) } - // Rank results candidates = rankResults(candidates, prefix: context.prefix, context: context) - // Limit results let limited = Array(candidates.prefix(maxSuggestions(for: context.clauseType))) return (limited, context) @@ -155,10 +149,8 @@ final class SQLCompletionProvider { return items } - // Add items based on clause type switch context.clauseType { case .from, .join: - // Tables + schema/database names + JOIN/clause transition keywords items = await schemaProvider?.tableCompletionItems() ?? [] items += await schemaProvider?.namespaceCompletionItems() ?? [] items += filterKeywords([ @@ -170,7 +162,6 @@ final class SQLCompletionProvider { ]) case .into: - // Tables + INSERT continuation keywords items = await schemaProvider?.tableCompletionItems() ?? [] items += filterKeywords([ "VALUES", "SELECT", "SET", @@ -183,7 +174,6 @@ final class SQLCompletionProvider { case .select: if let funcName = context.currentFunction { - // Inside function arguments within SELECT context let upperFunc = funcName.uppercased() if upperFunc == "COUNT" { // COUNT() special: suggest * and DISTINCT as top items @@ -200,7 +190,6 @@ final class SQLCompletionProvider { distinctItem.sortPriority = 20 items.append(distinctItem) } - // Function-arg items: columns, functions, value keywords items += await columnItems(for: context.tableReferences) items += functionItems() items += filterKeywords(["NULL", "TRUE", "FALSE"]) @@ -208,7 +197,6 @@ final class SQLCompletionProvider { items += filterKeywords(["DISTINCT"]) } } else { - // Normal SELECT list: star wildcard + columns + functions + keywords items.append(SQLCompletionItem( label: "*", kind: .keyword, @@ -238,7 +226,6 @@ final class SQLCompletionProvider { case .on: // HP-3: ON clause — prioritize columns from joined tables items += await columnItems(for: context.tableReferences) - // Add qualified column suggestions (table.column) for join conditions for ref in context.tableReferences { let qualifier = ref.alias ?? ref.tableName let cols = await schemaProvider?.columnCompletionItems(for: ref.tableName, schema: ref.schema) ?? [] @@ -279,14 +266,12 @@ final class SQLCompletionProvider { "IS NULL", "IS NOT NULL" ]) items += functionItems() - // Clause transitions after WHERE conditions items += filterKeywords([ "ORDER BY", "GROUP BY", "HAVING", "LIMIT", "UNION", "INTERSECT", "EXCEPT" ]) case .groupBy: - // Columns + clause transitions items += await columnItems(for: context.tableReferences) items += filterKeywords([ "HAVING", "ORDER BY", "LIMIT", @@ -294,7 +279,6 @@ final class SQLCompletionProvider { ]) case .orderBy: - // Columns + sort direction + clause transitions items += await columnItems(for: context.tableReferences) items += filterKeywords([ "ASC", "DESC", "NULLS FIRST", "NULLS LAST", @@ -303,20 +287,17 @@ final class SQLCompletionProvider { ]) case .set: - // Columns for UPDATE SET clause + transition keywords if let firstTable = context.tableReferences.first { items = await schemaProvider?.columnCompletionItems(for: firstTable.tableName, schema: firstTable.schema) ?? [] } items += filterKeywords(["WHERE", "RETURNING"]) case .insertColumns: - // Columns for INSERT column list if let firstTable = context.tableReferences.first { items = await schemaProvider?.columnCompletionItems(for: firstTable.tableName, schema: firstTable.schema) ?? [] } case .values: - // Functions and keywords for VALUES + post-values transitions items = functionItems() items += filterKeywords([ "NULL", "DEFAULT", "TRUE", "FALSE", @@ -324,7 +305,6 @@ final class SQLCompletionProvider { ]) case .functionArg: - // Inside function arguments - suggest columns and other functions let isCountFunction = context.currentFunction?.uppercased() == "COUNT" if isCountFunction { // COUNT() special: suggest * as top item @@ -352,14 +332,12 @@ final class SQLCompletionProvider { } case .caseExpression: - // Inside CASE expression items += await columnItems(for: context.tableReferences) items += filterKeywords(["WHEN", "THEN", "ELSE", "END", "AND", "OR", "IS", "NULL", "TRUE", "FALSE"]) items += SQLKeywords.operatorItems() items += functionItems() case .inList: - // Inside IN (...) list - suggest values, subqueries, columns items += await columnItems(for: context.tableReferences) items += filterKeywords(["SELECT", "NULL", "TRUE", "FALSE"]) items += functionItems() @@ -369,7 +347,6 @@ final class SQLCompletionProvider { items += filterKeywords(["OFFSET", "FETCH", "NEXT", "ROWS", "ONLY"]) case .alterTable: - // After ALTER TABLE tablename - suggest DDL operations and constraint types items = filterKeywords([ "ADD", "DROP", "MODIFY", "CHANGE", "RENAME", "COLUMN", "INDEX", "PRIMARY", "FOREIGN", "KEY", @@ -379,14 +356,12 @@ final class SQLCompletionProvider { ]) case .alterTableColumn: - // After ALTER TABLE tablename DROP/MODIFY/CHANGE/RENAME or AFTER/BEFORE - suggest column names if let firstTable = context.tableReferences.first { items = await schemaProvider?.columnCompletionItems(for: firstTable.tableName, schema: firstTable.schema) ?? [] } case .createTable: if context.nestingLevel >= 1 { - // Inside CREATE TABLE (...) — column definitions // Boost FK-related keywords so they appear within the 20-item limit items = boostedKeywords([ "REFERENCES", "ON DELETE", "ON UPDATE", @@ -411,7 +386,6 @@ final class SQLCompletionProvider { } case .columnDef: - // Typing column data type (after ADD COLUMN name) items = dataTypeKeywords() items += filterKeywords([ "NOT", "NULL", "DEFAULT", "AUTO_INCREMENT", "SERIAL", @@ -422,20 +396,16 @@ final class SQLCompletionProvider { ]) case .returning: - // After RETURNING (PostgreSQL) - suggest columns items += await columnItems(for: context.tableReferences) items += filterKeywords(["*"]) case .union: - // After UNION/INTERSECT/EXCEPT - suggest SELECT items = filterKeywords(["SELECT", "ALL"]) case .using: - // After USING in JOIN - suggest columns items += await columnItems(for: context.tableReferences) case .window: - // After OVER/PARTITION BY - suggest columns and window keywords items += await columnItems(for: context.tableReferences) items += filterKeywords([ "PARTITION BY", "ORDER BY", "ASC", "DESC", @@ -444,23 +414,19 @@ final class SQLCompletionProvider { ]) case .dropObject: - // After DROP TABLE/INDEX/VIEW - suggest tables items = await schemaProvider?.tableCompletionItems() ?? [] items += filterKeywords(["IF EXISTS", "CASCADE", "RESTRICT"]) case .createIndex: if context.tableReferences.isEmpty { - // Before ON tablename — suggest tables and ON keyword items = await schemaProvider?.tableCompletionItems() ?? [] items += filterKeywords(["ON"]) } else { - // After ON tablename (inside parens) — suggest columns items = await columnItems(for: context.tableReferences) items += filterKeywords(["USING", "BTREE", "HASH", "GIN", "GIST"]) } case .createView: - // After CREATE VIEW - suggest SELECT items = filterKeywords(["SELECT", "AS"]) items += await schemaProvider?.tableCompletionItems() ?? [] @@ -579,12 +545,10 @@ final class SQLCompletionProvider { let lowerPrefix = prefix.lowercased() return items.filter { item in - // Exact prefix match if item.filterText.hasPrefix(lowerPrefix) { return true } - // Contains match if item.filterText.contains(lowerPrefix) { return true } @@ -751,12 +715,10 @@ final class SQLCompletionProvider { func calculateScore(for item: SQLCompletionItem, prefix: String, context: SQLContext) -> Int { var score = item.sortPriority - // Exact prefix match bonus if item.filterText.hasPrefix(prefix) { score -= 500 } - // Exact match bonus if item.filterText == prefix { score -= 1_000 } diff --git a/TablePro/Core/Autocomplete/SQLContextAnalyzer.swift b/TablePro/Core/Autocomplete/SQLContextAnalyzer.swift index 499e73c19..6c4df11b7 100644 --- a/TablePro/Core/Autocomplete/SQLContextAnalyzer.swift +++ b/TablePro/Core/Autocomplete/SQLContextAnalyzer.swift @@ -87,7 +87,6 @@ struct SQLContext { let isInsideString: Bool // Inside a string literal let isInsideComment: Bool // Inside a comment - // Enhanced context for smarter completions let cteNames: [String] // Common Table Expression names in scope let nestingLevel: Int // Subquery nesting level (0 = main query) let currentFunction: String? // If inside function args, the function name @@ -191,21 +190,15 @@ final class SQLContextAnalyzer { ("\\bCREATE\\s+TABLE\\s+[^(]*\\([^)]*$", .createTable), ("\\bCREATE\\s+(?:TEMPORARY\\s+)?TABLE\\s+[^;]*\\([^)]*\\)\\s*\\w*$", .createTable), ("\\bCREATE\\s+(?:TEMPORARY\\s+)?TABLE\\s+(?:IF\\s+NOT\\s+EXISTS\\s+)?\\w*$", .createTable), - // DROP object patterns ("\\bDROP\\s+(?:TABLE|VIEW|INDEX)\\s+(?:IF\\s+EXISTS\\s+)?\\w*$", .dropObject), - // CREATE INDEX pattern ("\\bCREATE\\s+(?:UNIQUE\\s+)?INDEX\\s+\\w+\\s+ON\\s+\\w+\\s*\\([^)]*$", .createIndex), ("\\bCREATE\\s+(?:UNIQUE\\s+)?INDEX\\s+\\w*$", .createIndex), - // CREATE VIEW pattern ("\\bCREATE\\s+(?:OR\\s+REPLACE\\s+)?(?:MATERIALIZED\\s+)?VIEW\\s+\\w+\\s+AS\\s+[^;]*$", .createView), ("\\bCREATE\\s+(?:OR\\s+REPLACE\\s+)?(?:MATERIALIZED\\s+)?VIEW\\s+\\w*$", .createView), - // USING clause in JOIN ("\\bUSING\\s*\\([^)]*$", .using), - // Window function OVER clause ("\\bOVER\\s*\\([^)]*$", .window), ("\\bPARTITION\\s+BY\\s+[^)]*$", .window), - // Parenthesis-bounded value/list contexts ("\\bIN\\s*\\([^)]*$", .inList), ("\\b(LIMIT|OFFSET)\\s+\\d*$", .limit), ("\\bVALUES\\s*(?:\\([^)]*\\)\\s*,?\\s*)+\\w*$", .values), @@ -287,7 +280,6 @@ final class SQLContextAnalyzer { let clampedPosition = max(0, min(adjustedPosition, nsStatement.length)) let textBeforeCursor = nsStatement.substring(to: clampedPosition) - // Check if inside string or comment if isInsideString(textBeforeCursor) { return SQLContext( clauseType: .unknown, @@ -312,13 +304,10 @@ final class SQLContextAnalyzer { ) } - // Extract prefix and dot prefix let (prefix, prefixStart, dotPrefix) = extractPrefix(from: textBeforeCursor) - // Find all table references in the current statement var tableReferences = extractTableReferences(from: currentStatement) - // Extract CTEs from the current statement let cteNames = extractCTENames(from: currentStatement) // Resolve derived tables (FROM/JOIN subqueries and CTEs) to their @@ -328,7 +317,6 @@ final class SQLContextAnalyzer { var seenReferences = Set(tableReferences) - // Extract ALTER TABLE table name and add to references if let alterTableName = extractAlterTableName(from: currentStatement) { let alterRef = TableReference(tableName: alterTableName, alias: nil) if seenReferences.insert(alterRef).inserted { @@ -336,13 +324,10 @@ final class SQLContextAnalyzer { } } - // Calculate nesting level (subquery depth) let nestingLevel = calculateNestingLevel(in: textBeforeCursor) - // Detect function context let currentFunction = detectFunctionContext(in: textBeforeCursor) - // Check if immediately after comma let isAfterComma = checkIfAfterComma(textBeforeCursor) // Clause detection runs on the text BEFORE the token being typed: the @@ -361,7 +346,6 @@ final class SQLContextAnalyzer { clauseText = textBeforePrefix } - // Determine clause type let resolution = determineClauseType( textBeforeCursor: clauseText, dotPrefix: dotPrefix, @@ -391,7 +375,6 @@ final class SQLContextAnalyzer { var cteNames: [String] = [] let nsRange = NSRange(location: 0, length: (query as NSString).length) - // Find first CTE (uses pre-compiled static regex) if let match = Self.cteFirstRegex.firstMatch(in: query, range: nsRange) { let nameNSRange = match.range(at: 1) if nameNSRange.location != NSNotFound { @@ -399,7 +382,6 @@ final class SQLContextAnalyzer { } } - // Find additional CTEs (comma-separated, uses pre-compiled static regex) Self.cteCommaRegex.enumerateMatches(in: query, range: nsRange) { match, _, _ in if let match = match { let nameNSRange = match.range(at: 1) @@ -557,7 +539,6 @@ final class SQLContextAnalyzer { lastWord = ns.substring(with: NSRange(location: wordStart, length: length - wordStart)) } - // If we're inside parentheses, check if it's a function call if let lastParen = parenStack.last, let funcName = lastParen.precedingWord { let upperFunc = funcName.uppercased() @@ -588,7 +569,6 @@ final class SQLContextAnalyzer { private func checkIfAfterComma(_ text: String) -> Bool { let ns = text as NSString let length = ns.length - // Scan backwards past whitespace var i = length - 1 while i >= 0 { let ch = ns.character(at: i) @@ -929,7 +909,6 @@ final class SQLContextAnalyzer { windowedText = textBeforeCursor } - // Remove string literals and comments for analysis let cleaned = removeStringsAndComments(from: windowedText) // Structural / DDL contexts FIRST. These are anchored to a leading keyword @@ -1132,8 +1111,6 @@ final class SQLContextAnalyzer { for match in matches.reversed() { let matchRange = match.range let matched = ns.substring(with: matchRange) - // Single-quoted strings -> empty quotes; double-quoted strings -> empty quotes - // Block comments and line comments -> removed entirely if matched.hasPrefix("'") { mutable.replaceCharacters(in: matchRange, with: "''") } else if matched.hasPrefix("\"") { diff --git a/TablePro/Core/Autocomplete/SQLSchemaProvider.swift b/TablePro/Core/Autocomplete/SQLSchemaProvider.swift index d6907b29d..1dbfcbee2 100644 --- a/TablePro/Core/Autocomplete/SQLSchemaProvider.swift +++ b/TablePro/Core/Autocomplete/SQLSchemaProvider.swift @@ -235,21 +235,18 @@ actor SQLSchemaProvider { func resolveAlias(_ aliasOrName: String, in references: [TableReference]) -> String? { let lowerName = aliasOrName.lowercased() - // First check if it's an alias for ref in references { if ref.alias?.lowercased() == lowerName { return ref.tableName } } - // Then check if it's a table name directly for ref in references { if ref.tableName.lowercased() == lowerName { return ref.tableName } } - // Finally check against known tables for table in tables { if table.name.lowercased() == lowerName { return table.name diff --git a/TablePro/Core/ChangeTracking/SQLStatementGenerator.swift b/TablePro/Core/ChangeTracking/SQLStatementGenerator.swift index 32cf563fc..b872ea932 100644 --- a/TablePro/Core/ChangeTracking/SQLStatementGenerator.swift +++ b/TablePro/Core/ChangeTracking/SQLStatementGenerator.swift @@ -104,11 +104,9 @@ struct SQLStatementGenerator { } } - // Generate DELETE statements // Try batched DELETE first (uses PK if available), fall back to individual DELETEs if !deleteChanges.isEmpty { if let stmt = generateBatchDeleteSQL(for: deleteChanges) { - // Batched delete successful (has PK) statements.append(stmt) } else { // No PK - generate individual DELETE statements matching all columns @@ -259,12 +257,6 @@ struct SQLStatementGenerator { return ParameterizedStatement(sql: sql, parameters: parameters) } - /// Marker type for SQL function literals that cannot be parameterized - private struct SQLFunctionLiteral { - let value: String - init(_ value: String) { self.value = value } - } - // MARK: - UPDATE Generation func generateUpdateSQL(for change: RowChange) -> ParameterizedStatement? { diff --git a/TablePro/Core/Database/DatabaseDriver.swift b/TablePro/Core/Database/DatabaseDriver.swift index 609ea43c8..ec4b06ec1 100644 --- a/TablePro/Core/Database/DatabaseDriver.swift +++ b/TablePro/Core/Database/DatabaseDriver.swift @@ -230,16 +230,11 @@ extension DatabaseDriver { var queryBuildingPluginDriver: (any PluginDatabaseDriver)? { nil } func quoteIdentifier(_ name: String) -> String { - let q = "\"" - let escaped = name.replacingOccurrences(of: q, with: q + q) - return "\(q)\(escaped)\(q)" + SQLEscaping.quoteIdentifier(name) } func escapeStringLiteral(_ value: String) -> String { - var result = value - result = result.replacingOccurrences(of: "'", with: "''") - result = result.replacingOccurrences(of: "\0", with: "") - return result + SQLEscaping.escapeStringLiteral(value) } func createViewTemplate() -> String? { nil } @@ -410,7 +405,6 @@ extension DatabaseDriver { var supportsTransactions: Bool { true } func cancelQuery() throws { - // No-op by default } /// Default timeout implementation — delegates to each plugin's PluginDatabaseDriver. diff --git a/TablePro/Core/Database/DatabaseManager+Health.swift b/TablePro/Core/Database/DatabaseManager+Health.swift index b4ed9241c..ccec16033 100644 --- a/TablePro/Core/Database/DatabaseManager+Health.swift +++ b/TablePro/Core/Database/DatabaseManager+Health.swift @@ -17,7 +17,6 @@ extension DatabaseManager { /// Start health monitoring for a connection internal func startHealthMonitor(for connectionId: UUID) async { Self.logger.info("startHealthMonitor called for \(connectionId) (existing monitors: \(self.healthMonitors.count))") - // Stop any existing monitor await stopHealthMonitor(for: connectionId) let monitor = ConnectionHealthMonitor( @@ -112,7 +111,6 @@ extension DatabaseManager { /// Creates a fresh driver, connects, and applies timeout for the given session. /// For SSH-tunneled sessions, rebuilds the tunnel before connecting the driver. internal func reconnectDriver(for session: ConnectionSession) async throws -> ReconnectResult { - // Disconnect existing driver session.driver?.disconnect() // Rebuild the tunnel if needed; otherwise reuse effective connection @@ -228,7 +226,6 @@ extension DatabaseManager { Self.logger.info("Manual reconnect requested for: \(session.connection.name)") - // Update status to connecting updateSession(sessionId) { session in session.status = .connecting } @@ -236,7 +233,6 @@ extension DatabaseManager { await SchemaService.shared.invalidate(connectionId: sessionId) await DatabaseTreeMetadataService.shared.handleReconnect(connectionId: sessionId) - // Stop existing health monitor await stopHealthMonitor(for: sessionId) do { @@ -264,7 +260,6 @@ extension DatabaseManager { passwordOverride = prompted } - // Create new driver and connect let driver = try await DatabaseDriverFactory.createDriver( for: effectiveConnection, passwordOverride: passwordOverride, @@ -283,7 +278,6 @@ extension DatabaseManager { savedDatabase: databaseSwitchRequiresReconnect(session.connection) ? nil : activeSessions[sessionId]?.currentDatabase ) - // Update session updateSession(sessionId) { session in session.driver = driver session.status = .connected diff --git a/TablePro/Core/Database/DatabaseManager+Schema.swift b/TablePro/Core/Database/DatabaseManager+Schema.swift index 76c365d13..d563fa104 100644 --- a/TablePro/Core/Database/DatabaseManager+Schema.swift +++ b/TablePro/Core/Database/DatabaseManager+Schema.swift @@ -97,7 +97,6 @@ extension DatabaseManager { try await driver.commitTransaction() } - // Record each statement in query history let connId = connectionId let dbName = self.activeSessions[connectionId]?.activeDatabase ?? "" for stmt in statements { @@ -148,7 +147,6 @@ extension DatabaseManager { return nil } - // Query the actual constraint name from pg_constraint let escapedTable = tableName.replacingOccurrences(of: "'", with: "''") let schema: String if let schemaDriver = driver as? SchemaSwitchable, diff --git a/TablePro/Core/Database/FilterSQLGenerator.swift b/TablePro/Core/Database/FilterSQLGenerator.swift index 448e632ed..db26bdf26 100644 --- a/TablePro/Core/Database/FilterSQLGenerator.swift +++ b/TablePro/Core/Database/FilterSQLGenerator.swift @@ -213,7 +213,6 @@ struct FilterSQLGenerator { return "NULL" } - // Check for boolean literals if trimmed.caseInsensitiveCompare("TRUE") == .orderedSame { return dialect.booleanLiteralStyle == .truefalse ? "TRUE" : "1" } @@ -221,12 +220,10 @@ struct FilterSQLGenerator { return dialect.booleanLiteralStyle == .truefalse ? "FALSE" : "0" } - // Try to detect numeric values if Int(trimmed) != nil || Double(trimmed) != nil { return trimmed } - // String value - escape and quote return "'\(escapeStringValue(trimmed))'" } diff --git a/TablePro/Core/Database/SQLEscaping.swift b/TablePro/Core/Database/SQLEscaping.swift index 48c9a6b9d..774404ad8 100644 --- a/TablePro/Core/Database/SQLEscaping.swift +++ b/TablePro/Core/Database/SQLEscaping.swift @@ -25,6 +25,13 @@ enum SQLEscaping { return result } + /// Quote a SQL identifier using ANSI double-quote rules, doubling any embedded quote. + static func quoteIdentifier(_ identifier: String) -> String { + let quote = "\"" + let escaped = identifier.replacingOccurrences(of: quote, with: quote + quote) + return "\(quote)\(escaped)\(quote)" + } + /// Known SQL temporal function expressions that should not be quoted/parameterized. /// Canonical source — used by SQLStatementGenerator and sidebar save logic. static let temporalFunctionExpressions: Set = [ diff --git a/TablePro/Core/MCP/MCPAuditLogStorage.swift b/TablePro/Core/MCP/MCPAuditLogStorage.swift index c31d3b2af..ce6d951ae 100644 --- a/TablePro/Core/MCP/MCPAuditLogStorage.swift +++ b/TablePro/Core/MCP/MCPAuditLogStorage.swift @@ -19,12 +19,6 @@ actor MCPAuditLogStorage { private var dbPath: String? private let testDatabaseSuffix: String? - enum TimeRange: Equatable { - case lastHours(Int) - case lastDays(Int) - case all - } - init() { self.testDatabaseSuffix = nil setupDatabase() diff --git a/TablePro/Core/MCP/TokenPermissionFilter.swift b/TablePro/Core/MCP/TokenPermissionFilter.swift deleted file mode 100644 index 8c42600e6..000000000 --- a/TablePro/Core/MCP/TokenPermissionFilter.swift +++ /dev/null @@ -1,47 +0,0 @@ -import Foundation - -protocol ConnectionIdentifiable { - var connectionId: UUID { get } -} - -enum TokenPermissionFilter { - static let overfetchMultiplier = 3 - private static let maxRoundTrips = 2 - - static func filter(_ items: [T], by access: ConnectionAccess) -> [T] { - switch access { - case .all: - return items - case .limited(let ids): - return items.filter { ids.contains($0.connectionId) } - } - } - - static func fetchFiltered( - access: ConnectionAccess, - limit: Int, - fetch: (Int, Int) async throws -> [T] - ) async throws -> [T] { - if case .all = access { - let items = try await fetch(limit, 0) - return Array(items.prefix(limit)) - } - - guard limit > 0 else { return [] } - - let fetchLimit = limit * overfetchMultiplier - var collected: [T] = [] - var offset = 0 - - for _ in 0..= limit { break } - if raw.count < fetchLimit { break } - offset += fetchLimit - } - - return Array(collected.prefix(limit)) - } -} diff --git a/TablePro/Core/Plugins/PluginManager+Registration.swift b/TablePro/Core/Plugins/PluginManager+Registration.swift index aafb39862..7f4b9e679 100644 --- a/TablePro/Core/Plugins/PluginManager+Registration.swift +++ b/TablePro/Core/Plugins/PluginManager+Registration.swift @@ -34,7 +34,6 @@ extension PluginManager { driverPlugins[additionalId] = driver } - // Self-register plugin metadata from the DriverPlugin protocol. let snapshot = PluginMetadataRegistry.shared.buildMetadataSnapshot( from: driverType, isDownloadable: driverType.isDownloadable diff --git a/TablePro/Core/Plugins/QueryResultExportDataSource.swift b/TablePro/Core/Plugins/QueryResultExportDataSource.swift index ca3c22387..8d29a0686 100644 --- a/TablePro/Core/Plugins/QueryResultExportDataSource.swift +++ b/TablePro/Core/Plugins/QueryResultExportDataSource.swift @@ -50,14 +50,14 @@ final class QueryResultExportDataSource: PluginExportDataSource, @unchecked Send if let driver { return driver.quoteIdentifier(identifier) } - return "\"\(identifier.replacingOccurrences(of: "\"", with: "\"\""))\"" + return SQLEscaping.quoteIdentifier(identifier) } func escapeStringLiteral(_ value: String) -> String { if let driver { return driver.escapeStringLiteral(value) } - return value.replacingOccurrences(of: "'", with: "''") + return SQLEscaping.escapeStringLiteral(value) } func fetchTableDDL(table: String, databaseName: String) async throws -> String { diff --git a/TablePro/Core/Plugins/Registry/RegistryClient.swift b/TablePro/Core/Plugins/Registry/RegistryClient.swift index dbbf5f91b..3fdda1822 100644 --- a/TablePro/Core/Plugins/Registry/RegistryClient.swift +++ b/TablePro/Core/Plugins/Registry/RegistryClient.swift @@ -106,7 +106,6 @@ final class RegistryClient { func fetchManifest(forceRefresh: Bool = false) async { fetchState = .loading - // Invalidate ETag cache when registry URL changes let currentURL = registryURL.absoluteString let lastURL = UserDefaults.standard.string(forKey: Self.lastRegistryURLKey) if currentURL != lastURL { diff --git a/TablePro/Core/SSH/Auth/AgentAuthenticator.swift b/TablePro/Core/SSH/Auth/AgentAuthenticator.swift index d1b85489f..df12cc4d8 100644 --- a/TablePro/Core/SSH/Auth/AgentAuthenticator.swift +++ b/TablePro/Core/SSH/Auth/AgentAuthenticator.swift @@ -80,7 +80,6 @@ internal struct AgentAuthenticator: SSHAuthenticator { throw SSHTunnelError.tunnelCreationFailed("Failed to list SSH agent identities") } - // Iterate through available identities and try each var previousIdentity: UnsafeMutablePointer? var currentIdentity: UnsafeMutablePointer? diff --git a/TablePro/Core/SSH/HostKeyStore.swift b/TablePro/Core/SSH/HostKeyStore.swift index 83d41ef37..1882e5476 100644 --- a/TablePro/Core/SSH/HostKeyStore.swift +++ b/TablePro/Core/SSH/HostKeyStore.swift @@ -88,7 +88,6 @@ internal final class HostKeyStore: @unchecked Sendable { let hostKey = hostIdentifier(hostname, port) var entries = loadEntries() - // Remove existing entry for this host and key type if present entries.removeAll { $0.host == hostKey && $0.keyType == keyType } entries.append((host: hostKey, keyType: keyType, keyData: key)) diff --git a/TablePro/Core/SSH/LibSSH2TunnelFactory.swift b/TablePro/Core/SSH/LibSSH2TunnelFactory.swift index 0c0627d41..af6295ad2 100644 --- a/TablePro/Core/SSH/LibSSH2TunnelFactory.swift +++ b/TablePro/Core/SSH/LibSSH2TunnelFactory.swift @@ -48,7 +48,6 @@ internal enum LibSSH2TunnelFactory { ) do { - // Bind local listening socket let listenFD = try bindListenSocket(port: localPort) let tunnel = LibSSH2Tunnel( @@ -292,7 +291,6 @@ internal enum LibSSH2TunnelFactory { /// Clean up all resources in an authenticated chain. private static func cleanupChain(_ chain: AuthenticatedChain, reason: String) { - // Disconnect the final session tablepro_libssh2_session_disconnect(chain.session, reason) libssh2_session_free(chain.session) if chain.socketFD != chain.initialSocketFD { @@ -334,7 +332,6 @@ internal enum LibSSH2TunnelFactory { } defer { freeaddrinfo(result) } - // Iterate through all addresses returned by getaddrinfo var currentAddr: UnsafeMutablePointer? = firstAddr var lastError: String = "No address found" diff --git a/TablePro/Core/SSH/SSHTunnelManager.swift b/TablePro/Core/SSH/SSHTunnelManager.swift index 4b5159024..948f34283 100644 --- a/TablePro/Core/SSH/SSHTunnelManager.swift +++ b/TablePro/Core/SSH/SSHTunnelManager.swift @@ -98,7 +98,6 @@ actor SSHTunnelManager { totpDigits: Int = 6, totpPeriod: Int = 30 ) async throws -> Int { - // Close existing tunnel if any if tunnels[connectionId] != nil { try await closeTunnel(connectionId: connectionId) } diff --git a/TablePro/Core/SSH/TOTP/Base32.swift b/TablePro/Core/SSH/TOTP/Base32.swift index 1828b9961..75e8543e7 100644 --- a/TablePro/Core/SSH/TOTP/Base32.swift +++ b/TablePro/Core/SSH/TOTP/Base32.swift @@ -13,7 +13,6 @@ internal enum Base32 { for (index, char) in alphabet.enumerated() { let asciiValue = Int(char.asciiValue ?? 0) table[asciiValue] = UInt8(index) - // Lowercase mapping if let lower = Character(char.lowercased()).asciiValue { table[Int(lower)] = UInt8(index) } @@ -25,7 +24,6 @@ internal enum Base32 { /// - Parameter string: Base32-encoded string (case-insensitive, padding optional) /// - Returns: Decoded data, or nil if invalid static func decode(_ string: String) -> Data? { - // Strip whitespace, dashes, and padding let cleaned = string.filter { char in char != " " && char != "-" && char != "=" && char != "\n" && char != "\r" && char != "\t" } diff --git a/TablePro/Core/SSH/TOTP/TOTPGenerator.swift b/TablePro/Core/SSH/TOTP/TOTPGenerator.swift index 3168d1686..38dcabd35 100644 --- a/TablePro/Core/SSH/TOTP/TOTPGenerator.swift +++ b/TablePro/Core/SSH/TOTP/TOTPGenerator.swift @@ -32,7 +32,6 @@ internal struct TOTPGenerator { var bigEndianCounter = counter.bigEndian let counterData = Data(bytes: &bigEndianCounter, count: 8) - // Compute HMAC let hmac = computeHmac(key: secret, message: counterData) // Dynamic truncation @@ -42,7 +41,6 @@ internal struct TOTPGenerator { | UInt32(hmac[offset + 2]) << 8 | UInt32(hmac[offset + 3]) - // Modulo and zero-pad var divisor: UInt32 = 1 for _ in 0.. 1 } @@ -463,7 +456,6 @@ final class StructureChangeManager: ChangeManaging { } } - // Validate index columns exist for index in workingIndexes.filter({ $0.isValid }) { for columnName in index.columns { if !columnNames.contains(columnName) { @@ -472,7 +464,6 @@ final class StructureChangeManager: ChangeManaging { } } - // Validate foreign key columns exist for fk in workingForeignKeys.filter({ $0.isValid }) { for columnName in fk.columns { if !columnNames.contains(columnName) { @@ -481,7 +472,6 @@ final class StructureChangeManager: ChangeManaging { } } - // Validate primary key columns exist for columnName in workingPrimaryKey { if !columnNames.contains(columnName) { validationErrors[.primaryKey] = "Primary key references non-existent column: \(columnName)" diff --git a/TablePro/Core/Services/ColumnTypeClassifier.swift b/TablePro/Core/Services/ColumnTypeClassifier.swift index 0e204b586..a13a8db5e 100644 --- a/TablePro/Core/Services/ColumnTypeClassifier.swift +++ b/TablePro/Core/Services/ColumnTypeClassifier.swift @@ -95,12 +95,10 @@ struct ColumnTypeClassifier { private static let typeLookup: [String: (String) -> ColumnType] = { var map: [String: (String) -> ColumnType] = [:] - // Boolean for key in ["BOOL", "BOOLEAN", "BIT"] { map[key] = { .boolean(rawType: $0) } } - // Integer for key in [ "INT", "INTEGER", "BIGINT", "SMALLINT", "TINYINT", "MEDIUMINT", "SERIAL", "BIGSERIAL", "SMALLSERIAL", @@ -112,7 +110,6 @@ struct ColumnTypeClassifier { map[key] = { .integer(rawType: $0) } } - // Decimal for key in [ "FLOAT", "DOUBLE", "DECIMAL", "NUMERIC", "REAL", "NUMBER", "MONEY", "SMALLMONEY", @@ -124,12 +121,10 @@ struct ColumnTypeClassifier { map[key] = { .decimal(rawType: $0) } } - // Date for key in ["DATE", "DATE32"] { map[key] = { .date(rawType: $0) } } - // Timestamp for key in [ "TIMESTAMP", "TIMESTAMPTZ", "TIMESTAMP_TZ", "TIMESTAMP_NTZ", "TIMESTAMP_S", "TIMESTAMP_MS", "TIMESTAMP_NS", @@ -138,7 +133,6 @@ struct ColumnTypeClassifier { map[key] = { .timestamp(rawType: $0) } } - // Datetime for key in [ "DATETIME", "DATETIME2", "DATETIME64", "DATETIMEOFFSET", "SMALLDATETIME" @@ -146,12 +140,10 @@ struct ColumnTypeClassifier { map[key] = { .datetime(rawType: $0) } } - // JSON for key in ["JSON", "JSONB"] { map[key] = { .json(rawType: $0) } } - // Blob for key in [ "BLOB", "BYTEA", "BINARY", "VARBINARY", "RAW", "IMAGE", "TINYBLOB", "MEDIUMBLOB", "LONGBLOB" @@ -159,15 +151,12 @@ struct ColumnTypeClassifier { map[key] = { .blob(rawType: $0) } } - // Enum for key in ["ENUM", "ENUM8", "ENUM16"] { map[key] = { .enumType(rawType: $0, values: nil) } } - // Set map["SET"] = { .set(rawType: $0, values: nil) } - // Spatial for key in [ "GEOMETRY", "POINT", "LINESTRING", "POLYGON", "MULTIPOINT", "MULTILINESTRING", "MULTIPOLYGON", diff --git a/TablePro/Core/Services/Export/ConnectionExportService.swift b/TablePro/Core/Services/Export/ConnectionExportService.swift index f859c1aaf..401a68430 100644 --- a/TablePro/Core/Services/Export/ConnectionExportService.swift +++ b/TablePro/Core/Services/Export/ConnectionExportService.swift @@ -106,7 +106,6 @@ enum ConnectionExportService { sshConfig = connection.sshConfig } - // Resolve tag name let tagName: String? if let tagId = connection.tagId { tagName = TagStorage.shared.tag(for: tagId)?.name @@ -114,7 +113,6 @@ enum ConnectionExportService { tagName = nil } - // Resolve group name let groupName: String? if let groupId = connection.groupId { groupName = GroupStorage.shared.group(for: groupId)?.name @@ -165,13 +163,10 @@ enum ConnectionExportService { exportableSSL = nil } - // Color let color: String? = connection.color == .none ? nil : connection.color.rawValue - // Safe mode level let safeModeLevel: String? = connection.safeModeLevel == .silent ? nil : connection.safeModeLevel.rawValue - // AI policy let aiPolicy: String? = connection.aiPolicy?.rawValue // Filter secure fields from additionalFields @@ -213,7 +208,6 @@ enum ConnectionExportService { exportableConnections.append(exportable) - // Collect unique group/tag names if let name = tagName { tagNames.insert(name) } if let name = groupName { groupNames.insert(name) } } @@ -466,7 +460,6 @@ enum ConnectionExportService { return ImportItem(connection: exportable, status: .duplicate(existing: duplicate)) } - // Check for warnings var warnings: [String] = [] // SSH key path check diff --git a/TablePro/Core/Services/Export/ExportService.swift b/TablePro/Core/Services/Export/ExportService.swift index 4c324104e..4f9afa53d 100644 --- a/TablePro/Core/Services/Export/ExportService.swift +++ b/TablePro/Core/Services/Export/ExportService.swift @@ -101,7 +101,6 @@ final class ExportService { throw ExportError.formatNotFound(config.formatId) } - // Reset state state = ExportState(isExporting: true, totalTables: tables.count) isCancelled = false @@ -116,18 +115,14 @@ final class ExportService { throw ExportError.notConnected } - // Fetch total row counts state.totalRows = await fetchTotalRowCount(for: tables, driver: driver) - // Create data source adapter let dataSource = ExportDataSourceAdapter(driver: driver, databaseType: databaseType) - // Create progress tracker let nsProgress = Progress(totalUnitCount: Int64(state.totalRows)) let progress = PluginExportProgress(progress: nsProgress) currentProgress = progress - // Observe NSProgress for UI updates let observation = nsProgress.observe(\.completedUnitCount) { [weak self] observed, _ in let count = Int(observed.completedUnitCount) Task { @MainActor [weak self] in @@ -148,7 +143,6 @@ final class ExportService { } defer { descObservation.invalidate() } - // Convert ExportTableItems to PluginExportTables let pluginTables = tables.map { table in PluginExportTable( name: table.name, diff --git a/TablePro/Core/Services/Export/ForeignApp/DBeaverImporter.swift b/TablePro/Core/Services/Export/ForeignApp/DBeaverImporter.swift index 1a8863622..c78377018 100644 --- a/TablePro/Core/Services/Export/ForeignApp/DBeaverImporter.swift +++ b/TablePro/Core/Services/Export/ForeignApp/DBeaverImporter.swift @@ -197,7 +197,6 @@ struct DBeaverImporter: ForeignAppImporter { if let folderInfo = folders[path], let desc = folderInfo["description"] as? String, !desc.isEmpty { groupName = desc } else { - // Use last component of folder path as group name groupName = path.components(separatedBy: "/").last } } else { @@ -236,7 +235,6 @@ struct DBeaverImporter: ForeignAppImporter { let properties = sshTunnel["properties"] as? [String: Any] ?? [:] - // Check if the handler is enabled let enabled = sshTunnel["enabled"] as? Bool ?? (properties["host"] != nil) guard enabled else { return nil } @@ -309,7 +307,6 @@ struct DBeaverImporter: ForeignAppImporter { private func parseColor(_ config: [String: Any]) -> String? { guard let colorString = config["color"] as? String, !colorString.isEmpty else { return nil } // DBeaver stores colors as comma-separated RGB values like "255,0,0" - // Map common colors to our color names let components = colorString.components(separatedBy: ",").compactMap { Int($0.trimmingCharacters(in: .whitespaces)) } guard components.count >= 3 else { return nil } let (r, g, b) = (components[0], components[1], components[2]) diff --git a/TablePro/Core/Services/Export/ImportService.swift b/TablePro/Core/Services/Export/ImportService.swift index 1271c083f..00edec5e4 100644 --- a/TablePro/Core/Services/Export/ImportService.swift +++ b/TablePro/Core/Services/Export/ImportService.swift @@ -64,7 +64,6 @@ final class ImportService { throw DatabaseError.notConnected } - // Reset state state = ImportState(isImporting: true) defer { state.isImporting = false @@ -93,7 +92,6 @@ final class ImportService { } defer { source.cleanup() } - // Create progress tracker let initialTotal = Int64(knownStatementCount ?? 0) let nsProgress = Progress(totalUnitCount: initialTotal) let progress = PluginImportProgress(progress: nsProgress) @@ -135,7 +133,6 @@ final class ImportService { } catch { state.errorMessage = error.localizedDescription - // Record failed import history QueryHistoryManager.shared.recordQuery( query: "-- Import from \(url.lastPathComponent) (\(progress.processedStatements) statements before failure)", connectionId: connection.id, @@ -149,13 +146,11 @@ final class ImportService { throw error } - // Update final state state.processedStatements = result.executedStatements state.skippedStatements = result.skippedStatements state.estimatedTotalStatements = result.executedStatements + result.skippedStatements state.progress = 1.0 - // Record success history QueryHistoryManager.shared.recordQuery( query: "-- Import from \(url.lastPathComponent) (\(result.executedStatements) statements)", connectionId: connection.id, diff --git a/TablePro/Core/Services/Formatting/DateFormattingService.swift b/TablePro/Core/Services/Formatting/DateFormattingService.swift index 9f16de6eb..1bb21b2ae 100644 --- a/TablePro/Core/Services/Formatting/DateFormattingService.swift +++ b/TablePro/Core/Services/Formatting/DateFormattingService.swift @@ -36,7 +36,6 @@ final class DateFormattingService { // MARK: - Initialization private init() { - // Initialize with default format (ISO 8601) // Will be updated by AppSettingsManager after it completes initialization self.currentFormat = .iso8601 self.formatter = Self.createFormatter(format: DateFormatOption.iso8601.formatString) diff --git a/TablePro/Core/Services/Formatting/SQLFormatterService.swift b/TablePro/Core/Services/Formatting/SQLFormatterService.swift index 0fd59c845..3b454a9b8 100644 --- a/TablePro/Core/Services/Formatting/SQLFormatterService.swift +++ b/TablePro/Core/Services/Formatting/SQLFormatterService.swift @@ -103,7 +103,6 @@ internal struct SQLTokenFormatter { self.dataTypes = Self.builtinDataTypes.union(dialectDataTypes) } - // Mutable state private var output = "" private var clauseStack: [ClauseContext] = [] private var afterNewline = true diff --git a/TablePro/Core/Services/Formatting/SQLFormatterTypes.swift b/TablePro/Core/Services/Formatting/SQLFormatterTypes.swift index 802867e38..b489babbc 100644 --- a/TablePro/Core/Services/Formatting/SQLFormatterTypes.swift +++ b/TablePro/Core/Services/Formatting/SQLFormatterTypes.swift @@ -26,7 +26,7 @@ struct SQLFormatterOptions { /// Result of a formatting operation with cursor mapping struct SQLFormatterResult { let formattedSQL: String - let cursorOffset: Int? // New cursor position (nil if no cursor provided) + let cursorOffset: Int? init(formattedSQL: String, cursorOffset: Int? = nil) { self.formattedSQL = formattedSQL diff --git a/TablePro/Core/Services/Formatting/ValueDisplayDetector.swift b/TablePro/Core/Services/Formatting/ValueDisplayDetector.swift index 23298b223..1522530a0 100644 --- a/TablePro/Core/Services/Formatting/ValueDisplayDetector.swift +++ b/TablePro/Core/Services/Formatting/ValueDisplayDetector.swift @@ -90,7 +90,6 @@ enum ValueDisplayDetector { guard nameMatches else { return nil } - // Validate with sample value if available if let sample = sampleValue, let numericValue = Double(sample) { // Millisecond timestamps are > 10 billion if numericValue > 10_000_000_000 { diff --git a/TablePro/Core/Services/Infrastructure/DatabaseFileWatcher.swift b/TablePro/Core/Services/Infrastructure/DatabaseFileWatcher.swift index 2cfbe624a..5025a5dc1 100644 --- a/TablePro/Core/Services/Infrastructure/DatabaseFileWatcher.swift +++ b/TablePro/Core/Services/Infrastructure/DatabaseFileWatcher.swift @@ -53,7 +53,6 @@ final class DatabaseFileWatcher { // MARK: - Private private func startSource(connectionId: UUID) { - // Cancel any existing source if let existing = activeSources.removeValue(forKey: connectionId) { existing.cancel() } @@ -94,7 +93,6 @@ final class DatabaseFileWatcher { // SQLite journaling (rename + recreate) can invalidate the old fd. startSource(connectionId: connectionId) - // Debounced refresh debounceTasks[connectionId]?.cancel() debounceTasks[connectionId] = Task { @MainActor [weak self] in try? await Task.sleep(for: self?.debounceInterval ?? .milliseconds(500)) diff --git a/TablePro/Core/Services/Infrastructure/PreConnectHookRunner.swift b/TablePro/Core/Services/Infrastructure/PreConnectHookRunner.swift index 88f62be3c..4dbc92fed 100644 --- a/TablePro/Core/Services/Infrastructure/PreConnectHookRunner.swift +++ b/TablePro/Core/Services/Infrastructure/PreConnectHookRunner.swift @@ -68,7 +68,6 @@ enum PreConnectHookRunner { try process.run() - // 10-second timeout on a separate detached task let timeoutTask = Task.detached { try await Task.sleep(nanoseconds: 10_000_000_000) if process.isRunning { diff --git a/TablePro/Core/Services/Infrastructure/SettingsValidation.swift b/TablePro/Core/Services/Infrastructure/SettingsValidation.swift index 5168de677..adb0b08e4 100644 --- a/TablePro/Core/Services/Infrastructure/SettingsValidation.swift +++ b/TablePro/Core/Services/Infrastructure/SettingsValidation.swift @@ -90,10 +90,8 @@ extension Int { // MARK: - Validation Constants enum SettingsValidationRules { - // String validation static let nullDisplayMaxLength = 20 - // Int validation static let defaultPageSizeRange = 10...100_000 static let queryResultRowCapRange: ClosedRange = 100...500_000 static let minNonNegative = 0 diff --git a/TablePro/Core/Services/Infrastructure/UpdaterBridge.swift b/TablePro/Core/Services/Infrastructure/UpdaterBridge.swift index 199ffa33a..14a3390ea 100644 --- a/TablePro/Core/Services/Infrastructure/UpdaterBridge.swift +++ b/TablePro/Core/Services/Infrastructure/UpdaterBridge.swift @@ -33,7 +33,6 @@ final class UpdaterBridge { // Apply stored setting so Sparkle checks automatically on launch controller.updater.automaticallyChecksForUpdates = AppSettingsManager.shared.general.automaticallyCheckForUpdates - // Observe canCheckForUpdates via KVO observation = controller.updater.observe(\.canCheckForUpdates, options: [.new]) { [weak self] _, change in let newValue = change.newValue ?? false Task { @MainActor [weak self] in diff --git a/TablePro/Core/Services/Licensing/LicenseAPIClient.swift b/TablePro/Core/Services/Licensing/LicenseAPIClient.swift index 0aa9710f0..66a42225f 100644 --- a/TablePro/Core/Services/Licensing/LicenseAPIClient.swift +++ b/TablePro/Core/Services/Licensing/LicenseAPIClient.swift @@ -108,11 +108,9 @@ final class LicenseAPIClient { throw LicenseError.invalidKey case 409: - // Conflict — activation limit reached throw LicenseError.activationLimitReached case 403: - // Parse error message to determine specific error if let errorResponse = try? decoder.decode(LicenseAPIErrorResponse.self, from: data) { let msg = errorResponse.message.lowercased() if msg.contains("suspend") { diff --git a/TablePro/Core/Services/Licensing/LicenseManager.swift b/TablePro/Core/Services/Licensing/LicenseManager.swift index 4db7f4637..2b411bf35 100644 --- a/TablePro/Core/Services/Licensing/LicenseManager.swift +++ b/TablePro/Core/Services/Licensing/LicenseManager.swift @@ -126,13 +126,10 @@ final class LicenseManager { ) do { - // Call server let signedPayload = try await apiClient.activate(request: request) - // Verify signature let payloadData = try verifier.verify(payload: signedPayload) - // Build and store license let newLicense = License.from( payload: payloadData, signedPayload: signedPayload, @@ -221,7 +218,6 @@ final class LicenseManager { let signedPayload = try await apiClient.validate(request: request) let payloadData = try verifier.verify(payload: signedPayload) - // Update cached license with fresh data let updatedLicense = License.from( payload: payloadData, signedPayload: signedPayload, @@ -238,7 +234,6 @@ final class LicenseManager { Self.logger.warning("Re-validation failed: \(error.localizedDescription)") if license.daysSinceLastValidation > gracePeriodDays { - // Grace period exceeded — mark as validation failed self.status = .validationFailed Self.logger.error("Grace period exceeded (\(license.daysSinceLastValidation) days)") } diff --git a/TablePro/Core/Services/Licensing/LicenseSignatureVerifier.swift b/TablePro/Core/Services/Licensing/LicenseSignatureVerifier.swift index 0a1f8447c..61c11c9da 100644 --- a/TablePro/Core/Services/Licensing/LicenseSignatureVerifier.swift +++ b/TablePro/Core/Services/Licensing/LicenseSignatureVerifier.swift @@ -37,12 +37,10 @@ final class LicenseSignatureVerifier { encoder.outputFormatting = [.sortedKeys] let dataJSON = try encoder.encode(payload.data) - // Decode the base64 signature guard let signatureData = Data(base64Encoded: payload.signature) else { throw LicenseError.signatureInvalid } - // Verify RSA-SHA256 signature let isValid = SecKeyVerifySignature( publicKey, .rsaSignatureMessagePKCS1v15SHA256, @@ -73,7 +71,6 @@ final class LicenseSignatureVerifier { /// Parse a PEM-encoded public key into a SecKey private static func createSecKey(fromPEM pem: String) -> SecKey? { - // Strip PEM headers/footers and whitespace let stripped = pem .replacingOccurrences(of: "-----BEGIN PUBLIC KEY-----", with: "") .replacingOccurrences(of: "-----END PUBLIC KEY-----", with: "") diff --git a/TablePro/Core/Services/Query/QueryPlanParser.swift b/TablePro/Core/Services/Query/QueryPlanParser.swift index c607c07d6..d2acc3bfb 100644 --- a/TablePro/Core/Services/Query/QueryPlanParser.swift +++ b/TablePro/Core/Services/Query/QueryPlanParser.swift @@ -277,7 +277,6 @@ struct IndentedTextPlanParser: QueryPlanParser { let lines = rawText.components(separatedBy: "\n").filter { !$0.isEmpty } guard !lines.isEmpty else { return nil } - // Parse each line's indent level and content struct ParsedLine { let indent: Int let text: String diff --git a/TablePro/Core/Storage/ConnectionStorage.swift b/TablePro/Core/Storage/ConnectionStorage.swift index 1cb81f520..c6317fc88 100644 --- a/TablePro/Core/Storage/ConnectionStorage.swift +++ b/TablePro/Core/Storage/ConnectionStorage.swift @@ -305,7 +305,6 @@ final class ConnectionStorage { func duplicateConnection(_ connection: DatabaseConnection) -> DatabaseConnection { let newId = UUID() - // Create duplicate with new ID and "(Copy)" suffix let duplicate = DatabaseConnection( id: newId, name: String(format: String(localized: "%@ (Copy)"), connection.name), @@ -333,7 +332,6 @@ final class ConnectionStorage { additionalFields: connection.additionalFields.isEmpty ? nil : connection.additionalFields ) - // Save the duplicate connection var connections = loadConnections() connections.append(duplicate) guard saveConnections(connections) else { diff --git a/TablePro/Core/Storage/StoredConnection.swift b/TablePro/Core/Storage/StoredConnection.swift index a83dc221e..d5d3e7918 100644 --- a/TablePro/Core/Storage/StoredConnection.swift +++ b/TablePro/Core/Storage/StoredConnection.swift @@ -15,7 +15,6 @@ struct StoredConnection: Codable { let username: String let type: String - // SSH Configuration let sshEnabled: Bool let sshHost: String let sshPort: Int? @@ -24,45 +23,35 @@ struct StoredConnection: Codable { let sshPrivateKeyPath: String let sshAgentSocketPath: String - // SSL Configuration let sslMode: String let sslCaCertificatePath: String let sslClientCertificatePath: String let sslClientKeyPath: String - // Color, Tag, and Group let color: String let tagId: String? let groupId: String? let sshProfileId: String? - // Safe mode level let safeModeLevel: String - // AI policy let aiPolicy: String? // AI rules text included in the system prompt for this connection let aiRules: String? - // AI tools whitelisted for this connection let aiAlwaysAllowedTools: [String]? - // MongoDB-specific let mongoAuthSource: String? let mongoReadPreference: String? let mongoWriteConcern: String? - // Redis-specific let redisDatabase: Int? - // MSSQL schema let mssqlSchema: String? - // Oracle service name let oracleServiceName: String? - // Startup commands let startupCommands: String? // Sort order for sync @@ -75,7 +64,6 @@ struct StoredConnection: Codable { let isFavorite: Bool - // TOTP configuration let totpMode: String let totpAlgorithm: String let totpDigits: Int @@ -102,7 +90,6 @@ struct StoredConnection: Codable { self.username = connection.username self.type = connection.type.rawValue - // SSH Configuration self.sshEnabled = connection.sshConfig.enabled self.sshHost = connection.sshConfig.host self.sshPort = connection.sshConfig.port @@ -111,61 +98,47 @@ struct StoredConnection: Codable { self.sshPrivateKeyPath = connection.sshConfig.privateKeyPath self.sshAgentSocketPath = connection.sshConfig.agentSocketPath - // TOTP configuration self.totpMode = connection.sshConfig.totpMode.rawValue self.totpAlgorithm = connection.sshConfig.totpAlgorithm.rawValue self.totpDigits = connection.sshConfig.totpDigits self.totpPeriod = connection.sshConfig.totpPeriod - // SSL Configuration self.sslMode = connection.sslConfig.mode.rawValue self.sslCaCertificatePath = connection.sslConfig.caCertificatePath self.sslClientCertificatePath = connection.sslConfig.clientCertificatePath self.sslClientKeyPath = connection.sslConfig.clientKeyPath - // Color, Tag, and Group self.color = connection.color.rawValue self.tagId = connection.tagId?.uuidString self.groupId = connection.groupId?.uuidString self.sshProfileId = connection.sshProfileId?.uuidString - // Safe mode level self.safeModeLevel = connection.safeModeLevel.rawValue - // AI policy self.aiPolicy = connection.aiPolicy?.rawValue self.aiRules = connection.aiRules self.aiAlwaysAllowedTools = connection.aiAlwaysAllowedTools.isEmpty ? nil : Array(connection.aiAlwaysAllowedTools).sorted() - // MongoDB-specific self.mongoAuthSource = connection.mongoAuthSource self.mongoReadPreference = connection.mongoReadPreference self.mongoWriteConcern = connection.mongoWriteConcern - // Redis-specific self.redisDatabase = connection.redisDatabase - // MSSQL schema self.mssqlSchema = connection.mssqlSchema - // Oracle service name self.oracleServiceName = connection.oracleServiceName - // Startup commands self.startupCommands = connection.startupCommands - // Sort order self.sortOrder = connection.sortOrder - // Local-only self.localOnly = connection.localOnly - // Sample marker self.isSample = connection.isSample - // Favorite flag self.isFavorite = connection.isFavorite // SSH tunnel mode (v2 format preserving jump hosts, profiles, etc.) @@ -176,7 +149,6 @@ struct StoredConnection: Codable { ? (try? JSONEncoder().encode(connection.cloudflareTunnelMode)) : nil - // Plugin-driven additional fields self.additionalFields = connection.additionalFields.isEmpty ? nil : connection.additionalFields // Password source (not synced to iCloud; see SyncRecordMapper) diff --git a/TablePro/Core/Storage/TagStorage.swift b/TablePro/Core/Storage/TagStorage.swift index a19003069..27fc1b608 100644 --- a/TablePro/Core/Storage/TagStorage.swift +++ b/TablePro/Core/Storage/TagStorage.swift @@ -21,7 +21,6 @@ final class TagStorage { private var cachedTags: [ConnectionTag]? private init() { - // Initialize with presets on first launch if loadTags().isEmpty { saveTags(ConnectionTag.presets) } @@ -66,7 +65,6 @@ final class TagStorage { /// Add a new custom tag func addTag(_ tag: ConnectionTag) { var tags = loadTags() - // Prevent duplicates by name guard !tags.contains(where: { $0.name.lowercased() == tag.name.lowercased() }) else { return } diff --git a/TablePro/Core/Sync/SyncCoordinator.swift b/TablePro/Core/Sync/SyncCoordinator.swift index c1c840cc3..2dfd28c1e 100644 --- a/TablePro/Core/Sync/SyncCoordinator.swift +++ b/TablePro/Core/Sync/SyncCoordinator.swift @@ -172,7 +172,6 @@ final class SyncCoordinator { changeTracker.markDirty(.tableFavorite, id: FavoriteTablesStorage.syncId(for: entry)) } - // Mark all settings categories as dirty for category in ["general", "appearance", "editor", "dataGrid", "history", "tabs", "keyboard", "ai"] { changeTracker.markDirty(.settings, id: category) } @@ -199,7 +198,6 @@ final class SyncCoordinator { private func evaluateStatus() { let licenseManager = services.licenseManager - // Check license guard licenseManager.isFeatureAvailable(.iCloudSync) else { switch licenseManager.status { case .expired: @@ -210,14 +208,12 @@ final class SyncCoordinator { return } - // Check sync settings let syncSettings = services.appSettingsStorage.loadSync() guard syncSettings.enabled else { syncStatus = .disabled(.userDisabled) return } - // Check iCloud account guard iCloudAccountAvailable else { syncStatus = .disabled(.noAccount) return @@ -258,7 +254,6 @@ final class SyncCoordinator { var recordIDsToDelete: [CKRecord.ID] = [] let zoneID = await engine.zoneID - // Collect dirty connections if settings.syncConnections { let dirtyConnectionIds = changeTracker.dirtyRecords(for: .connection) if !dirtyConnectionIds.isEmpty { @@ -281,18 +276,15 @@ final class SyncCoordinator { } } - // Collect dirty groups and tags if settings.syncGroupsAndTags { collectDirtyGroups(into: &recordsToSave, deletions: &recordIDsToDelete, zoneID: zoneID) collectDirtyTags(into: &recordsToSave, deletions: &recordIDsToDelete, zoneID: zoneID) } - // Collect dirty SSH profiles if settings.syncSSHProfiles { collectDirtySSHProfiles(into: &recordsToSave, deletions: &recordIDsToDelete, zoneID: zoneID) } - // Collect dirty settings if settings.syncSettings { let dirtySettingsIds = changeTracker.dirtyRecords(for: .settings) for category in dirtySettingsIds { @@ -665,7 +657,6 @@ final class SyncCoordinator { await checkAccountStatus() evaluateStatus() - // If account changed, clear metadata and re-sync let currentAccountId = metadataStorage.lastAccountId if let newAccountId = try? await self.currentAccountId(), currentAccountId != nil, currentAccountId != newAccountId { diff --git a/TablePro/Core/Sync/SyncError.swift b/TablePro/Core/Sync/SyncError.swift index 469c1baca..c3ed1afe0 100644 --- a/TablePro/Core/Sync/SyncError.swift +++ b/TablePro/Core/Sync/SyncError.swift @@ -46,7 +46,6 @@ enum SyncError: LocalizedError, Equatable { return syncError } - // Map CKError codes to SyncError if let ckError = error as? CKError { switch ckError.code { case .networkUnavailable, .networkFailure: diff --git a/TablePro/Core/Utilities/Connection/EnvVarResolver.swift b/TablePro/Core/Utilities/Connection/EnvVarResolver.swift index 77842117b..8b432cbbd 100644 --- a/TablePro/Core/Utilities/Connection/EnvVarResolver.swift +++ b/TablePro/Core/Utilities/Connection/EnvVarResolver.swift @@ -70,23 +70,19 @@ internal enum EnvVarResolver { resolved.database = resolve(connection.database) resolved.username = resolve(connection.username) - // SSH fields resolved.sshConfig.host = resolve(connection.sshConfig.host) resolved.sshConfig.username = resolve(connection.sshConfig.username) resolved.sshConfig.privateKeyPath = resolve(connection.sshConfig.privateKeyPath) resolved.sshConfig.agentSocketPath = resolve(connection.sshConfig.agentSocketPath) - // SSL certificate paths resolved.sslConfig.caCertificatePath = resolve(connection.sslConfig.caCertificatePath) resolved.sslConfig.clientCertificatePath = resolve(connection.sslConfig.clientCertificatePath) resolved.sslConfig.clientKeyPath = resolve(connection.sslConfig.clientKeyPath) - // Startup commands if let commands = connection.startupCommands { resolved.startupCommands = resolve(commands) } - // Additional fields values var resolvedFields: [String: String] = [:] for (key, value) in connection.additionalFields { resolvedFields[key] = resolve(value) diff --git a/TablePro/Core/Utilities/MemoryPressureAdvisor.swift b/TablePro/Core/Utilities/MemoryPressureAdvisor.swift index c20fc385f..9d0d3b08b 100644 --- a/TablePro/Core/Utilities/MemoryPressureAdvisor.swift +++ b/TablePro/Core/Utilities/MemoryPressureAdvisor.swift @@ -54,7 +54,6 @@ internal enum MemoryPressureAdvisor { baseBudget = 2 } - // Halve the budget under memory pressure return isUnderPressure ? max(1, baseBudget / 2) : baseBudget } diff --git a/TablePro/Core/Vim/VimKeyInterceptor.swift b/TablePro/Core/Vim/VimKeyInterceptor.swift index 2b1faa8a8..66a4e6517 100644 --- a/TablePro/Core/Vim/VimKeyInterceptor.swift +++ b/TablePro/Core/Vim/VimKeyInterceptor.swift @@ -166,7 +166,6 @@ final class VimKeyInterceptor { return event // Pass through other Ctrl combinations } - // Translate NSEvent to Character guard let characters = event.characters, let char = characters.first else { return event } @@ -188,7 +187,6 @@ final class VimKeyInterceptor { closeSuggestionPopup() } - // Feed to Vim engine let shift = modifiers.contains(.shift) let consumed = engine.process(char, shift: shift) diff --git a/TablePro/Core/Vim/VimTextBufferAdapter.swift b/TablePro/Core/Vim/VimTextBufferAdapter.swift index 218af68e1..dfd681b4a 100644 --- a/TablePro/Core/Vim/VimTextBufferAdapter.swift +++ b/TablePro/Core/Vim/VimTextBufferAdapter.swift @@ -124,7 +124,6 @@ final class VimTextBufferAdapter: VimTextBuffer { if nsString.length == 0 { return (0, 0) } - // Find line start for the clamped offset let safeOffset = min(clampedOffset, max(0, nsString.length - 1)) let lineRange = nsString.lineRange(for: NSRange(location: safeOffset, length: 0)) let column = clampedOffset - lineRange.location @@ -188,11 +187,9 @@ final class VimTextBufferAdapter: VimTextBuffer { pos += 1 } } else { - // Skip same-class characters while pos < nsString.length && charClass(nsString.character(at: pos)) == startClass { pos += 1 } - // Skip whitespace between words while pos < nsString.length && charClass(nsString.character(at: pos)) == .whitespace { pos += 1 } @@ -201,11 +198,9 @@ final class VimTextBufferAdapter: VimTextBuffer { } else { var pos = min(offset, nsString.length) if pos > 0 { pos -= 1 } - // Skip whitespace backward while pos > 0 && charClass(nsString.character(at: pos)) == .whitespace { pos -= 1 } - // Skip same-class characters backward let cls = charClass(nsString.character(at: pos)) while pos > 0 && charClass(nsString.character(at: pos - 1)) == cls { pos -= 1 @@ -220,12 +215,10 @@ final class VimTextBufferAdapter: VimTextBuffer { guard nsString.length > 0 else { return 0 } var pos = min(offset + 1, nsString.length - 1) - // Skip whitespace while pos < nsString.length && charClass(nsString.character(at: pos)) == .whitespace { pos += 1 } guard pos < nsString.length else { return nsString.length - 1 } - // Go to end of same-class run let cls = charClass(nsString.character(at: pos)) while pos < nsString.length - 1 && charClass(nsString.character(at: pos + 1)) == cls { pos += 1 diff --git a/TablePro/Models/Connection/ConnectionExport.swift b/TablePro/Models/Connection/ConnectionExport.swift index 5715841d7..c3c46d550 100644 --- a/TablePro/Models/Connection/ConnectionExport.swift +++ b/TablePro/Models/Connection/ConnectionExport.swift @@ -6,18 +6,6 @@ import Foundation import UniformTypeIdentifiers -// MARK: - Sheet Binding Wrappers - -struct IdentifiableURL: Identifiable { - let id = UUID() - let url: URL -} - -struct IdentifiableConnections: Identifiable { - let id = UUID() - let connections: [DatabaseConnection] -} - // MARK: - UTType extension UTType { diff --git a/TablePro/Models/Connection/ConnectionTag.swift b/TablePro/Models/Connection/ConnectionTag.swift index e119a9fcc..9a999c551 100644 --- a/TablePro/Models/Connection/ConnectionTag.swift +++ b/TablePro/Models/Connection/ConnectionTag.swift @@ -12,7 +12,7 @@ struct ConnectionTag: Identifiable, Hashable, Codable { let id: UUID var name: String var isPreset: Bool // Preset tags cannot be deleted - var color: ConnectionColor // Tag display color + var color: ConnectionColor init(id: UUID = UUID(), name: String, isPreset: Bool = false, color: ConnectionColor = .gray) { self.id = id diff --git a/TablePro/Models/Connection/ConnectionToolbarState.swift b/TablePro/Models/Connection/ConnectionToolbarState.swift index 451e92723..7dd79311c 100644 --- a/TablePro/Models/Connection/ConnectionToolbarState.swift +++ b/TablePro/Models/Connection/ConnectionToolbarState.swift @@ -11,46 +11,6 @@ import Observation import SwiftUI import TableProPluginKit -// MARK: - Connection Environment - -/// Represents the connection environment type for visual badges -enum ConnectionEnvironment: String, CaseIterable { - case local = "LOCAL" - case ssh = "SSH" - case production = "PROD" - case staging = "STAGING" - - /// SF Symbol for this environment type - var iconName: String { - switch self { - case .local: return "house.fill" - case .ssh: return "lock.fill" - case .production: return "exclamationmark.triangle.fill" - case .staging: return "testtube.2" - } - } - - /// Badge background color - var backgroundColor: Color { - switch self { - case .local: return .gray.opacity(0.3) - case .ssh: return .orange.opacity(0.3) - case .production: return .red.opacity(0.3) - case .staging: return .blue.opacity(0.3) - } - } - - /// Badge foreground color - var foregroundColor: Color { - switch self { - case .local: return .secondary - case .ssh: return .orange - case .production: return .red - case .staging: return .blue - } - } -} - // MARK: - Connection State /// Represents the current state of the database connection diff --git a/TablePro/Models/ERDiagram/ERDiagramLayout.swift b/TablePro/Models/ERDiagram/ERDiagramLayout.swift index cd7c00a56..33a46a7ff 100644 --- a/TablePro/Models/ERDiagram/ERDiagramLayout.swift +++ b/TablePro/Models/ERDiagram/ERDiagramLayout.swift @@ -140,7 +140,6 @@ enum ERDiagramLayout { layerAssignment[id] = 0 } - // Group by layer var layers: [Int: [UUID]] = [:] for (id, layer) in layerAssignment { layers[layer, default: []].append(id) diff --git a/TablePro/Models/Query/QueryTabState.swift b/TablePro/Models/Query/QueryTabState.swift index 0d38707ed..e92eb34dc 100644 --- a/TablePro/Models/Query/QueryTabState.swift +++ b/TablePro/Models/Query/QueryTabState.swift @@ -13,11 +13,11 @@ final class GridSelectionState { /// Type of tab enum TabType: Equatable, Codable, Hashable { - case query // SQL editor tab - case table // Direct table view tab - case createTable // Create new table tab - case erDiagram // ER diagram tab - case serverDashboard // Server dashboard tab + case query + case table + case createTable + case erDiagram + case serverDashboard } /// Minimal representation of a tab for persistence @@ -182,7 +182,7 @@ struct PaginationState: Equatable { var pageSize: Int // Rows per page (passed from manager/coordinator) var currentPage: Int = 1 // Current page number (1-based) var currentOffset: Int = 0 // Current OFFSET for SQL query - var isLoading: Bool = false // Loading indicator + var isLoading: Bool = false var isApproximateRowCount: Bool = false // True when totalRowCount is from fast estimate // Result truncation state (query tabs) @@ -305,7 +305,6 @@ struct PaginationState: Equatable { mutating func updatePageSize(_ newSize: Int) { guard newSize > 0 else { return } pageSize = newSize - // Recalculate current page based on current offset currentPage = (currentOffset / pageSize) + 1 } diff --git a/TablePro/Models/Schema/SchemaChange.swift b/TablePro/Models/Schema/SchemaChange.swift index f14b4f044..83428140d 100644 --- a/TablePro/Models/Schema/SchemaChange.swift +++ b/TablePro/Models/Schema/SchemaChange.swift @@ -10,22 +10,18 @@ import Foundation /// Enum representing all possible schema change types enum SchemaChange: Hashable, Equatable { - // Column operations case addColumn(EditableColumnDefinition) case modifyColumn(old: EditableColumnDefinition, new: EditableColumnDefinition) case deleteColumn(EditableColumnDefinition) - // Index operations case addIndex(EditableIndexDefinition) case modifyIndex(old: EditableIndexDefinition, new: EditableIndexDefinition) case deleteIndex(EditableIndexDefinition) - // Foreign key operations case addForeignKey(EditableForeignKeyDefinition) case modifyForeignKey(old: EditableForeignKeyDefinition, new: EditableForeignKeyDefinition) case deleteForeignKey(EditableForeignKeyDefinition) - // Primary key operations case modifyPrimaryKey(old: [String], new: [String]) /// Whether this change is a deletion diff --git a/TablePro/Models/Settings/AppSettings.swift b/TablePro/Models/Settings/AppSettings.swift index bd4829f52..56f97e46c 100644 --- a/TablePro/Models/Settings/AppSettings.swift +++ b/TablePro/Models/Settings/AppSettings.swift @@ -223,7 +223,6 @@ struct DataGridSettings: Codable, Equatable { let sanitized = nullDisplay.sanitized let maxLength = SettingsValidationRules.nullDisplayMaxLength - // Clamp to max length if sanitized.isEmpty { return "NULL" // Fallback to default } else if sanitized.count > maxLength { diff --git a/TablePro/Models/UI/MultiRowEditState.swift b/TablePro/Models/UI/MultiRowEditState.swift index 08caeedbf..3f5dfe9f8 100644 --- a/TablePro/Models/UI/MultiRowEditState.swift +++ b/TablePro/Models/UI/MultiRowEditState.swift @@ -57,7 +57,7 @@ final class MultiRowEditState { private(set) var selectedRowIndices: Set = [] private(set) var allRows: [[String?]] = [] private(set) var columns: [String] = [] - private(set) var columnTypes: [ColumnType] = [] // Changed from [String] to [ColumnType] + private(set) var columnTypes: [ColumnType] = [] var hasEdits: Bool { fields.contains { $0.hasEdit } @@ -82,21 +82,18 @@ final class MultiRowEditState { self.columns = columns self.columnTypes = columnTypes - // Build field states var newFields: [FieldEditState] = [] for (colIndex, columnName) in columns.enumerated() { let columnTypeEnum = colIndex < columnTypes.count ? columnTypes[colIndex] : ColumnType.text(rawType: nil) let isLongText = columnTypeEnum.isLongText - // Gather values from all selected rows var values: [String?] = [] for row in allRows { let value = colIndex < row.count ? row[colIndex] : nil values.append(value) } - // Check if all values are the same let allSame = values.dropFirst().allSatisfy { $0 == values.first } let hasMultipleValues = !allSame @@ -104,7 +101,6 @@ final class MultiRowEditState { if hasMultipleValues { originalValue = nil } else { - // Get first value, unwrapping the optional properly originalValue = values.first.flatMap { $0 } } diff --git a/TablePro/TableProApp.swift b/TablePro/TableProApp.swift index 1cd92e270..35234b825 100644 --- a/TablePro/TableProApp.swift +++ b/TablePro/TableProApp.swift @@ -690,7 +690,6 @@ struct AppMenuCommands: Commands { // Tab navigation shortcuts — native macOS window tabs CommandGroup(after: .windowArrangement) { - // Tab switching by number (Cmd+1 through Cmd+9) ForEach(1...9, id: \.self) { number in Button("Select Tab \(number)") { actions?.selectTab(number: number) @@ -758,7 +757,6 @@ struct AppMenuCommands: Commands { @main struct TableProApp: App { - // Connect AppKit delegate for proper window configuration @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate diff --git a/TablePro/Theme/ThemeRegistryInstaller.swift b/TablePro/Theme/ThemeRegistryInstaller.swift index 159ba82f4..fc95805b6 100644 --- a/TablePro/Theme/ThemeRegistryInstaller.swift +++ b/TablePro/Theme/ThemeRegistryInstaller.swift @@ -95,7 +95,6 @@ internal final class ThemeRegistryInstaller { // Remove old files without triggering theme reload or fallback _ = try removeRegistryFiles(for: plugin.id) - // Write new themes var installedThemes: [InstalledRegistryTheme] = [] for theme in stagedThemes { try ThemeStorage.saveRegistryTheme(theme) diff --git a/TablePro/Theme/ThemeStorage.swift b/TablePro/Theme/ThemeStorage.swift index 1f271c940..214798e29 100644 --- a/TablePro/Theme/ThemeStorage.swift +++ b/TablePro/Theme/ThemeStorage.swift @@ -45,16 +45,13 @@ internal struct ThemeStorage { themes.append(contentsOf: loadBuiltInThemes(from: bundleDir)) } - // If no bundled themes loaded, use compiled presets as fallback if themes.isEmpty { themes = [ThemeDefinition.default] } - // Load registry themes ensureRegistryDirectory() themes.append(contentsOf: loadThemes(from: registryThemesDirectory, isBuiltIn: false)) - // Load user themes ensureUserDirectory() themes.append(contentsOf: loadThemes(from: userThemesDirectory, isBuiltIn: false)) diff --git a/TablePro/Views/Components/HighlightedSQLTextView.swift b/TablePro/Views/Components/HighlightedSQLTextView.swift index ed702ddac..a1b5de5fc 100644 --- a/TablePro/Views/Components/HighlightedSQLTextView.swift +++ b/TablePro/Views/Components/HighlightedSQLTextView.swift @@ -23,7 +23,6 @@ struct HighlightedSQLTextView: NSViewRepresentable { return scrollView } - // Configure text view textView.isEditable = false textView.isSelectable = true textView.font = NSFont.monospacedSystemFont(ofSize: fontSize, weight: .regular) @@ -45,7 +44,6 @@ struct HighlightedSQLTextView: NSViewRepresentable { func updateNSView(_ scrollView: NSScrollView, context: Context) { guard let textView = scrollView.documentView as? NSTextView else { return } - // Update font if changed if let currentFont = textView.font, currentFont.pointSize != fontSize { textView.font = NSFont.monospacedSystemFont(ofSize: fontSize, weight: .regular) if !textView.string.isEmpty { @@ -53,7 +51,6 @@ struct HighlightedSQLTextView: NSViewRepresentable { } } - // Update text if changed if textView.string != sql { textView.string = sql if !sql.isEmpty { diff --git a/TablePro/Views/Connection/ConnectionSidebarHeader.swift b/TablePro/Views/Connection/ConnectionSidebarHeader.swift index fe49cf9c1..e4429881f 100644 --- a/TablePro/Views/Connection/ConnectionSidebarHeader.swift +++ b/TablePro/Views/Connection/ConnectionSidebarHeader.swift @@ -24,9 +24,7 @@ struct ConnectionSidebarHeader: View { var body: some View { VStack(spacing: 0) { - // Connection selector button Menu { - // Active connections if !sessions.isEmpty { Section("Active Connections") { ForEach(sortedSessions) { session in @@ -42,10 +40,8 @@ struct ConnectionSidebarHeader: View { Spacer() - // Status indicator statusIndicator(for: session) - // Checkmark for active if session.id == currentSessionId { Image(systemName: "checkmark") } @@ -55,7 +51,6 @@ struct ConnectionSidebarHeader: View { } } - // Saved connections if !savedConnections.isEmpty { if !sessions.isEmpty { Divider() @@ -82,13 +77,11 @@ struct ConnectionSidebarHeader: View { Divider() } - // New connection Button(action: onNewConnection) { Label("New Connection", systemImage: "plus.circle") } } label: { HStack(spacing: 8) { - // Database icon if let session = currentSession { session.connection.type.iconImage .renderingMode(.template) @@ -100,14 +93,12 @@ struct ConnectionSidebarHeader: View { .foregroundStyle(.secondary) } - // Connection name Text(currentSession?.connection.name ?? "No Connection") .font(.body.weight(.medium)) .lineLimit(1) Spacer() - // Status + Chevron HStack(spacing: 6) { if let session = currentSession { Circle() diff --git a/TablePro/Views/Connection/ConnectionTagEditor.swift b/TablePro/Views/Connection/ConnectionTagEditor.swift index 86f0d307c..cf0ccb1ab 100644 --- a/TablePro/Views/Connection/ConnectionTagEditor.swift +++ b/TablePro/Views/Connection/ConnectionTagEditor.swift @@ -22,7 +22,6 @@ struct ConnectionTagEditor: View { var body: some View { Menu { - // None option Button { selectedTagId = nil } label: { @@ -37,7 +36,6 @@ struct ConnectionTagEditor: View { Divider() - // Available tags ForEach(allTags) { tag in Button { selectedTagId = tag.id @@ -55,14 +53,12 @@ struct ConnectionTagEditor: View { Divider() - // Create new tag Button { showingCreateSheet = true } label: { Label("Create New Tag...", systemImage: "plus.circle") } - // Manage tags (delete custom tags) if allTags.contains(where: { !$0.isPreset }) { Divider() diff --git a/TablePro/Views/Connection/OnboardingContentView.swift b/TablePro/Views/Connection/OnboardingContentView.swift index 2162ba53e..6c2b8b5d7 100644 --- a/TablePro/Views/Connection/OnboardingContentView.swift +++ b/TablePro/Views/Connection/OnboardingContentView.swift @@ -24,7 +24,6 @@ struct OnboardingContentView: View { var body: some View { VStack(spacing: 0) { - // Main content area ZStack { switch currentPage { case 0: @@ -40,7 +39,6 @@ struct OnboardingContentView: View { } .frame(maxWidth: .infinity, maxHeight: .infinity) - // Bottom navigation bar navigationBar } .onKeyPress(.leftArrow) { diff --git a/TablePro/Views/Connection/SSHProfileEditorView.swift b/TablePro/Views/Connection/SSHProfileEditorView.swift index 139895939..44e26d424 100644 --- a/TablePro/Views/Connection/SSHProfileEditorView.swift +++ b/TablePro/Views/Connection/SSHProfileEditorView.swift @@ -15,15 +15,12 @@ struct SSHProfileEditorView: View { var onSave: ((SSHProfile) -> Void)? var onDelete: (() -> Void)? - // Profile identity @State private var profileName: String = "" - // Server @State private var host: String = "" @State private var port: String = "22" @State private var username: String = "" - // Authentication @State private var authMethod: SSHAuthMethod = .password @State private var sshPassword: String = "" @State private var privateKeyPath: String = "" @@ -31,21 +28,17 @@ struct SSHProfileEditorView: View { @State private var agentSocketOption: SSHAgentSocketOption = .systemDefault @State private var customAgentSocketPath: String = "" - // TOTP @State private var totpMode: TOTPMode = .none @State private var totpSecret: String = "" @State private var totpAlgorithm: TOTPAlgorithm = .sha1 @State private var totpDigits: Int = 6 @State private var totpPeriod: Int = 30 - // Jump hosts @State private var jumpHosts: [SSHJumpHost] = [] - // SSH config auto-fill @State private var sshConfigEntries: [SSHConfigEntry] = [] @State private var selectedSSHConfigHost: String = "" - // Deletion @State private var showingDeleteConfirmation = false @State private var connectionsUsingProfile = 0 @State private var isTesting = false @@ -424,7 +417,6 @@ struct SSHProfileEditorView: View { SSHProfileStorage.shared.addProfile(profile) } - // Save secrets to Keychain if (authMethod == .password || authMethod == .keyboardInteractive) && !sshPassword.isEmpty { SSHProfileStorage.shared.saveSSHPassword(sshPassword, for: profileId) } else { diff --git a/TablePro/Views/ERDiagram/ERDiagramNodeRenderer.swift b/TablePro/Views/ERDiagram/ERDiagramNodeRenderer.swift index a46a185e8..f8fb0ed88 100644 --- a/TablePro/Views/ERDiagram/ERDiagramNodeRenderer.swift +++ b/TablePro/Views/ERDiagram/ERDiagramNodeRenderer.swift @@ -42,14 +42,11 @@ enum ERDiagramNodeRenderer { let roundedRect = RoundedRectangle(cornerRadius: cornerRadius) let path = Path(roundedRect: rect, cornerRadius: cornerRadius) - // Background context.fill(path, with: .color(Color(nsColor: .controlBackgroundColor))) - // Border let borderColor = isSelected ? Color.accentColor : Color(nsColor: .tertiaryLabelColor) context.stroke(path, with: .color(borderColor), lineWidth: isSelected ? 2 : 1) - // Header background let headerHeight: CGFloat = ERDiagramLayout.headerHeight let headerRect = CGRect(x: rect.minX, y: rect.minY, width: rect.width, height: headerHeight) let headerPath = Path { p in @@ -60,7 +57,6 @@ enum ERDiagramNodeRenderer { } context.fill(headerPath, with: .color(Color.accentColor.opacity(0.15))) - // Header text let displayName = (node.tableName as NSString).length > maxTableNameChars ? String(node.tableName.prefix(maxTableNameChars)) + "\u{2026}" : node.tableName @@ -72,7 +68,6 @@ enum ERDiagramNodeRenderer { anchor: .leading ) - // Table icon let iconText = Text(Image(systemName: "tablecells")) .font(.system(size: Self.iconPointSize * scale)) .foregroundStyle(.secondary) @@ -82,7 +77,6 @@ enum ERDiagramNodeRenderer { anchor: .leading ) - // Header divider let dividerY = rect.minY + headerHeight var dividerPath = Path() dividerPath.move(to: CGPoint(x: rect.minX, y: dividerY)) @@ -96,7 +90,6 @@ enum ERDiagramNodeRenderer { for (idx, col) in node.displayColumns.enumerated() { let rowY = dividerY + CGFloat(idx) * rowHeight + rowHeight / 2 - // PK/FK badge if col.isPrimaryKey { let badge = Text(Image(systemName: "key.fill")).font(.system(size: Self.badgePointSize * scale)).foregroundStyle(.yellow) clipped.draw(clipped.resolve(badge), at: CGPoint(x: rect.minX + badgeXOffset, y: rowY), anchor: .center) @@ -105,7 +98,6 @@ enum ERDiagramNodeRenderer { clipped.draw(clipped.resolve(badge), at: CGPoint(x: rect.minX + badgeXOffset, y: rowY), anchor: .center) } - // Column name let nameText = Text(col.name).font(.system(size: Self.columnNamePointSize * scale, design: .monospaced)) clipped.draw( clipped.resolve(nameText), diff --git a/TablePro/Views/Editor/AIEditorContextMenu.swift b/TablePro/Views/Editor/AIEditorContextMenu.swift index c6b97cd86..673569477 100644 --- a/TablePro/Views/Editor/AIEditorContextMenu.swift +++ b/TablePro/Views/Editor/AIEditorContextMenu.swift @@ -33,7 +33,6 @@ final class AIEditorContextMenu: NSMenu, NSMenuDelegate { func menuNeedsUpdate(_ menu: NSMenu) { menu.removeAllItems() - // Standard editing items let cutItem = NSMenuItem(title: String(localized: "Cut"), action: #selector(NSText.cut(_:)), keyEquivalent: "") menu.addItem(cutItem) diff --git a/TablePro/Views/Editor/HistoryPanelView.swift b/TablePro/Views/Editor/HistoryPanelView.swift index 6529bdddc..043db7d3e 100644 --- a/TablePro/Views/Editor/HistoryPanelView.swift +++ b/TablePro/Views/Editor/HistoryPanelView.swift @@ -70,7 +70,6 @@ struct HistoryPanelView: View { private extension HistoryPanelView { var historyList: some View { VStack(spacing: 0) { - // Header with filter controls and search VStack(spacing: 8) { HStack { Spacer() @@ -101,7 +100,6 @@ private extension HistoryPanelView { Divider() - // Entry list or empty state if entries.isEmpty { emptyState } else { @@ -217,7 +215,6 @@ private extension HistoryPanelView { var queryPreview: some View { if let entry = selectedEntry { VStack(spacing: 0) { - // Query text with syntax highlighting HighlightedSQLTextView( sql: entry.query.hasSuffix(";") ? entry.query : entry.query + ";", databaseType: entry.query.trimmingCharacters(in: .whitespaces) @@ -227,7 +224,6 @@ private extension HistoryPanelView { Divider() - // Metadata VStack(alignment: .leading, spacing: 4) { Text(buildPrimaryMetadata(entry)) .font(.subheadline) @@ -241,7 +237,6 @@ private extension HistoryPanelView { Divider() - // Action buttons HStack { Button(copyButtonTitle) { copyQueryWithFeedback(entry) @@ -316,7 +311,6 @@ private extension HistoryPanelView { await dataProvider.loadData() entries = dataProvider.historyEntries - // Clear selection if the selected entry no longer exists if let id = selectedEntryID, !entries.contains(where: { $0.id == id }) { selectedEntryID = nil } diff --git a/TablePro/Views/Editor/QueryEditorView.swift b/TablePro/Views/Editor/QueryEditorView.swift index f9d67ecfa..a38076781 100644 --- a/TablePro/Views/Editor/QueryEditorView.swift +++ b/TablePro/Views/Editor/QueryEditorView.swift @@ -86,7 +86,6 @@ struct QueryEditorView: View { Spacer() - // Clear button Button(action: { queryText = "" onClearResults?() @@ -98,7 +97,6 @@ struct QueryEditorView: View { .help(String(localized: "Clear Query")) .accessibilityLabel(String(localized: "Clear Query")) - // Format button Button(action: formatQuery) { Image(systemName: "text.alignleft") .frame(width: 24, height: 24) @@ -122,7 +120,6 @@ struct QueryEditorView: View { explainButton(hasQueryText: hasQueryText) - // Execute button Button(action: onExecute) { HStack(spacing: 4) { Image(systemName: "play.fill") diff --git a/TablePro/Views/Editor/SQLEditorCoordinator.swift b/TablePro/Views/Editor/SQLEditorCoordinator.swift index a54b4537b..bf020b0df 100644 --- a/TablePro/Views/Editor/SQLEditorCoordinator.swift +++ b/TablePro/Views/Editor/SQLEditorCoordinator.swift @@ -201,7 +201,6 @@ final class SQLEditorCoordinator: TextViewCoordinator, TextViewDelegate { vimEngine = nil vimCursorManager = nil - // Release editor controller heavy state controller?.releaseHeavyState() EditorEventRouter.shared.unregister(self) diff --git a/TablePro/Views/Export/ExportDialog.swift b/TablePro/Views/Export/ExportDialog.swift index 192a1cd11..8314ee9c0 100644 --- a/TablePro/Views/Export/ExportDialog.swift +++ b/TablePro/Views/Export/ExportDialog.swift @@ -72,17 +72,14 @@ struct ExportDialog: View { var body: some View { VStack(spacing: 0) { - // Content HStack(spacing: 0) { if !isQueryResultsMode { - // Left: Table tree view tableSelectionView .frame(minWidth: leftPanelWidth) Divider() } - // Right: Export options exportOptionsView .frame(width: 280) } @@ -90,7 +87,6 @@ struct ExportDialog: View { Divider() - // Footer footerView } .frame(width: dialogWidth) @@ -209,7 +205,6 @@ struct ExportDialog: View { private var tableSelectionView: some View { VStack(spacing: 0) { - // Header with title and selection count HStack { Text("Items") .font(.subheadline.weight(.medium)) @@ -232,7 +227,6 @@ struct ExportDialog: View { Divider() - // Tree view or loading indicator if isLoading { VStack { Spacer() @@ -270,7 +264,6 @@ struct ExportDialog: View { private var exportOptionsView: some View { VStack(alignment: .leading, spacing: 0) { - // Format picker with selection count VStack(alignment: .leading, spacing: 12) { if availableFormats.isEmpty { HStack { @@ -333,7 +326,6 @@ struct ExportDialog: View { Divider() - // Format-specific options ScrollView { VStack(alignment: .leading, spacing: 0) { if let settable = currentPlugin as? any SettablePluginDiscoverable, @@ -359,7 +351,6 @@ struct ExportDialog: View { Divider() - // File name section VStack(alignment: .leading, spacing: 6) { Text("File name") .font(.subheadline) @@ -377,7 +368,6 @@ struct ExportDialog: View { .fixedSize() } - // Show validation error if filename is invalid if let validationError = fileNameValidationError { Text(validationError) .font(.subheadline) @@ -506,7 +496,6 @@ struct ExportDialog: View { return String(localized: "Filename cannot be '.' or '..' or contain path traversal") } - // Check for Windows reserved device names (case-insensitive) let baseName = name.components(separatedBy: ".").first ?? name if Self.windowsReservedNames.contains(baseName.uppercased()) { return String(format: String(localized: "'%@' is a reserved Windows device name"), baseName) @@ -677,7 +666,6 @@ struct ExportDialog: View { databaseItems = items isLoading = false - // Set default filename based on selection if preselectedTables.count == 1, let first = preselectedTables.first { config.fileName = first } else if !connection.database.isEmpty { diff --git a/TablePro/Views/Export/ExportProgressView.swift b/TablePro/Views/Export/ExportProgressView.swift index 77bd091fc..addcf5051 100644 --- a/TablePro/Views/Export/ExportProgressView.swift +++ b/TablePro/Views/Export/ExportProgressView.swift @@ -22,16 +22,13 @@ struct ExportProgressView: View { var body: some View { VStack(spacing: 20) { - // Title Text(totalTables > 1 ? String(localized: "Export multiple tables") : String(localized: "Export table")) .font(.title3.weight(.semibold)) - // Table info and row count VStack(spacing: 8) { HStack { - // Show status message if set, otherwise show table name if !statusMessage.isEmpty { Text(statusMessage) .font(.body) @@ -52,7 +49,6 @@ struct ExportProgressView: View { } } - // Progress bar - indeterminate when status message is shown if !statusMessage.isEmpty { ProgressView() .progressViewStyle(.linear) diff --git a/TablePro/Views/Export/ExportSuccessView.swift b/TablePro/Views/Export/ExportSuccessView.swift index 7e73d6e21..c5fd9c2e0 100644 --- a/TablePro/Views/Export/ExportSuccessView.swift +++ b/TablePro/Views/Export/ExportSuccessView.swift @@ -22,14 +22,12 @@ struct ExportSuccessView: View { } var body: some View { VStack(spacing: 20) { - // Success icon Image(systemName: "checkmark.circle.fill") .font(.largeTitle) .imageScale(.large) .symbolRenderingMode(.hierarchical) .foregroundStyle(.green) - // Title and message VStack(spacing: 6) { Text("Success") .font(.title3.weight(.semibold)) @@ -39,7 +37,6 @@ struct ExportSuccessView: View { .foregroundStyle(.secondary) } - // Buttons VStack(spacing: 10) { Button("Open containing folder") { if localDontShowAgain { @@ -59,7 +56,6 @@ struct ExportSuccessView: View { .controlSize(.large) } - // Don't show again checkbox Toggle("Don't show this again", isOn: $localDontShowAgain) .toggleStyle(.checkbox) .font(.callout) diff --git a/TablePro/Views/Import/ImportDialog.swift b/TablePro/Views/Import/ImportDialog.swift index a919a7758..a94a345d9 100644 --- a/TablePro/Views/Import/ImportDialog.swift +++ b/TablePro/Views/Import/ImportDialog.swift @@ -284,7 +284,6 @@ struct ImportDialog: View { Spacer() } - // Plugin-provided options if let settable = currentPlugin as? any SettablePluginDiscoverable, let pluginView = settable.settingsView() { pluginView diff --git a/TablePro/Views/Main/Child/MainEditorContentView.swift b/TablePro/Views/Main/Child/MainEditorContentView.swift index cfac91a48..99f9089c9 100644 --- a/TablePro/Views/Main/Child/MainEditorContentView.swift +++ b/TablePro/Views/Main/Child/MainEditorContentView.swift @@ -45,7 +45,6 @@ struct MainEditorContentView: View { let onApplyFilters: ([TableFilter]) -> Void let onClearFilters: () -> Void - // Pagination callbacks let onFirstPage: () -> Void let onPreviousPage: () -> Void let onNextPage: () -> Void @@ -88,7 +87,6 @@ struct MainEditorContentView: View { emptyStateView } - // Global History Panel if isHistoryVisible { Divider() HistoryPanelView(connectionId: connectionId) @@ -736,19 +734,16 @@ struct MainEditorContentView: View { private var emptyStateView: some View { VStack(spacing: 20) { - // Icon Image(systemName: "tablecells") .font(.largeTitle) .imageScale(.large) .symbolRenderingMode(.hierarchical) .foregroundStyle(.quaternary) - // Title Text("No tabs open") .font(.title3.weight(.medium)) .foregroundStyle(.secondary) - // Helpful instructions with keyboard shortcuts VStack(spacing: 8) { HStack(spacing: 6) { Text("⌘T") diff --git a/TablePro/Views/Main/Extensions/MainContentCoordinator+Navigation.swift b/TablePro/Views/Main/Extensions/MainContentCoordinator+Navigation.swift index a88b1d53e..a95df3aca 100644 --- a/TablePro/Views/Main/Extensions/MainContentCoordinator+Navigation.swift +++ b/TablePro/Views/Main/Extensions/MainContentCoordinator+Navigation.swift @@ -93,7 +93,6 @@ extension MainContentCoordinator { return } - // If no tabs exist (empty state), add a table tab directly. if tabManager.tabs.isEmpty { addFirstTableTab( tableName: tableName, diff --git a/TablePro/Views/Main/Extensions/MainContentCoordinator+SidebarSave.swift b/TablePro/Views/Main/Extensions/MainContentCoordinator+SidebarSave.swift index 49b0f8859..a0c457279 100644 --- a/TablePro/Views/Main/Extensions/MainContentCoordinator+SidebarSave.swift +++ b/TablePro/Views/Main/Extensions/MainContentCoordinator+SidebarSave.swift @@ -47,7 +47,6 @@ extension MainContentCoordinator { ) } - // Route through the unified statement generation pipeline let statements = try changeManager.generateSQL(for: changes) guard !statements.isEmpty else { return } try await executeSidebarChanges(statements: statements) diff --git a/TablePro/Views/Main/Extensions/MainContentView+EventHandlers.swift b/TablePro/Views/Main/Extensions/MainContentView+EventHandlers.swift index ed2b39129..76d49e5bc 100644 --- a/TablePro/Views/Main/Extensions/MainContentView+EventHandlers.swift +++ b/TablePro/Views/Main/Extensions/MainContentView+EventHandlers.swift @@ -70,7 +70,6 @@ extension MainContentView { // Skip during tab switch — handleTabChange already configures the change manager guard !coordinator.isHandlingTabSwitch else { return } - // Prune hidden columns that no longer exist in results if let newColumns = newColumns { coordinator.pruneHiddenColumns(currentColumns: newColumns) } @@ -80,7 +79,6 @@ extension MainContentView { !changeManager.hasChanges else { return } - // Reconfigure if columns changed OR table name changed (switching tables) let columnsChanged = changeManager.columns != newColumns let tableChanged = changeManager.tableName != (tab.tableContext.tableName ?? "") diff --git a/TablePro/Views/Main/Extensions/MainContentView+Setup.swift b/TablePro/Views/Main/Extensions/MainContentView+Setup.swift index 943299753..d8719b1a8 100644 --- a/TablePro/Views/Main/Extensions/MainContentView+Setup.swift +++ b/TablePro/Views/Main/Extensions/MainContentView+Setup.swift @@ -290,7 +290,6 @@ extension MainContentView { window.representedURL = tabManager.selectedTab?.content.sourceFileURL window.isDocumentEdited = tabManager.selectedTab?.content.isFileDirty ?? false - // Update command actions window reference now that it's available commandActions?.window = window // Publish command actions to the registry NOW. `windowDidBecomeKey` diff --git a/TablePro/Views/Main/MainContentCommandActions.swift b/TablePro/Views/Main/MainContentCommandActions.swift index a72a750b0..1e390a22d 100644 --- a/TablePro/Views/Main/MainContentCommandActions.swift +++ b/TablePro/Views/Main/MainContentCommandActions.swift @@ -596,7 +596,6 @@ final class MainContentCommandActions { coordinator?.createTableActions?.createTable?() return } - // Check if we're in structure view mode if coordinator?.tabManager.selectedTab?.display.resultsViewMode == .structure { coordinator?.structureActions?.saveChanges?() } else if coordinator?.changeManager.hasChanges == true diff --git a/TablePro/Views/Main/MainContentCoordinator.swift b/TablePro/Views/Main/MainContentCoordinator.swift index 6baf302a2..3a020422b 100644 --- a/TablePro/Views/Main/MainContentCoordinator.swift +++ b/TablePro/Views/Main/MainContentCoordinator.swift @@ -752,7 +752,6 @@ final class MainContentCoordinator { changeManager.clearChanges() changeManager.pluginDriver = nil - // Release metadata tableMetadata = nil services.schemaProviderRegistry.release(for: connection.id) diff --git a/TablePro/Views/Main/TableSelectionAction.swift b/TablePro/Views/Main/TableSelectionAction.swift index 2e61c101d..00b3b0b7b 100644 --- a/TablePro/Views/Main/TableSelectionAction.swift +++ b/TablePro/Views/Main/TableSelectionAction.swift @@ -49,7 +49,6 @@ enum SidebarSyncAction: Equatable { selectedTables: Set, currentTabTableName: String? ) -> SidebarSyncAction { - // Only sync when tables just loaded and sidebar has no selection guard !newTables.isEmpty, selectedTables.isEmpty, let tabTableName = currentTabTableName, newTables.contains(where: { $0.name == tabTableName }) diff --git a/TablePro/Views/QueryPlan/QueryPlanDiagramView.swift b/TablePro/Views/QueryPlan/QueryPlanDiagramView.swift index fa1ffe61a..3257449f9 100644 --- a/TablePro/Views/QueryPlan/QueryPlanDiagramView.swift +++ b/TablePro/Views/QueryPlan/QueryPlanDiagramView.swift @@ -258,7 +258,6 @@ struct QueryPlanDiagramView: View { path.addCurve(to: end, control1: CGPoint(x: start.x, y: midY), control2: CGPoint(x: end.x, y: midY)) context.stroke(path, with: .color(.secondary.opacity(0.4)), lineWidth: 1) - // Arrowhead var arrow = Path() let s = PlanLayout.arrowHeadSize arrow.move(to: end) diff --git a/TablePro/Views/QueryPlan/QueryPlanTreeView.swift b/TablePro/Views/QueryPlan/QueryPlanTreeView.swift index 4833504c3..ce972cd97 100644 --- a/TablePro/Views/QueryPlan/QueryPlanTreeView.swift +++ b/TablePro/Views/QueryPlan/QueryPlanTreeView.swift @@ -15,7 +15,6 @@ struct QueryPlanTreeView: View { var body: some View { VStack(spacing: 0) { - // Tree list List(selection: $selection) { OutlineGroup( [plan.rootNode], @@ -27,7 +26,6 @@ struct QueryPlanTreeView: View { } .listStyle(.inset(alternatesRowBackgrounds: true)) - // Detail panel for selected node if let selectedNode = findNode(selection, in: plan.rootNode) { Divider() QueryPlanDetailView(node: selectedNode) @@ -55,13 +53,11 @@ private struct QueryPlanRowView: View { var body: some View { HStack(spacing: 8) { - // Cost indicator Circle() .fill(costColor) .frame(width: 8, height: 8) .accessibilityHidden(true) - // Operation + table VStack(alignment: .leading, spacing: 1) { HStack(spacing: 4) { Text(node.operation) @@ -89,7 +85,6 @@ private struct QueryPlanRowView: View { Spacer(minLength: 16) - // Cost if let startup = node.estimatedStartupCost, let total = node.estimatedTotalCost { Text(String(format: "%.2f..%.2f", startup, total)) .font(.system(.caption, design: .monospaced)) @@ -97,7 +92,6 @@ private struct QueryPlanRowView: View { .frame(width: 110, alignment: .trailing) } - // Rows if let rows = node.estimatedRows { Text("\(rows.formatted(.number.grouping(.automatic))) rows") .font(.system(.caption, design: .monospaced)) @@ -145,7 +139,6 @@ private struct QueryPlanDetailView: View { ScrollView(.horizontal, showsIndicators: false) { ScrollView(.vertical) { HStack(alignment: .top, spacing: 24) { - // Estimates VStack(alignment: .leading, spacing: 4) { Text(node.operation) .font(.caption.weight(.semibold)) @@ -170,7 +163,6 @@ private struct QueryPlanDetailView: View { } } - // Extra properties if !filteredProperties.isEmpty { VStack(alignment: .leading, spacing: 4) { Text("Details") diff --git a/TablePro/Views/Settings/LicenseActivationSheet.swift b/TablePro/Views/Settings/LicenseActivationSheet.swift index 25be56923..39ea4b4d9 100644 --- a/TablePro/Views/Settings/LicenseActivationSheet.swift +++ b/TablePro/Views/Settings/LicenseActivationSheet.swift @@ -17,7 +17,6 @@ struct LicenseActivationSheet: View { var body: some View { VStack(spacing: 0) { - // Header VStack(spacing: 8) { Image(systemName: "key.fill") .font(.title) @@ -34,7 +33,6 @@ struct LicenseActivationSheet: View { .padding(.top, 24) .padding(.bottom, 20) - // License key input VStack(spacing: 12) { TextField("XXXXX-XXXXX-XXXXX-XXXXX-XXXXX", text: $licenseKeyInput) .font(.system(.body, design: .monospaced)) @@ -52,7 +50,6 @@ struct LicenseActivationSheet: View { } .padding(.horizontal, 32) - // Actions VStack(spacing: 10) { if isActivating { ProgressView() diff --git a/TablePro/Views/Settings/ShortcutRecorderView.swift b/TablePro/Views/Settings/ShortcutRecorderView.swift index 0e382e919..6e51997c7 100644 --- a/TablePro/Views/Settings/ShortcutRecorderView.swift +++ b/TablePro/Views/Settings/ShortcutRecorderView.swift @@ -111,7 +111,6 @@ final class ShortcutRecorderNSView: NSView { override func draw(_ dirtyRect: NSRect) { let bounds = self.bounds - // Background if isRecording { NSColor.controlAccentColor.withAlphaComponent(0.1).setFill() } else { @@ -120,7 +119,6 @@ final class ShortcutRecorderNSView: NSView { let bgPath = NSBezierPath(roundedRect: bounds, xRadius: 6, yRadius: 6) bgPath.fill() - // Border if isRecording { NSColor.controlAccentColor.setStroke() } else { @@ -134,7 +132,6 @@ final class ShortcutRecorderNSView: NSView { borderPath.lineWidth = isRecording ? 2.0 : 1.0 borderPath.stroke() - // Text let text = displayText let textColor: NSColor = isRecording ? .secondaryLabelColor : .labelColor let font = NSFont.systemFont(ofSize: 12, weight: .medium) @@ -156,7 +153,6 @@ final class ShortcutRecorderNSView: NSView { /// The text to display in the view private var displayText: String { if isRecording { - // Show live modifier display or placeholder let modifierString = modifierDisplayString if modifierString.isEmpty { return String(localized: "Type shortcut...") @@ -164,7 +160,6 @@ final class ShortcutRecorderNSView: NSView { return modifierString } - // Not recording — show current shortcut or "None" if let combo = currentCombo, !combo.isCleared { return combo.displayString } diff --git a/TablePro/Views/Sidebar/MaintenanceSheet.swift b/TablePro/Views/Sidebar/MaintenanceSheet.swift index 631a68fbb..95e3dd251 100644 --- a/TablePro/Views/Sidebar/MaintenanceSheet.swift +++ b/TablePro/Views/Sidebar/MaintenanceSheet.swift @@ -23,7 +23,6 @@ struct MaintenanceSheet: View { var body: some View { VStack(alignment: .leading, spacing: 16) { - // Header HStack { Image(systemName: "wrench.and.screwdriver") .font(.title2) @@ -40,10 +39,8 @@ struct MaintenanceSheet: View { Divider() - // Operation-specific options operationOptions - // SQL preview VStack(alignment: .leading, spacing: 4) { Text(String(localized: "SQL Preview")) .font(.caption) @@ -58,7 +55,6 @@ struct MaintenanceSheet: View { Divider() - // Buttons HStack { Spacer() Button(String(localized: "Cancel")) { dismiss() } diff --git a/TablePro/Views/Sidebar/TableOperationDialog.swift b/TablePro/Views/Sidebar/TableOperationDialog.swift index 499b99404..d4573fbb7 100644 --- a/TablePro/Views/Sidebar/TableOperationDialog.swift +++ b/TablePro/Views/Sidebar/TableOperationDialog.swift @@ -87,7 +87,6 @@ struct TableOperationDialog: View { var body: some View { VStack(spacing: 0) { - // Header Text(title) .font(.body.weight(.semibold)) .padding(.vertical, 16) @@ -95,16 +94,13 @@ struct TableOperationDialog: View { Divider() - // Options VStack(alignment: .leading, spacing: 16) { - // Note for multiple tables if isMultipleTables { Text("Same options will be applied to all selected tables.") .font(.subheadline) .foregroundStyle(.secondary) } - // Ignore foreign key checks VStack(alignment: .leading, spacing: 4) { Toggle(isOn: $ignoreForeignKeys) { Text("Ignore foreign key checks") @@ -123,7 +119,6 @@ struct TableOperationDialog: View { } .opacity(ignoreFKDisabled ? 0.6 : 1.0) - // Cascade option VStack(alignment: .leading, spacing: 4) { Toggle(isOn: $cascade) { Text("Cascade") @@ -145,7 +140,6 @@ struct TableOperationDialog: View { Divider() - // Footer buttons HStack { Button("Cancel") { isPresented = false @@ -171,7 +165,6 @@ struct TableOperationDialog: View { isPresented = false } .onAppear { - // Reset state when dialog opens ignoreForeignKeys = false cascade = false } diff --git a/TablePro/Views/Structure/StructureRowViewWithMenu.swift b/TablePro/Views/Structure/StructureRowViewWithMenu.swift index e0e7f8b91..cc324928d 100644 --- a/TablePro/Views/Structure/StructureRowViewWithMenu.swift +++ b/TablePro/Views/Structure/StructureRowViewWithMenu.swift @@ -60,7 +60,6 @@ final class StructureRowViewWithMenu: DataGridRowView { copyDefItem.target = self menu.addItem(copyDefItem) - // Copy As submenu let copyAsSubmenu = NSMenu() let csvItem = NSMenuItem( title: "CSV", diff --git a/TablePro/Views/Structure/TableStructureView+DataLoading.swift b/TablePro/Views/Structure/TableStructureView+DataLoading.swift index 138fbc172..e346b5a62 100644 --- a/TablePro/Views/Structure/TableStructureView+DataLoading.swift +++ b/TablePro/Views/Structure/TableStructureView+DataLoading.swift @@ -140,7 +140,6 @@ extension TableStructureView { } func onRefreshData() { - // Ignore refresh notifications while we're in the middle of our own save/reload guard !isReloadingAfterSave else { Self.logger.debug("Ignoring refresh notification - currently reloading after save") return @@ -149,9 +148,7 @@ extension TableStructureView { // Skip warning if we just saved (within 2 seconds) let justSaved = lastSaveTime.map { Date().timeIntervalSince($0) < 2.0 } ?? false - // Check for unsaved changes before refreshing if structureChangeManager.hasChanges && !justSaved { - // Show confirmation dialog Task { @MainActor in let window = coordinator?.contentWindow let confirmed = await AlertHelper.confirmDestructive( diff --git a/TablePro/Views/Structure/TableStructureView+Schema.swift b/TablePro/Views/Structure/TableStructureView+Schema.swift index e8861f631..38434cc07 100644 --- a/TablePro/Views/Structure/TableStructureView+Schema.swift +++ b/TablePro/Views/Structure/TableStructureView+Schema.swift @@ -25,7 +25,6 @@ extension TableStructureView { return } - // If user chose to skip preview, apply changes directly if skipSchemaPreview { Task { await executeSchemaChanges() @@ -66,7 +65,6 @@ extension TableStructureView { let changes = structureChangeManager.getChangesArray() guard !changes.isEmpty else { return } - // Check for destructive changes that require confirmation let destructiveChanges = changes.filter { $0.requiresDataMigration } if !destructiveChanges.isEmpty { let descriptions = destructiveChanges.map { $0.description } @@ -95,7 +93,6 @@ extension TableStructureView { databaseType: connection.type ) - // Success - reload schema loadedTabs.removeAll() // Reload all structure data before calling loadSchemaForEditing @@ -116,10 +113,8 @@ extension TableStructureView { Self.logger.error("Failed to reload indexes/FKs: \(error.localizedDescription, privacy: .public)") } - // Now load the complete schema into the change manager loadSchemaForEditing() - // Load current tab data for display await loadTabDataIfNeeded(selectedTab) // Force clear state after reload (in case it got set during the async process) @@ -137,7 +132,7 @@ extension TableStructureView { lastSaveTime = Date() isReloadingAfterSave = false } catch { - isReloadingAfterSave = false // Clear flag on error + isReloadingAfterSave = false AlertHelper.showErrorSheet( title: String(localized: "Error Applying Changes"), message: error.localizedDescription, @@ -158,7 +153,6 @@ extension TableStructureView { var ddlView: some View { VStack(spacing: 0) { - // DDL toolbar HStack(spacing: 12) { HStack(spacing: 4) { Button(action: { ddlFontSize = max(10, ddlFontSize - 1) }) { diff --git a/TablePro/Views/Structure/TableStructureView.swift b/TablePro/Views/Structure/TableStructureView.swift index 54d97ae0a..bfeb04008 100644 --- a/TablePro/Views/Structure/TableStructureView.swift +++ b/TablePro/Views/Structure/TableStructureView.swift @@ -38,7 +38,7 @@ struct TableStructureView: View { @State var loadedTabs: Set = [] @State var partsReloadToken = 0 @State var isReloadingAfterSave = false // Prevent onChange loops during save reload - @State var lastSaveTime: Date? // Track when we last saved + @State var lastSaveTime: Date? @AppStorage("skipSchemaPreview") var skipSchemaPreview = false // Search and sort state diff --git a/TableProTests/Core/Database/SQLEscapingTests.swift b/TableProTests/Core/Database/SQLEscapingTests.swift index 488cba8e4..c0866f3d3 100644 --- a/TableProTests/Core/Database/SQLEscapingTests.swift +++ b/TableProTests/Core/Database/SQLEscapingTests.swift @@ -106,6 +106,23 @@ struct SQLEscapingTests { #expect(result == "\\''") } + // MARK: - quoteIdentifier Tests (ANSI SQL) + + @Test("Plain identifier wrapped in double quotes") + func testQuoteIdentifierPlain() { + #expect(SQLEscaping.quoteIdentifier("users") == "\"users\"") + } + + @Test("Embedded double quote is doubled") + func testQuoteIdentifierEmbeddedQuote() { + #expect(SQLEscaping.quoteIdentifier("we\"ird") == "\"we\"\"ird\"") + } + + @Test("Empty identifier yields empty quotes") + func testQuoteIdentifierEmpty() { + #expect(SQLEscaping.quoteIdentifier("") == "\"\"") + } + // MARK: - escapeLikeWildcards Tests @Test("LIKE plain string unchanged")