From 7e6dc5b968ef9237faf4bd0de438b16a37c755ce Mon Sep 17 00:00:00 2001 From: Ngo Quoc Dat Date: Sat, 20 Jun 2026 12:04:16 +0700 Subject: [PATCH 1/3] refactor: remove unreferenced dead code --- .../SQLStatementGenerator.swift | 6 --- TablePro/Core/MCP/MCPAuditLogStorage.swift | 6 --- TablePro/Core/MCP/TokenPermissionFilter.swift | 47 ------------------- .../Models/Connection/ConnectionExport.swift | 12 ----- .../Connection/ConnectionToolbarState.swift | 40 ---------------- 5 files changed, 111 deletions(-) delete mode 100644 TablePro/Core/MCP/TokenPermissionFilter.swift diff --git a/TablePro/Core/ChangeTracking/SQLStatementGenerator.swift b/TablePro/Core/ChangeTracking/SQLStatementGenerator.swift index 32cf563fc..e56469f41 100644 --- a/TablePro/Core/ChangeTracking/SQLStatementGenerator.swift +++ b/TablePro/Core/ChangeTracking/SQLStatementGenerator.swift @@ -259,12 +259,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/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/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/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 From 2b19074a9f39f98dbdbb32b37787e92def0fa55c Mon Sep 17 00:00:00 2001 From: Ngo Quoc Dat Date: Sat, 20 Jun 2026 12:04:17 +0700 Subject: [PATCH 2/3] refactor(database): route identifier quoting and string escaping through SQLEscaping --- TablePro/Core/Database/DatabaseDriver.swift | 9 ++------- TablePro/Core/Database/SQLEscaping.swift | 7 +++++++ .../Plugins/QueryResultExportDataSource.swift | 4 ++-- .../Core/Database/SQLEscapingTests.swift | 17 +++++++++++++++++ 4 files changed, 28 insertions(+), 9 deletions(-) diff --git a/TablePro/Core/Database/DatabaseDriver.swift b/TablePro/Core/Database/DatabaseDriver.swift index 609ea43c8..35fe83ce4 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 } 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/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/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") From 1a15652e26dbaca5171e3828c203014815dd060d Mon Sep 17 00:00:00 2001 From: Ngo Quoc Dat Date: Sat, 20 Jun 2026 12:20:52 +0700 Subject: [PATCH 3/3] refactor(plugins): replace guarded force-unwraps with optional binding --- Plugins/BigQueryDriverPlugin/BigQueryAuth.swift | 10 +++------- .../BigQueryDriverPlugin/BigQueryQueryBuilder.swift | 8 ++------ Plugins/MSSQLDriverPlugin/MSSQLPlugin.swift | 4 +--- 3 files changed, 6 insertions(+), 16 deletions(-) diff --git a/Plugins/BigQueryDriverPlugin/BigQueryAuth.swift b/Plugins/BigQueryDriverPlugin/BigQueryAuth.swift index 0c7a5bae8..fdceb193c 100644 --- a/Plugins/BigQueryDriverPlugin/BigQueryAuth.swift +++ b/Plugins/BigQueryDriverPlugin/BigQueryAuth.swift @@ -88,7 +88,7 @@ internal final class ServiceAccountAuthProvider: @unchecked Sendable, BigQueryAu self.clientEmail = email self.privateKeyPEM = key - self.projectId = overrideProjectId?.isEmpty == false ? overrideProjectId! : saProjectId + self.projectId = overrideProjectId.flatMap { $0.isEmpty ? nil : $0 } ?? saProjectId if self.projectId.isEmpty { throw BigQueryError.authFailed("No project ID found in service account JSON or connection settings") @@ -349,9 +349,7 @@ internal final class ADCAuthProvider: @unchecked Sendable, BigQueryAuthProvider } let quotaProject = json["quota_project_id"] as? String ?? "" - self.projectId = overrideProjectId?.isEmpty == false - ? overrideProjectId! - : quotaProject + self.projectId = overrideProjectId.flatMap { $0.isEmpty ? nil : $0 } ?? quotaProject if self.projectId.isEmpty { throw BigQueryError.authFailed( @@ -374,9 +372,7 @@ internal final class ADCAuthProvider: @unchecked Sendable, BigQueryAuthProvider } // Resolve source credentials - let resolvedProjectId = overrideProjectId?.isEmpty == false - ? overrideProjectId! - : (json["quota_project_id"] as? String ?? "") + let resolvedProjectId = overrideProjectId.flatMap { $0.isEmpty ? nil : $0 } ?? (json["quota_project_id"] as? String ?? "") if resolvedProjectId.isEmpty { throw BigQueryError.authFailed( diff --git a/Plugins/BigQueryDriverPlugin/BigQueryQueryBuilder.swift b/Plugins/BigQueryDriverPlugin/BigQueryQueryBuilder.swift index adffeee8f..1772f0b94 100644 --- a/Plugins/BigQueryDriverPlugin/BigQueryQueryBuilder.swift +++ b/Plugins/BigQueryDriverPlugin/BigQueryQueryBuilder.swift @@ -185,9 +185,7 @@ internal struct BigQueryQueryBuilder { // Search if let searchText = params.searchText, !searchText.isEmpty { - let searchCols = params.searchColumns?.isEmpty == false - ? params.searchColumns! - : columns + let searchCols = params.searchColumns.flatMap { $0.isEmpty ? nil : $0 } ?? columns let escapedSearch = searchText.replacingOccurrences(of: "'", with: "''") let searchClauses = searchCols.map { col in "CAST(\(quoteIdentifier(col)) AS STRING) LIKE '%\(escapedSearch)%'" @@ -236,9 +234,7 @@ internal struct BigQueryQueryBuilder { } if let searchText = params.searchText, !searchText.isEmpty { - let searchCols = params.searchColumns?.isEmpty == false - ? params.searchColumns! - : columns + let searchCols = params.searchColumns.flatMap { $0.isEmpty ? nil : $0 } ?? columns let escapedSearch = searchText.replacingOccurrences(of: "'", with: "''") let searchClauses = searchCols.map { col in "CAST(\(quoteIdentifier(col)) AS STRING) LIKE '%\(escapedSearch)%'" diff --git a/Plugins/MSSQLDriverPlugin/MSSQLPlugin.swift b/Plugins/MSSQLDriverPlugin/MSSQLPlugin.swift index 83bf5566f..387d16ec9 100644 --- a/Plugins/MSSQLDriverPlugin/MSSQLPlugin.swift +++ b/Plugins/MSSQLDriverPlugin/MSSQLPlugin.swift @@ -219,9 +219,7 @@ final class MSSQLPluginDriver: PluginDatabaseDriver, @unchecked Sendable { init(config: DriverConnectionConfig) { self.config = config - self._currentSchema = config.additionalFields["mssqlSchema"]?.isEmpty == false - ? config.additionalFields["mssqlSchema"]! - : "dbo" + self._currentSchema = config.additionalFields["mssqlSchema"].flatMap { $0.isEmpty ? nil : $0 } ?? "dbo" } private var escapedSchema: String {