diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..fe287d0
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,7 @@
+[*.swift]
+indent_style = space
+indent_size = 4
+tab_width = 4
+insert_final_newline = true
+trim_trailing_whitespace = true
+
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 7329336..15b15bb 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -26,7 +26,7 @@ jobs:
api-breakage:
if: ${{ github.event_name == 'pull_request' && !(github.event.pull_request.draft || false) }}
runs-on: ubuntu-latest
- container: swift:jammy
+ container: swift:noble
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -42,18 +42,18 @@ jobs:
fail-fast: false
matrix:
include:
- - postgres-image-a: 'postgres:13'
- postgres-image-b: 'postgres:14'
+ - postgres-image-a: 'postgres:12'
+ postgres-image-b: 'postgres:13'
postgres-auth: 'trust'
- swift-image: 'swift:5.9-focal'
- - postgres-image-a: 'postgres:15'
- postgres-image-b: 'postgres:16'
- postgres-auth: 'md5'
swift-image: 'swift:5.10-jammy'
- - postgres-image-a: 'postgres:15'
- postgres-image-b: 'postgres:16'
+ - postgres-image-a: 'postgres:14'
+ postgres-image-b: 'postgres:15'
+ postgres-auth: 'md5'
+ swift-image: 'swift:6.0-noble'
+ - postgres-image-a: 'postgres:16'
+ postgres-image-b: 'postgres:17'
postgres-auth: 'scram-sha-256'
- swift-image: 'swift:6.0-jammy'
+ swift-image: 'swift:6.1-noble'
container: ${{ matrix.swift-image }}
runs-on: ubuntu-latest
services:
@@ -89,8 +89,8 @@ jobs:
fail-fast: false
matrix:
include:
- - macos-version: macos-14
- xcode-version: latest
+ - macos-version: macos-15
+ xcode-version: latest-stable
runs-on: ${{ matrix.macos-version }}
env:
LOG_LEVEL: debug
@@ -106,7 +106,7 @@ jobs:
run: |
brew upgrade || true
export PATH="$(brew --prefix)/opt/postgresql@16/bin:$PATH" PGDATA=/tmp/vapor-postgres-test PGUSER="${POSTGRES_USER_A}"
- (brew unlink postgresql@14 || true) && brew install postgresql@16 && brew link --force postgresql@16
+ brew install postgresql@17 && brew link --force postgresql@17
initdb --locale=C --auth-host "scram-sha-256" -U "${POSTGRES_USER_A}" --pwfile=<(echo "${POSTGRES_PASSWORD_A}")
pg_ctl start --wait
PGPASSWORD="${POSTGRES_PASSWORD_A}" createdb -w -O "${POSTGRES_USER_A}" "${POSTGRES_DB_A}"
diff --git a/.swift-format b/.swift-format
new file mode 100644
index 0000000..e95ade0
--- /dev/null
+++ b/.swift-format
@@ -0,0 +1,77 @@
+{
+ "fileScopedDeclarationPrivacy" : {
+ "accessLevel" : "private"
+ },
+ "indentConditionalCompilationBlocks" : false,
+ "indentSwitchCaseLabels" : false,
+ "indentation" : {
+ "spaces" : 4
+ },
+ "lineBreakAroundMultilineExpressionChainComponents" : false,
+ "lineBreakBeforeControlFlowKeywords" : false,
+ "lineBreakBeforeEachArgument" : false,
+ "lineBreakBeforeEachGenericRequirement" : false,
+ "lineBreakBetweenDeclarationAttributes" : false,
+ "lineLength" : 150,
+ "maximumBlankLines" : 1,
+ "multiElementCollectionTrailingCommas" : true,
+ "noAssignmentInExpressions" : {
+ "allowedFunctions" : [
+ ]
+ },
+ "prioritizeKeepingFunctionOutputTogether" : false,
+ "reflowMultilineStringLiterals" : {
+ "never" : {
+ }
+ },
+ "respectsExistingLineBreaks" : true,
+ "rules" : {
+ "AllPublicDeclarationsHaveDocumentation" : false,
+ "AlwaysUseLiteralForEmptyCollectionInit" : true,
+ "AlwaysUseLowerCamelCase" : true,
+ "AmbiguousTrailingClosureOverload" : true,
+ "AvoidRetroactiveConformances" : true,
+ "BeginDocumentationCommentWithOneLineSummary" : false,
+ "DoNotUseSemicolons" : true,
+ "DontRepeatTypeInStaticProperties" : true,
+ "FileScopedDeclarationPrivacy" : true,
+ "FullyIndirectEnum" : true,
+ "GroupNumericLiterals" : true,
+ "IdentifiersMustBeASCII" : true,
+ "NeverForceUnwrap" : false,
+ "NeverUseForceTry" : false,
+ "NeverUseImplicitlyUnwrappedOptionals" : false,
+ "NoAccessLevelOnExtensionDeclaration" : true,
+ "NoAssignmentInExpressions" : true,
+ "NoBlockComments" : true,
+ "NoCasesWithOnlyFallthrough" : true,
+ "NoEmptyLinesOpeningClosingBraces" : false,
+ "NoEmptyTrailingClosureParentheses" : true,
+ "NoLabelsInCasePatterns" : true,
+ "NoLeadingUnderscores" : false,
+ "NoParensAroundConditions" : true,
+ "NoPlaygroundLiterals" : true,
+ "NoVoidReturnOnFunctionSignature" : true,
+ "OmitExplicitReturns" : false,
+ "OneCasePerLine" : true,
+ "OneVariableDeclarationPerLine" : true,
+ "OnlyOneTrailingClosureArgument" : true,
+ "OrderedImports" : true,
+ "ReplaceForEachWithForLoop" : true,
+ "ReturnVoidInsteadOfEmptyTuple" : true,
+ "TypeNamesShouldBeCapitalized" : true,
+ "UseEarlyExits" : true,
+ "UseExplicitNilCheckInConditions" : true,
+ "UseLetInEveryBoundCaseVariable" : true,
+ "UseShorthandTypeNames" : true,
+ "UseSingleLinePropertyGetter" : true,
+ "UseSynthesizedInitializer" : true,
+ "UseTripleSlashForDocumentationComments" : true,
+ "UseWhereClausesInForLoops" : false,
+ "ValidateDocumentationComments" : false
+ },
+ "spacesAroundRangeFormationOperators" : true,
+ "spacesBeforeEndOfLineComments" : 1,
+ "tabWidth" : 4,
+ "version" : 1
+}
diff --git a/Package.swift b/Package.swift
index 0560db0..8035350 100644
--- a/Package.swift
+++ b/Package.swift
@@ -1,4 +1,4 @@
-// swift-tools-version:5.9
+// swift-tools-version:5.10
import PackageDescription
let package = Package(
@@ -13,7 +13,6 @@ let package = Package(
.library(name: "FluentPostgresDriver", targets: ["FluentPostgresDriver"]),
],
dependencies: [
- .package(url: "https://github.com/vapor/async-kit.git", from: "1.20.0"),
.package(url: "https://github.com/vapor/fluent-kit.git", from: "1.49.0"),
.package(url: "https://github.com/vapor/postgres-kit.git", from: "2.13.4"),
],
@@ -21,7 +20,6 @@ let package = Package(
.target(
name: "FluentPostgresDriver",
dependencies: [
- .product(name: "AsyncKit", package: "async-kit"),
.product(name: "FluentKit", package: "fluent-kit"),
.product(name: "FluentSQL", package: "fluent-kit"),
.product(name: "PostgresKit", package: "postgres-kit"),
diff --git a/README.md b/README.md
index 76a07bb..632b114 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,5 @@
-
-
-
-
-
+
@@ -11,7 +7,7 @@
-
+
diff --git a/Sources/FluentPostgresDriver/Deprecations/FluentPostgresConfiguration+Deprecated.swift b/Sources/FluentPostgresDriver/Deprecations/FluentPostgresConfiguration+Deprecated.swift
index 600c52c..5948911 100644
--- a/Sources/FluentPostgresDriver/Deprecations/FluentPostgresConfiguration+Deprecated.swift
+++ b/Sources/FluentPostgresDriver/Deprecations/FluentPostgresConfiguration+Deprecated.swift
@@ -1,9 +1,7 @@
-import Logging
import FluentKit
-import AsyncKit
-import NIOCore
-import NIOSSL
import Foundation
+import Logging
+import NIOCore
import PostgresKit
import PostgresNIO
diff --git a/Sources/FluentPostgresDriver/Docs.docc/theme-settings.json b/Sources/FluentPostgresDriver/Docs.docc/theme-settings.json
index 6f0b9d4..4ce4f1c 100644
--- a/Sources/FluentPostgresDriver/Docs.docc/theme-settings.json
+++ b/Sources/FluentPostgresDriver/Docs.docc/theme-settings.json
@@ -8,11 +8,14 @@
"fluentpsqldriver": "#336791",
"documentation-intro-fill": "radial-gradient(circle at top, var(--color-fluentpsqldriver) 30%, #000 100%)",
"documentation-intro-accent": "var(--color-fluentpsqldriver)",
+ "documentation-intro-eyebrow": "white",
+ "documentation-intro-figure": "white",
+ "documentation-intro-title": "white",
"logo-base": { "dark": "#fff", "light": "#000" },
"logo-shape": { "dark": "#000", "light": "#fff" },
"fill": { "dark": "#000", "light": "#fff" }
},
- "icons": { "technology": "/fluentpostgresdriver/images/vapor-fluentpostgresdriver-logo.svg" }
+ "icons": { "technology": "/fluentpostgresdriver/images/FluentPostgresDriver/vapor-fluentpostgresdriver-logo.svg" }
},
"features": {
"quickNavigation": { "enable": true },
diff --git a/Sources/FluentPostgresDriver/FluentPostgresConfiguration.swift b/Sources/FluentPostgresDriver/FluentPostgresConfiguration.swift
index 851e832..4055646 100644
--- a/Sources/FluentPostgresDriver/FluentPostgresConfiguration.swift
+++ b/Sources/FluentPostgresDriver/FluentPostgresConfiguration.swift
@@ -1,9 +1,8 @@
-import Logging
-import FluentKit
import AsyncKit
-import NIOCore
-import NIOSSL
+import FluentKit
import Foundation
+import Logging
+import NIOCore
import PostgresKit
import PostgresNIO
@@ -31,7 +30,8 @@ extension DatabaseConfigurationFactory {
configuration: try .init(url: urlString),
maxConnectionsPerEventLoop: maxConnectionsPerEventLoop,
connectionPoolTimeout: connectionPoolTimeout,
- encodingContext: encodingContext, decodingContext: decodingContext,
+ encodingContext: encodingContext,
+ decodingContext: decodingContext,
sqlLogLevel: sqlLogLevel
)
}
@@ -59,7 +59,8 @@ extension DatabaseConfigurationFactory {
configuration: try .init(url: url),
maxConnectionsPerEventLoop: maxConnectionsPerEventLoop,
connectionPoolTimeout: connectionPoolTimeout,
- encodingContext: encodingContext, decodingContext: decodingContext,
+ encodingContext: encodingContext,
+ decodingContext: decodingContext,
sqlLogLevel: sqlLogLevel
)
}
@@ -81,22 +82,19 @@ extension DatabaseConfigurationFactory {
decodingContext: PostgresDecodingContext,
sqlLogLevel: Logger.Level = .debug
) -> DatabaseConfigurationFactory {
- let configuration = FakeSendable(wrappedValue: configuration)
-
- return .init {
+ .init {
FluentPostgresConfiguration(
configuration: configuration,
maxConnectionsPerEventLoop: maxConnectionsPerEventLoop,
connectionPoolTimeout: connectionPoolTimeout,
- encodingContext: encodingContext, decodingContext: decodingContext,
+ encodingContext: encodingContext,
+ decodingContext: decodingContext,
sqlLogLevel: sqlLogLevel
)
}
}
}
-fileprivate struct FakeSendable: @unchecked Sendable { let wrappedValue: T }
-
/// We'd like to just default the context parameters of the "actual" method. Unfortunately, there are a few
/// cases involving the UNIX domain socket initalizer where usage can resolve to either the new
/// `SQLPostgresConfiguration`-based method or the deprecated `PostgresConfiguration`-based method, with no
@@ -170,7 +168,7 @@ extension DatabaseConfigurationFactory {
/// The actual concrete configuration type produced by a configuration factory.
struct FluentPostgresConfiguration: DatabaseConfiguration {
var middleware: [any AnyModelMiddleware] = []
- fileprivate let configuration: FakeSendable
+ fileprivate let configuration: SQLPostgresConfiguration
let maxConnectionsPerEventLoop: Int
let connectionPoolTimeout: TimeAmount
let encodingContext: PostgresEncodingContext
@@ -178,14 +176,14 @@ struct FluentPostgresConfiguration any DatabaseDriver {
- let connectionSource = PostgresConnectionSource(sqlConfiguration: self.configuration.wrappedValue)
+ let connectionSource = PostgresConnectionSource(sqlConfiguration: self.configuration)
let elgPool = EventLoopGroupConnectionPool(
source: connectionSource,
maxConnectionsPerEventLoop: self.maxConnectionsPerEventLoop,
requestTimeout: self.connectionPoolTimeout,
on: databases.eventLoopGroup
)
-
+
return _FluentPostgresDriver(
pool: elgPool,
encodingContext: self.encodingContext,
diff --git a/Sources/FluentPostgresDriver/FluentPostgresDatabase.swift b/Sources/FluentPostgresDriver/FluentPostgresDatabase.swift
index 91eac91..539e5f5 100644
--- a/Sources/FluentPostgresDriver/FluentPostgresDatabase.swift
+++ b/Sources/FluentPostgresDriver/FluentPostgresDatabase.swift
@@ -16,23 +16,24 @@ struct _FluentPostgresDatabase {
extension _FluentPostgresDatabase: Database {
func execute(
query: DatabaseQuery,
- onOutput: @escaping @Sendable (any DatabaseOutput) -> ()
+ onOutput: @escaping @Sendable (any DatabaseOutput) -> Void
) -> EventLoopFuture {
var expression = SQLQueryConverter(delegate: PostgresConverterDelegate()).convert(query)
-
+
/// For `.create` query actions, we want to return the generated IDs, unless the `customIDKey` is the
/// empty string, which we use as a very hacky signal for "we don't implement this for composite IDs yet".
if case .create = query.action, query.customIDKey != .some(.string("")) {
expression = SQLKit.SQLList([expression, SQLReturning(.init((query.customIDKey ?? .id).description))], separator: SQLRaw(" "))
}
-
+
return self.execute(sql: expression, { onOutput($0.databaseOutput()) })
}
func execute(schema: DatabaseSchema) -> EventLoopFuture {
let expression = SQLSchemaConverter(delegate: PostgresConverterDelegate()).convert(schema)
- return self.execute(sql: expression,
+ return self.execute(
+ sql: expression,
// N.B.: Don't fatalError() here; what're users supposed to do about it?
{ self.logger.debug("Unexpected row returned from schema query: \($0)") }
)
@@ -50,9 +51,11 @@ extension _FluentPostgresDatabase: Database {
return self.eventLoop.makeSucceededFuture(())
}
- return self.eventLoop.flatten(e.createCases.map { create in
- self.alter(enum: e.name).add(value: create).run()
- })
+ return self.eventLoop.flatten(
+ e.createCases.map { create in
+ self.alter(enum: e.name).add(value: create).run()
+ }
+ )
case .delete:
return self.drop(enum: e.name).run()
}
@@ -64,10 +67,12 @@ extension _FluentPostgresDatabase: Database {
}
return self.withConnection { conn in
guard let sqlConn = conn as? any SQLDatabase else {
- fatalError("""
+ fatalError(
+ """
Connection yielded by a Fluent+Postgres database is not also an SQLDatabase.
This is a bug in Fluent; please report it at https://github.com/vapor/fluent-postgres-driver/issues
- """)
+ """
+ )
}
return sqlConn.raw("BEGIN").run().flatMap {
closure(conn).flatMap { result in
@@ -78,16 +83,22 @@ extension _FluentPostgresDatabase: Database {
}
}
}
-
+
func withConnection(_ closure: @escaping @Sendable (any Database) -> EventLoopFuture) -> EventLoopFuture {
self.withConnection { (underlying: any PostgresDatabase) in
- closure(_FluentPostgresDatabase(
- database: underlying.sql(encodingContext: self.encodingContext, decodingContext: self.decodingContext, queryLogLevel: self.database.queryLogLevel),
- context: self.context,
- encodingContext: self.encodingContext,
- decodingContext: self.decodingContext,
- inTransaction: true
- ))
+ closure(
+ _FluentPostgresDatabase(
+ database: underlying.sql(
+ encodingContext: self.encodingContext,
+ decodingContext: self.decodingContext,
+ queryLogLevel: self.database.queryLogLevel
+ ),
+ context: self.context,
+ encodingContext: self.encodingContext,
+ decodingContext: self.decodingContext,
+ inTransaction: true
+ )
+ )
}
}
}
@@ -96,11 +107,11 @@ extension _FluentPostgresDatabase: TransactionControlDatabase {
func beginTransaction() -> EventLoopFuture {
self.raw("BEGIN").run()
}
-
+
func commitTransaction() -> EventLoopFuture {
self.raw("COMMIT").run()
}
-
+
func rollbackTransaction() -> EventLoopFuture {
self.raw("ROLLBACK").run()
}
@@ -110,15 +121,15 @@ extension _FluentPostgresDatabase: SQLDatabase {
var version: (any SQLDatabaseReportedVersion)? { self.database.version }
var dialect: any SQLDialect { self.database.dialect }
var queryLogLevel: Logger.Level? { self.database.queryLogLevel }
-
- func execute(sql query: any SQLExpression, _ onRow: @escaping @Sendable (any SQLRow) -> ()) -> EventLoopFuture {
+
+ func execute(sql query: any SQLExpression, _ onRow: @escaping @Sendable (any SQLRow) -> Void) -> EventLoopFuture {
self.database.execute(sql: query, onRow)
}
-
- func execute(sql query: any SQLExpression, _ onRow: @escaping @Sendable (any SQLRow) -> ()) async throws {
+
+ func execute(sql query: any SQLExpression, _ onRow: @escaping @Sendable (any SQLRow) -> Void) async throws {
try await self.database.execute(sql: query, onRow)
}
-
+
func withSession(_ closure: @escaping @Sendable (any SQLDatabase) async throws -> R) async throws -> R {
try await self.database.withSession(closure)
}
@@ -128,15 +139,17 @@ extension _FluentPostgresDatabase: PostgresDatabase {
func send(_ request: any PostgresRequest, logger: Logger) -> EventLoopFuture {
self.withConnection { $0.send(request, logger: logger) }
}
-
+
func withConnection(_ closure: @escaping (PostgresConnection) -> EventLoopFuture) -> EventLoopFuture {
guard let psqlDb: any PostgresDatabase = self.database as? any PostgresDatabase else {
- fatalError("""
+ fatalError(
+ """
Connection yielded by a Fluent+Postgres database is not also a PostgresDatabase.
This is a bug in Fluent; please report it at https://github.com/vapor/fluent-postgres-driver/issues
- """)
+ """
+ )
}
-
+
return psqlDb.withConnection(closure)
}
}
diff --git a/Sources/FluentPostgresDriver/FluentPostgresDriver.swift b/Sources/FluentPostgresDriver/FluentPostgresDriver.swift
index 5b4d85c..53ebcb0 100644
--- a/Sources/FluentPostgresDriver/FluentPostgresDriver.swift
+++ b/Sources/FluentPostgresDriver/FluentPostgresDriver.swift
@@ -1,7 +1,7 @@
import AsyncKit
-import NIOCore
-import Logging
import FluentKit
+import Logging
+import NIOCore
import PostgresKit
/// Marked `@unchecked Sendable` to silence warning about `PostgresConnectionSource`
@@ -10,7 +10,7 @@ struct _FluentPostgresDriver: Da
let encodingContext: PostgresEncodingContext
let decodingContext: PostgresDecodingContext
let sqlLogLevel: Logger.Level
-
+
func makeDatabase(with context: DatabaseContext) -> any Database {
_FluentPostgresDatabase(
database: self.pool
@@ -23,11 +23,11 @@ struct _FluentPostgresDriver: Da
inTransaction: false
)
}
-
+
func shutdown() {
try? self.pool.syncShutdownGracefully()
}
-
+
func shutdownAsync() async {
try? await self.pool.shutdownAsync()
}
diff --git a/Sources/FluentPostgresDriver/PostgresConverterDelegate.swift b/Sources/FluentPostgresDriver/PostgresConverterDelegate.swift
index 7eb55c4..63e91bd 100644
--- a/Sources/FluentPostgresDriver/PostgresConverterDelegate.swift
+++ b/Sources/FluentPostgresDriver/PostgresConverterDelegate.swift
@@ -6,50 +6,50 @@ struct PostgresConverterDelegate: SQLConverterDelegate {
func customDataType(_ dataType: DatabaseSchema.DataType) -> (any SQLExpression)? {
switch dataType {
case .uuid:
- return SQLRaw("UUID")
+ SQLRaw("UUID")
case .bool:
- return SQLRaw("BOOL")
+ SQLRaw("BOOL")
case .data:
- return SQLRaw("BYTEA")
+ SQLRaw("BYTEA")
case .date:
- return SQLRaw("DATE")
+ SQLRaw("DATE")
case .datetime:
- return SQLRaw("TIMESTAMPTZ")
+ SQLRaw("TIMESTAMPTZ")
case .double:
- return SQLRaw("DOUBLE PRECISION")
+ SQLRaw("DOUBLE PRECISION")
case .dictionary:
- return SQLRaw("JSONB")
+ SQLRaw("JSONB")
case .array(of: let type):
if let type = type, let dataType = self.customDataType(type) {
- return SQLArrayDataType(dataType: dataType)
+ SQLArrayDataType(dataType: dataType)
} else {
- return SQLRaw("JSONB")
+ SQLRaw("JSONB")
}
case .enum(let value):
- return SQLIdentifier(value.name)
+ SQLIdentifier(value.name)
case .int8, .uint8:
- return SQLIdentifier("char")
+ SQLIdentifier("char")
case .int16, .uint16:
- return SQLRaw("SMALLINT")
+ SQLRaw("SMALLINT")
case .int32, .uint32:
- return SQLRaw("INT")
+ SQLRaw("INT")
case .int64, .uint64:
- return SQLRaw("BIGINT")
+ SQLRaw("BIGINT")
case .string:
- return SQLRaw("TEXT")
+ SQLRaw("TEXT")
case .time:
- return SQLRaw("TIME")
+ SQLRaw("TIME")
case .float:
- return SQLRaw("FLOAT")
+ SQLRaw("FLOAT")
case .custom:
- return nil
+ nil
}
}
}
private struct SQLArrayDataType: SQLExpression {
let dataType: any SQLExpression
-
+
func serialize(to serializer: inout SQLSerializer) {
self.dataType.serialize(to: &serializer)
serializer.write("[]")
diff --git a/Sources/FluentPostgresDriver/PostgresError+Database.swift b/Sources/FluentPostgresDriver/PostgresError+Database.swift
index a2435ee..cd6865e 100644
--- a/Sources/FluentPostgresDriver/PostgresError+Database.swift
+++ b/Sources/FluentPostgresDriver/PostgresError+Database.swift
@@ -3,70 +3,70 @@ import FluentSQL
import PostgresKit
import PostgresNIO
-fileprivate extension PostgresError.Code {
- var isSyntaxError: Bool {
+extension PostgresError.Code {
+ fileprivate var isSyntaxError: Bool {
switch self {
case .syntaxErrorOrAccessRuleViolation,
- .syntaxError,
- .insufficientPrivilege,
- .cannotCoerce,
- .groupingError,
- .windowingError,
- .invalidRecursion,
- .invalidForeignKey,
- .invalidName,
- .nameTooLong,
- .reservedName,
- .datatypeMismatch,
- .indeterminateDatatype,
- .collationMismatch,
- .indeterminateCollation,
- .wrongObjectType,
- .undefinedColumn,
- .undefinedFunction,
- .undefinedTable,
- .undefinedParameter,
- .undefinedObject,
- .duplicateColumn,
- .duplicateCursor,
- .duplicateDatabase,
- .duplicateFunction,
- .duplicatePreparedStatement,
- .duplicateSchema,
- .duplicateTable,
- .duplicateAlias,
- .duplicateObject,
- .ambiguousColumn,
- .ambiguousFunction,
- .ambiguousParameter,
- .ambiguousAlias,
- .invalidColumnReference,
- .invalidColumnDefinition,
- .invalidCursorDefinition,
- .invalidDatabaseDefinition,
- .invalidFunctionDefinition,
- .invalidPreparedStatementDefinition,
- .invalidSchemaDefinition,
- .invalidTableDefinition,
- .invalidObjectDefinition:
- return true
+ .syntaxError,
+ .insufficientPrivilege,
+ .cannotCoerce,
+ .groupingError,
+ .windowingError,
+ .invalidRecursion,
+ .invalidForeignKey,
+ .invalidName,
+ .nameTooLong,
+ .reservedName,
+ .datatypeMismatch,
+ .indeterminateDatatype,
+ .collationMismatch,
+ .indeterminateCollation,
+ .wrongObjectType,
+ .undefinedColumn,
+ .undefinedFunction,
+ .undefinedTable,
+ .undefinedParameter,
+ .undefinedObject,
+ .duplicateColumn,
+ .duplicateCursor,
+ .duplicateDatabase,
+ .duplicateFunction,
+ .duplicatePreparedStatement,
+ .duplicateSchema,
+ .duplicateTable,
+ .duplicateAlias,
+ .duplicateObject,
+ .ambiguousColumn,
+ .ambiguousFunction,
+ .ambiguousParameter,
+ .ambiguousAlias,
+ .invalidColumnReference,
+ .invalidColumnDefinition,
+ .invalidCursorDefinition,
+ .invalidDatabaseDefinition,
+ .invalidFunctionDefinition,
+ .invalidPreparedStatementDefinition,
+ .invalidSchemaDefinition,
+ .invalidTableDefinition,
+ .invalidObjectDefinition:
+ true
default:
- return false
+ false
}
}
- var isConstraintFailure: Bool {
+ fileprivate var isConstraintFailure: Bool {
switch self {
case .integrityConstraintViolation,
- .restrictViolation,
- .notNullViolation,
- .foreignKeyViolation,
- .uniqueViolation,
- .checkViolation,
- .exclusionViolation:
- return true
+ .restrictViolation,
+ .notNullViolation,
+ .foreignKeyViolation,
+ .uniqueViolation,
+ .checkViolation,
+ .exclusionViolation:
+ true
default:
- return false
+ false
}
}
}
@@ -76,8 +76,8 @@ extension PostgresError {
public var isSyntaxError: Bool { self.code.isSyntaxError }
public var isConnectionClosed: Bool {
switch self {
- case .connectionClosed: return true
- default: return false
+ case .connectionClosed: true
+ default: false
}
}
public var isConstraintFailure: Bool { self.code.isConstraintFailure }
@@ -87,30 +87,30 @@ extension PostgresError {
extension PSQLError {
public var isSyntaxError: Bool {
switch self.code {
- case .server: return self.serverInfo?[.sqlState].map { PostgresError.Code(raw: $0).isSyntaxError } ?? false
- default: return false
+ case .server: self.serverInfo?[.sqlState].map { PostgresError.Code(raw: $0).isSyntaxError } ?? false
+ default: false
}
}
-
+
public var isConnectionClosed: Bool {
switch self.code {
- case .serverClosedConnection, .clientClosedConnection: return true
- default: return false
+ case .serverClosedConnection, .clientClosedConnection: true
+ default: false
}
}
-
+
public var isConstraintFailure: Bool {
switch self.code {
- case .server: return self.serverInfo?[.sqlState].map { PostgresError.Code(raw: $0).isConstraintFailure } ?? false
- default: return false
+ case .server: self.serverInfo?[.sqlState].map { PostgresError.Code(raw: $0).isConstraintFailure } ?? false
+ default: false
}
}
}
#if compiler(<6)
-extension PostgresError: DatabaseError { }
-extension PSQLError: DatabaseError { }
+extension PostgresError: DatabaseError {}
+extension PSQLError: DatabaseError {}
#else
-extension PostgresError: @retroactive DatabaseError { }
-extension PSQLError: @retroactive DatabaseError { }
+extension PostgresError: @retroactive DatabaseError {}
+extension PSQLError: @retroactive DatabaseError {}
#endif
diff --git a/Sources/FluentPostgresDriver/PostgresRow+Database.swift b/Sources/FluentPostgresDriver/PostgresRow+Database.swift
index 898c3ae..beea4b6 100644
--- a/Sources/FluentPostgresDriver/PostgresRow+Database.swift
+++ b/Sources/FluentPostgresDriver/PostgresRow+Database.swift
@@ -1,19 +1,39 @@
-import PostgresNIO
-import PostgresKit
import FluentKit
+import PostgresKit
+import PostgresNIO
import SQLKit
extension SQLRow {
- internal func databaseOutput() -> some DatabaseOutput { _PostgresDatabaseOutput(row: self, schema: nil) }
+ func databaseOutput() -> some DatabaseOutput {
+ _PostgresDatabaseOutput(row: self, schema: nil)
+ }
}
private struct _PostgresDatabaseOutput: DatabaseOutput {
let row: any SQLRow
let schema: String?
- var description: String { String(describing: self.row) }
- private func adjust(key: FieldKey) -> FieldKey { self.schema.map { .prefix(.prefix(.string($0), "_"), key) } ?? key }
- func schema(_ schema: String) -> any DatabaseOutput { _PostgresDatabaseOutput(row: self.row, schema: schema) }
- func contains(_ key: FieldKey) -> Bool { self.row.contains(column: self.adjust(key: key).description) }
- func decodeNil(_ key: FieldKey) throws -> Bool { try self.row.decodeNil(column: self.adjust(key: key).description) }
- func decode(_ key: FieldKey, as: T.Type) throws -> T { try self.row.decode(column: self.adjust(key: key).description, as: T.self) }
+
+ var description: String {
+ String(describing: self.row)
+ }
+
+ private func adjust(key: FieldKey) -> FieldKey {
+ self.schema.map { .prefix(.prefix(.string($0), "_"), key) } ?? key
+ }
+
+ func schema(_ schema: String) -> any DatabaseOutput {
+ _PostgresDatabaseOutput(row: self.row, schema: schema)
+ }
+
+ func contains(_ key: FieldKey) -> Bool {
+ self.row.contains(column: self.adjust(key: key).description)
+ }
+
+ func decodeNil(_ key: FieldKey) throws -> Bool {
+ try self.row.decodeNil(column: self.adjust(key: key).description)
+ }
+
+ func decode(_ key: FieldKey, as: T.Type) throws -> T {
+ try self.row.decode(column: self.adjust(key: key).description, as: T.self)
+ }
}
diff --git a/Tests/FluentPostgresDriverTests/FluentPostgresDriverTests.swift b/Tests/FluentPostgresDriverTests/FluentPostgresDriverTests.swift
index 2501e4a..c268085 100644
--- a/Tests/FluentPostgresDriverTests/FluentPostgresDriverTests.swift
+++ b/Tests/FluentPostgresDriverTests/FluentPostgresDriverTests.swift
@@ -1,15 +1,16 @@
-import Logging
-import FluentKit
import FluentBenchmark
+import FluentKit
import FluentPostgresDriver
-import XCTest
+import Logging
import PostgresKit
import SQLKit
+import XCTest
func XCTAssertThrowsErrorAsync(
_ expression: @autoclosure () async throws -> T,
_ message: @autoclosure () -> String = "",
- file: StaticString = #filePath, line: UInt = #line,
+ file: StaticString = #filePath,
+ line: UInt = #line,
_ callback: (any Error) -> Void = { _ in }
) async {
do {
@@ -23,7 +24,8 @@ func XCTAssertThrowsErrorAsync(
func XCTAssertNoThrowAsync(
_ expression: @autoclosure () async throws -> T,
_ message: @autoclosure () -> String = "",
- file: StaticString = #filePath, line: UInt = #line
+ file: StaticString = #filePath,
+ line: UInt = #line
) async {
do {
_ = try await expression()
@@ -73,7 +75,7 @@ final class FluentPostgresDriverTests: XCTestCase {
XCTAssertFalse(($0 as? any DatabaseError)?.isConstraintFailure ?? true, "\(String(reflecting: $0))")
XCTAssertFalse(($0 as? any DatabaseError)?.isConnectionClosed ?? true, "\(String(reflecting: $0))")
}
-
+
let sql2 = (self.dbs.database(.a, logger: .init(label: "test.fluent.a"), on: self.eventLoopGroup.any())!) as! any SQLDatabase
try await sql2.drop(table: "foo").ifExists().run()
try await sql2.create(table: "foo").column("name", type: .text, .unique).run()
@@ -83,7 +85,7 @@ final class FluentPostgresDriverTests: XCTestCase {
XCTAssertFalse(($0 as? any DatabaseError)?.isSyntaxError ?? true, "\(String(reflecting: $0))")
XCTAssertFalse(($0 as? any DatabaseError)?.isConnectionClosed ?? true, "\(String(reflecting: $0))")
}
-
+
// Disabled until we figure out why it hangs instead of throwing an error.
//let postgres = (self.dbs.database(.a, logger: .init(label: "test.fluent.a"), on: self.eventLoopGroup.any())!) as! any PostgresDatabase
//await XCTAssertThrowsErrorAsync(try await postgres.withConnection { conn in
@@ -96,7 +98,7 @@ final class FluentPostgresDriverTests: XCTestCase {
// XCTAssertFalse(($0 as? any DatabaseError)?.isConstraintFailure ?? true, "\(String(reflecting: $0))")
//}
}
-
+
func testBlob() async throws {
struct CreateFoo: AsyncMigration {
func prepare(on database: any Database) async throws {
@@ -156,10 +158,14 @@ final class FluentPostgresDriverTests: XCTestCase {
let jsonDecoder = JSONDecoder()
jsonDecoder.dateDecodingStrategy = .iso8601
- self.dbs.use(.testPostgres(subconfig: "A",
- encodingContext: .init(jsonEncoder: jsonEncoder),
- decodingContext: .init(jsonDecoder: jsonDecoder)
- ), as: .iso8601)
+ self.dbs.use(
+ .testPostgres(
+ subconfig: "A",
+ encodingContext: .init(jsonEncoder: jsonEncoder),
+ decodingContext: .init(jsonDecoder: jsonDecoder)
+ ),
+ as: .iso8601
+ )
let db = self.dbs.database(
.iso8601,
logger: .init(label: "test"),
@@ -210,30 +216,38 @@ final class FluentPostgresDriverTests: XCTestCase {
throw error
}
}
-
+
func testEncodingArrayOfModels() async throws {
final class Elem: Model, ExpressibleByIntegerLiteral, @unchecked Sendable {
static let schema = ""
@ID(custom: .id) var id: Int?
- init() {}; init(integerLiteral l: Int) { self.id = l }
+ init() {}
+ init(integerLiteral l: Int) { self.id = l }
}
final class Seq: Model, ExpressibleByNilLiteral, ExpressibleByArrayLiteral, @unchecked Sendable {
static let schema = "seqs"
- @ID(custom: .id) var id: Int?; @OptionalField(key: "list") var list: [Elem]?
- init() {}; init(nilLiteral: ()) { self.list = nil }; init(arrayLiteral el: Elem...) { self.list = el }
+ @ID(custom: .id) var id: Int?
+ @OptionalField(key: "list") var list: [Elem]?
+ init() {}
+ init(nilLiteral: ()) { self.list = nil }
+ init(arrayLiteral el: Elem...) { self.list = el }
}
do {
try await self.db.schema(Seq.schema).field(.id, .int, .identifier(auto: true)).field("list", .sql(embed: "JSONB[]")).create()
-
- let s1: Seq = [1, 2], s2: Seq = nil; try [s1, s2].forEach { try $0.create(on: self.db).wait() }
-
+
+ let s1: Seq = [1, 2]
+ let s2: Seq = nil
+ try [s1, s2].forEach { try $0.create(on: self.db).wait() }
+
// Make sure it went into the DB as "array of jsonb" rather than as "array of one jsonb containing array" or such.
- let raws = try await (self.db as! any SQLDatabase).raw("SELECT array_to_json(list)::text t FROM seqs").all().map { try $0.decode(column: "t", as: String?.self) }
+ let raws = try await (self.db as! any SQLDatabase).raw("SELECT array_to_json(list)::text t FROM seqs").all().map {
+ try $0.decode(column: "t", as: String?.self)
+ }
XCTAssertEqual(raws, [#"[{"id": 1},{"id": 2}]"#, nil])
-
+
// Make sure it round-trips through Fluent.
let seqs = try await Seq.query(on: self.db).all()
-
+
XCTAssertEqual(seqs.count, 2)
XCTAssertEqual(seqs.dropFirst(0).first?.id, s1.id)
XCTAssertEqual(seqs.dropFirst(0).first?.list?.map(\.id), s1.list?.map(\.id))
@@ -245,17 +259,16 @@ final class FluentPostgresDriverTests: XCTestCase {
try await db.schema(Seq.schema).delete()
}
-
var benchmarker: FluentBenchmarker { .init(databases: self.dbs) }
var eventLoopGroup: any EventLoopGroup { MultiThreadedEventLoopGroup.singleton }
var threadPool: NIOThreadPool { NIOThreadPool.singleton }
var dbs: Databases!
var db: (any Database)!
var postgres: any PostgresDatabase { self.db as! any PostgresDatabase }
-
+
override func setUp() async throws {
try await super.setUp()
-
+
XCTAssert(isLoggingConfigured)
self.dbs = Databases(threadPool: self.threadPool, on: self.eventLoopGroup)
@@ -271,7 +284,7 @@ final class FluentPostgresDriverTests: XCTestCase {
_ = try await (b as! any PostgresDatabase).query("create schema public").get()
self.db = a
- }
+ }
override func tearDown() async throws {
await self.dbs.shutdownAsync()
@@ -293,8 +306,13 @@ extension DatabaseConfigurationFactory {
database: env("POSTGRES_DB_\(subconfig)") ?? "test_database",
tls: try! .prefer(.init(configuration: .makeClientConfiguration()))
)
-
- return .postgres(configuration: baseSubconfig, connectionPoolTimeout: .seconds(30), encodingContext: encodingContext, decodingContext: decodingContext)
+
+ return .postgres(
+ configuration: baseSubconfig,
+ connectionPoolTimeout: .seconds(30),
+ encodingContext: encodingContext,
+ decodingContext: decodingContext
+ )
}
}
diff --git a/Tests/FluentPostgresDriverTests/FluentPostgresTransactionControlTests.swift b/Tests/FluentPostgresDriverTests/FluentPostgresTransactionControlTests.swift
index b33a45f..4bdc52f 100644
--- a/Tests/FluentPostgresDriverTests/FluentPostgresTransactionControlTests.swift
+++ b/Tests/FluentPostgresDriverTests/FluentPostgresTransactionControlTests.swift
@@ -1,9 +1,9 @@
-import Logging
-import FluentKit
import FluentBenchmark
+import FluentKit
import FluentPostgresDriver
-import XCTest
+import Logging
import PostgresKit
+import XCTest
final class FluentPostgresTransactionControlTests: XCTestCase {
func testRollback() async throws {
@@ -33,15 +33,15 @@ final class FluentPostgresTransactionControlTests: XCTestCase {
let count2 = try await Todo.query(on: self.db).count()
XCTAssertEqual(count2, 0)
}
-
+
var eventLoopGroup: any EventLoopGroup { MultiThreadedEventLoopGroup.singleton }
var threadPool: NIOThreadPool { NIOThreadPool.singleton }
var dbs: Databases!
var db: (any Database)!
-
+
override func setUp() async throws {
try await super.setUp()
-
+
XCTAssert(isLoggingConfigured)
self.dbs = Databases(threadPool: self.threadPool, on: self.eventLoopGroup)
@@ -50,7 +50,7 @@ final class FluentPostgresTransactionControlTests: XCTestCase {
self.db = self.dbs.database(.a, logger: Logger(label: "test.fluent.a"), on: self.eventLoopGroup.any())
_ = try await (self.db as! any PostgresDatabase).query("drop schema public cascade").get()
_ = try await (self.db as! any PostgresDatabase).query("create schema public").get()
-
+
try await CreateTodo().prepare(on: self.db)
}
@@ -59,7 +59,7 @@ final class FluentPostgresTransactionControlTests: XCTestCase {
await self.dbs.shutdownAsync()
try await super.tearDown()
}
-
+
final class Todo: Model, @unchecked Sendable {
static let schema = "todos"
@@ -70,9 +70,12 @@ final class FluentPostgresTransactionControlTests: XCTestCase {
var title: String
init() {}
- init(title: String) { self.title = title; id = nil }
+ init(title: String) {
+ self.title = title
+ id = nil
+ }
}
-
+
struct CreateTodo: AsyncMigration {
func prepare(on database: any Database) async throws {
try await database.schema("todos")