diff --git a/Plugins/PostgreSQLDriverPlugin/CockroachPluginDriver.swift b/Plugins/PostgreSQLDriverPlugin/CockroachPluginDriver.swift index 2d6cbb261..e6bdcb77c 100644 --- a/Plugins/PostgreSQLDriverPlugin/CockroachPluginDriver.swift +++ b/Plugins/PostgreSQLDriverPlugin/CockroachPluginDriver.swift @@ -246,13 +246,11 @@ final class CockroachPluginDriver: LibPQBackedDriver, @unchecked Sendable { } func createDatabase(_ request: PluginCreateDatabaseRequest) async throws { - let quotedName = request.name.replacingOccurrences(of: "\"", with: "\"\"") - _ = try await execute(query: "CREATE DATABASE \"\(quotedName)\"") + _ = try await execute(query: "CREATE DATABASE \(quoteIdentifier(request.name))") } func dropDatabase(name: String) async throws { - let quotedName = name.replacingOccurrences(of: "\"", with: "\"\"") - _ = try await execute(query: "DROP DATABASE \"\(quotedName)\"") + _ = try await execute(query: "DROP DATABASE \(quoteIdentifier(name))") } // MARK: - Query Helpers diff --git a/Plugins/PostgreSQLDriverPlugin/PostgreSQLPluginDriver+Columns.swift b/Plugins/PostgreSQLDriverPlugin/PostgreSQLPluginDriver+Columns.swift index 1513bd861..b9edc97c5 100644 --- a/Plugins/PostgreSQLDriverPlugin/PostgreSQLPluginDriver+Columns.swift +++ b/Plugins/PostgreSQLDriverPlugin/PostgreSQLPluginDriver+Columns.swift @@ -8,8 +8,8 @@ import TableProPluginKit extension PostgreSQLPluginDriver { func fetchColumns(table: String, schema: String?) async throws -> [PluginColumnInfo] { - let safeSchema = escapeLiteralForColumns(currentSchema ?? "public") - let safeTable = escapeLiteralForColumns(table) + let safeSchema = escapeStringLiteral(currentSchema ?? "public") + let safeTable = escapeStringLiteral(table) let enumMap = try await fetchEnumLabelMap(schema: safeSchema) let caps = versionedCapabilities let identityProjection = caps.hasIdentityColumns ? "a.attidentity" : "NULL::text" @@ -60,7 +60,7 @@ extension PostgreSQLPluginDriver { } func fetchAllColumns(schema: String?) async throws -> [String: [PluginColumnInfo]] { - let safeSchema = escapeLiteralForColumns(currentSchema ?? "public") + let safeSchema = escapeStringLiteral(currentSchema ?? "public") let enumMap = try await fetchEnumLabelMap(schema: safeSchema) let caps = versionedCapabilities let identityProjection = caps.hasIdentityColumns ? "a.attidentity" : "NULL::text" @@ -134,10 +134,6 @@ extension PostgreSQLPluginDriver { return map } - fileprivate func escapeLiteralForColumns(_ str: String) -> String { - str.replacingOccurrences(of: "'", with: "''") - } - fileprivate func mapPgColumnRow( _ row: [PluginCellValue], tableNameOffset: Int, diff --git a/Plugins/PostgreSQLDriverPlugin/PostgreSQLPluginDriver.swift b/Plugins/PostgreSQLDriverPlugin/PostgreSQLPluginDriver.swift index dfa3cd330..854af3174 100644 --- a/Plugins/PostgreSQLDriverPlugin/PostgreSQLPluginDriver.swift +++ b/Plugins/PostgreSQLDriverPlugin/PostgreSQLPluginDriver.swift @@ -336,7 +336,7 @@ final class PostgreSQLPluginDriver: LibPQBackedDriver, @unchecked Sendable { func fetchTableDDL(table: String, schema: String?) async throws -> String { let safeTable = escapeLiteral(table) - let quotedTable = "\"\(table.replacingOccurrences(of: "\"", with: "\"\""))\"" + let quotedTable = quoteIdentifier(table) let caps = versionedCapabilities let identityClause: String = caps.hasIdentityColumns ? """ @@ -431,7 +431,7 @@ final class PostgreSQLPluginDriver: LibPQBackedDriver, @unchecked Sendable { var parts = columnDefs parts.append(contentsOf: constraints) - let quotedSchema = "\"\(core.currentSchema.replacingOccurrences(of: "\"", with: "\"\""))\"" + let quotedSchema = quoteIdentifier(core.currentSchema) let ddl = "CREATE TABLE \(quotedSchema).\(quotedTable) (\n " + parts.joined(separator: ",\n ") + "\n);" @@ -599,9 +599,9 @@ final class PostgreSQLPluginDriver: LibPQBackedDriver, @unchecked Sendable { let incrementBy = row[4].asText ?? "1" let cycle = row[5].asText == "t" ? " CYCLE" : "" let lastValue = row.count > 6 ? row[6].asText : nil - let quotedSeqName = "\"\(seqName.replacingOccurrences(of: "\"", with: "\"\""))\"" - let escapedSchemaForLiteral = schemaName.replacingOccurrences(of: "'", with: "''") - let escapedSeqForLiteral = seqName.replacingOccurrences(of: "'", with: "''") + let quotedSeqName = quoteIdentifier(seqName) + let escapedSchemaForLiteral = escapeStringLiteral(schemaName) + let escapedSeqForLiteral = escapeStringLiteral(seqName) var ddl = "CREATE SEQUENCE \(quotedSeqName) INCREMENT BY \(incrementBy)" + " MINVALUE \(minVal) MAXVALUE \(maxVal)" + " START WITH \(startVal)\(cycle);" @@ -692,7 +692,7 @@ final class PostgreSQLPluginDriver: LibPQBackedDriver, @unchecked Sendable { } func createDatabase(_ request: PluginCreateDatabaseRequest) async throws { - let quotedName = request.name.replacingOccurrences(of: "\"", with: "\"\"") + let quotedName = quoteIdentifier(request.name) guard let encoding = request.values["encoding"] else { throw LibPQPluginError( @@ -709,7 +709,7 @@ final class PostgreSQLPluginDriver: LibPQBackedDriver, @unchecked Sendable { ) } - var sql = "CREATE DATABASE \"\(quotedName)\" ENCODING '\(encoding)'" + var sql = "CREATE DATABASE \(quotedName) ENCODING '\(encoding)'" let supportsProvider = versionedCapabilities.hasDatabaseICULocale let provider = supportsProvider ? (request.values["provider"] ?? "libc") : "libc" @@ -789,8 +789,7 @@ final class PostgreSQLPluginDriver: LibPQBackedDriver, @unchecked Sendable { } func dropDatabase(name: String) async throws { - let escapedName = name.replacingOccurrences(of: "\"", with: "\"\"") - _ = try await execute(query: "DROP DATABASE \"\(escapedName)\"") + _ = try await execute(query: "DROP DATABASE \(quoteIdentifier(name))") } private struct Template1Defaults { diff --git a/Plugins/PostgreSQLDriverPlugin/PostgreSQLSchemaQueries.swift b/Plugins/PostgreSQLDriverPlugin/PostgreSQLSchemaQueries.swift index 8a5cd6332..2a93b77bd 100644 --- a/Plugins/PostgreSQLDriverPlugin/PostgreSQLSchemaQueries.swift +++ b/Plugins/PostgreSQLDriverPlugin/PostgreSQLSchemaQueries.swift @@ -77,7 +77,10 @@ enum PostgreSQLSchemaQueries { } static func setSearchPath(toSchema schema: String) -> String { - let quotedIdentifier = schema.replacingOccurrences(of: "\"", with: "\"\"") - return "SET search_path TO \"\(quotedIdentifier)\", public" + let quotedIdentifier = "\"\(schema.replacingOccurrences(of: "\"", with: "\"\""))\"" + guard schema != "public" else { + return "SET search_path TO \(quotedIdentifier)" + } + return "SET search_path TO \(quotedIdentifier), public" } } diff --git a/Plugins/PostgreSQLDriverPlugin/RedshiftPluginDriver.swift b/Plugins/PostgreSQLDriverPlugin/RedshiftPluginDriver.swift index 1877a7257..9c99a1746 100644 --- a/Plugins/PostgreSQLDriverPlugin/RedshiftPluginDriver.swift +++ b/Plugins/PostgreSQLDriverPlugin/RedshiftPluginDriver.swift @@ -295,8 +295,8 @@ final class RedshiftPluginDriver: LibPQBackedDriver, @unchecked Sendable { func fetchTableDDL(table: String, schema: String?) async throws -> String { let safeTable = escapeLiteral(table) - let quotedTable = "\"\(table.replacingOccurrences(of: "\"", with: "\"\""))\"" - let quotedSchema = "\"\(core.currentSchema.replacingOccurrences(of: "\"", with: "\"\""))\"" + let quotedTable = quoteIdentifier(table) + let quotedSchema = quoteIdentifier(core.currentSchema) do { let showResult = try await execute(query: "SHOW TABLE \(quotedSchema).\(quotedTable)") @@ -508,14 +508,12 @@ final class RedshiftPluginDriver: LibPQBackedDriver, @unchecked Sendable { ) } - let quotedName = request.name.replacingOccurrences(of: "\"", with: "\"\"") - let sql = "CREATE DATABASE \"\(quotedName)\" COLLATE \(collate)" + let sql = "CREATE DATABASE \(quoteIdentifier(request.name)) COLLATE \(collate)" _ = try await execute(query: sql) } func dropDatabase(name: String) async throws { - let escapedName = name.replacingOccurrences(of: "\"", with: "\"\"") - _ = try await execute(query: "DROP DATABASE \"\(escapedName)\"") + _ = try await execute(query: "DROP DATABASE \(quoteIdentifier(name))") } // MARK: - All Tables Metadata @@ -537,5 +535,4 @@ final class RedshiftPluginDriver: LibPQBackedDriver, @unchecked Sendable { ORDER BY "table" """ } - } diff --git a/TableProTests/Plugins/PostgreSQLSearchPathTests.swift b/TableProTests/Plugins/PostgreSQLSearchPathTests.swift index d6dbc0756..b4587cf64 100644 --- a/TableProTests/Plugins/PostgreSQLSearchPathTests.swift +++ b/TableProTests/Plugins/PostgreSQLSearchPathTests.swift @@ -16,6 +16,14 @@ struct PostgreSQLSearchPathTests { ) } + @Test("omits the redundant public fallback when public is the selected schema") + func publicSchema() { + #expect( + PostgreSQLSchemaQueries.setSearchPath(toSchema: "public") + == "SET search_path TO \"public\"" + ) + } + @Test("preserves mixed-case schema names with identifier quoting") func mixedCaseSchema() { #expect(