@@ -21,6 +21,10 @@ final class PostgreSQLPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
2121 var supportsSchemas : Bool { true }
2222 var supportsTransactions : Bool { true }
2323 var serverVersion : String ? { libpqConnection? . serverVersion ( ) }
24+ var serverVersionNumber : Int32 { libpqConnection? . serverVersionNumber ( ) ?? 0 }
25+ var capabilities : PostgreSQLCapabilities {
26+ PostgreSQLCapabilities ( serverVersion: serverVersionNumber)
27+ }
2428 var parameterStyle : ParameterStyle { . dollar }
2529
2630 var capabilities : PluginCapabilities {
@@ -230,22 +234,39 @@ final class PostgreSQLPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
230234
231235 func fetchTables( schema: String ? ) async throws -> [ PluginTableInfo ] {
232236 let schemaLiteral = escapeLiteral ( schema ?? _currentSchema)
233- let query = """
237+ let caps = capabilities
238+
239+ var unions : [ String ] = [
240+ """
234241 SELECT table_name, table_type FROM information_schema.tables
235242 WHERE table_schema = ' \( schemaLiteral) '
236243 AND table_type IN ('BASE TABLE', 'VIEW')
237- UNION ALL
238- SELECT matviewname AS table_name, 'MATERIALIZED VIEW' AS table_type
239- FROM pg_matviews
240- WHERE schemaname = ' \( schemaLiteral) '
241- UNION ALL
242- SELECT c.relname AS table_name, 'FOREIGN TABLE' AS table_type
243- FROM pg_foreign_table ft
244- JOIN pg_class c ON c.oid = ft.ftrelid
245- JOIN pg_namespace n ON n.oid = c.relnamespace
246- WHERE n.nspname = ' \( schemaLiteral) '
247- ORDER BY table_name
248244 """
245+ ]
246+
247+ if caps. hasMaterializedViewsCatalog {
248+ unions. append (
249+ """
250+ SELECT matviewname AS table_name, 'MATERIALIZED VIEW' AS table_type
251+ FROM pg_matviews
252+ WHERE schemaname = ' \( schemaLiteral) '
253+ """
254+ )
255+ }
256+
257+ if caps. hasForeignTablesCatalog {
258+ unions. append (
259+ """
260+ SELECT c.relname AS table_name, 'FOREIGN TABLE' AS table_type
261+ FROM pg_foreign_table ft
262+ JOIN pg_class c ON c.oid = ft.ftrelid
263+ JOIN pg_namespace n ON n.oid = c.relnamespace
264+ WHERE n.nspname = ' \( schemaLiteral) '
265+ """
266+ )
267+ }
268+
269+ let query = unions. joined ( separator: " \n UNION ALL \n " ) + " \n ORDER BY table_name "
249270 let result = try await execute ( query: query)
250271 return result. rows. compactMap { row -> PluginTableInfo ? in
251272 guard let name = row [ 0 ] . asText else { return nil }
@@ -263,10 +284,13 @@ final class PostgreSQLPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
263284
264285
265286 func fetchIndexes( table: String , schema: String ? ) async throws -> [ PluginIndexInfo ] {
287+ let columnOrdering = capabilities. hasArrayPosition
288+ ? " ORDER BY array_position(ix.indkey, a.attnum) "
289+ : " ORDER BY a.attnum "
266290 let query = """
267291 SELECT
268292 i.relname AS index_name,
269- ARRAY_AGG(a.attname ORDER BY array_position(ix.indkey, a.attnum )) AS columns,
293+ ARRAY_AGG(a.attname \( columnOrdering ) ) AS columns,
270294 ix.indisunique AS is_unique,
271295 ix.indisprimary AS is_primary,
272296 am.amname AS index_type,
@@ -409,22 +433,43 @@ final class PostgreSQLPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
409433 func fetchTableDDL( table: String , schema: String ? ) async throws -> String {
410434 let safeTable = escapeLiteral ( table)
411435 let quotedTable = " \" \( table. replacingOccurrences ( of: " \" " , with: " \" \" " ) ) \" "
436+ let caps = capabilities
412437
413- let columnsQuery = """
414- SELECT
415- quote_ident(a.attname) || ' ' || format_type(a.atttypid, a.atttypmod) ||
438+ let identityClause : String = caps. hasIdentityColumns ? """
416439 CASE
417440 WHEN a.attidentity = 'a' THEN ' GENERATED ALWAYS AS IDENTITY'
418441 WHEN a.attidentity = 'd' THEN ' GENERATED BY DEFAULT AS IDENTITY'
419442 ELSE ''
420443 END ||
444+ """ : " "
445+
446+ let generatedClause : String = caps. hasGeneratedColumns ? """
421447 CASE
422448 WHEN a.attgenerated = 's' THEN ' GENERATED ALWAYS AS (' || pg_get_expr(d.adbin, d.adrelid) || ') STORED'
423449 ELSE ''
424450 END ||
451+ """ : " "
452+
453+ let defaultGuard : String
454+ switch ( caps. hasIdentityColumns, caps. hasGeneratedColumns) {
455+ case ( true , true ) :
456+ defaultGuard = " AND a.attidentity = '' AND a.attgenerated = '' "
457+ case ( true , false ) :
458+ defaultGuard = " AND a.attidentity = '' "
459+ case ( false , true ) :
460+ defaultGuard = " AND a.attgenerated = '' "
461+ case ( false , false ) :
462+ defaultGuard = " "
463+ }
464+
465+ let columnsQuery = """
466+ SELECT
467+ quote_ident(a.attname) || ' ' || format_type(a.atttypid, a.atttypmod) ||
468+ \( identityClause)
469+ \( generatedClause)
425470 CASE WHEN a.attnotnull THEN ' NOT NULL' ELSE '' END ||
426471 CASE
427- WHEN a.atthasdef AND a.attidentity = '' AND a.attgenerated = ''
472+ WHEN a.atthasdef \( defaultGuard )
428473 THEN ' DEFAULT ' || pg_get_expr(d.adbin, d.adrelid)
429474 ELSE ''
430475 END
@@ -627,6 +672,7 @@ final class PostgreSQLPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
627672 }
628673
629674 func fetchDependentSequences( table: String , schema: String ? ) async throws -> [ ( name: String , ddl: String ) ] {
675+ guard capabilities. hasSequencesCatalog else { return [ ] }
630676 let safeTable = escapeLiteral ( table)
631677 let query = """
632678 SELECT s.sequencename,
@@ -674,8 +720,7 @@ final class PostgreSQLPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
674720 ]
675721
676722 func createDatabaseFormSpec( ) async throws -> PluginCreateDatabaseFormSpec ? {
677- let majorVersion = parsedServerMajorVersion ( )
678- let supportsProvider = ( majorVersion ?? 0 ) >= 15
723+ let supportsProvider = capabilities. hasDatabaseICULocale
679724
680725 async let templateDefaultsTask = fetchTemplate1Defaults ( )
681726 async let collationsTask = fetchCollations ( )
@@ -768,8 +813,7 @@ final class PostgreSQLPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
768813
769814 var sql = " CREATE DATABASE \" \( quotedName) \" ENCODING ' \( encoding) ' "
770815
771- let majorVersion = parsedServerMajorVersion ( )
772- let supportsProvider = ( majorVersion ?? 0 ) >= 15
816+ let supportsProvider = capabilities. hasDatabaseICULocale
773817 let provider = supportsProvider ? ( request. values [ " provider " ] ?? " libc " ) : " libc "
774818
775819 switch provider {
@@ -851,22 +895,6 @@ final class PostgreSQLPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
851895 _ = try await execute ( query: " DROP DATABASE \" \( escapedName) \" " )
852896 }
853897
854- private func parsedServerMajorVersion( ) -> Int ? {
855- guard let raw = serverVersion else { return nil }
856- let trimmed = raw. trimmingCharacters ( in: . whitespacesAndNewlines)
857- let scanner = Scanner ( string: trimmed)
858- scanner. charactersToBeSkipped = nil
859- _ = scanner. scanCharacters ( from: CharacterSet . decimalDigits. inverted)
860- guard let digitRun = scanner. scanCharacters ( from: . decimalDigits) ,
861- let value = Int ( digitRun) else {
862- return nil
863- }
864- if value > 999 {
865- return value / 10_000
866- }
867- return value
868- }
869-
870898 private struct Template1Defaults {
871899 let collate : String
872900 let ctype : String
@@ -875,11 +903,11 @@ final class PostgreSQLPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
875903 }
876904
877905 private func fetchTemplate1Defaults( ) async -> Template1Defaults ? {
878- let majorVersion = parsedServerMajorVersion ( ) ?? 0
906+ let caps = capabilities
879907 let selectColumns : String
880- if majorVersion >= 17 {
908+ if caps . hasDatabaseLocale {
881909 selectColumns = " datcollate, datctype, datlocprovider, datlocale "
882- } else if majorVersion >= 15 {
910+ } else if caps . hasDatabaseICULocale {
883911 selectColumns = " datcollate, datctype, datlocprovider, daticulocale "
884912 } else {
885913 selectColumns = " datcollate, datctype, NULL, NULL "
0 commit comments