diff --git a/.github/workflows/api-docs.yml b/.github/workflows/api-docs.yml
index 0fea43d..316a00d 100644
--- a/.github/workflows/api-docs.yml
+++ b/.github/workflows/api-docs.yml
@@ -3,6 +3,9 @@ on:
push:
branches:
- main
+permissions:
+ contents: read
+ id-token: write
jobs:
build-and-deploy:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 72ffa43..7191f71 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -5,22 +5,22 @@ concurrency:
on:
pull_request: { types: [opened, reopened, synchronize, ready_for_review] }
push: { branches: [ main ] }
-
+permissions:
+ contents: read
env:
LOG_LEVEL: info
- SWIFT_DETERMINISTIC_HASHING: 1
- POSTGRES_HOSTNAME: 'psql-a'
- POSTGRES_HOSTNAME_A: 'psql-a'
- POSTGRES_HOSTNAME_B: 'psql-b'
- POSTGRES_DB: 'test_database'
- POSTGRES_DB_A: 'test_database'
- POSTGRES_DB_B: 'test_database'
- POSTGRES_USER: 'test_username'
- POSTGRES_USER_A: 'test_username'
- POSTGRES_USER_B: 'test_username'
- POSTGRES_PASSWORD: 'test_password'
- POSTGRES_PASSWORD_A: 'test_password'
- POSTGRES_PASSWORD_B: 'test_password'
+ POSTGRES_HOSTNAME_A: &postgres_host_a 'psql-a'
+ POSTGRES_HOSTNAME_B: &postgres_host_b 'psql-b'
+ POSTGRES_HOSTNAME: *postgres_host_a
+ POSTGRES_DB_A: &postgres_db_a 'test_database'
+ POSTGRES_DB_B: &postgres_db_b 'test_database'
+ POSTGRES_DB: *postgres_db_a
+ POSTGRES_USER_A: &postgres_user_a 'test_username'
+ POSTGRES_USER_B: &postgres_user_b 'test_username'
+ POSTGRES_USER: *postgres_user_a
+ POSTGRES_PASSWORD_A: &postgres_pass_a 'test_password'
+ POSTGRES_PASSWORD_B: &postgres_pass_b 'test_password'
+ POSTGRES_PASSWORD: *postgres_pass_a
jobs:
api-breakage:
@@ -42,29 +42,29 @@ jobs:
fail-fast: false
matrix:
postgres-image:
- - postgres:17
- - postgres:15
- - postgres:13
+ - postgres:18
+ - postgres:16
+ - postgres:14
swift-image:
- - swift:5.10-jammy
- swift:6.0-noble
- swift:6.1-noble
+ - swift:6.2-noble
include:
- - postgres-image: postgres:17
+ - postgres-image: postgres:18
postgres-auth: scram-sha-256
- - postgres-image: postgres:15
+ - postgres-image: postgres:16
postgres-auth: md5
- - postgres-image: postgres:13
+ - postgres-image: postgres:14
postgres-auth: trust
runs-on: ubuntu-latest
container: ${{ matrix.swift-image }}
services:
- psql-a:
+ *postgres_host_a:
image: ${{ matrix.postgres-image }}
env:
- POSTGRES_USER: test_username
- POSTGRES_DB: test_database
- POSTGRES_PASSWORD: test_password
+ POSTGRES_USER: *postgres_user_a
+ POSTGRES_DB: *postgres_db_a
+ POSTGRES_PASSWORD: *postgres_pass_a
POSTGRES_HOST_AUTH_METHOD: ${{ matrix.postgres-auth }}
POSTGRES_INITDB_ARGS: --auth-host=${{ matrix.postgres-auth }}
steps:
@@ -73,7 +73,7 @@ jobs:
- name: Check out package
uses: actions/checkout@v5
- name: Run local tests
- run: swift test --enable-code-coverage
+ run: swift test --enable-code-coverage --explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable
- name: Upload coverage data
uses: vapor/swift-codecov-action@v0.3
with:
@@ -82,22 +82,22 @@ jobs:
linux-integration:
if: ${{ !(github.event.pull_request.draft || false) }}
runs-on: ubuntu-latest
- container: swift:6.1-noble
+ container: swift:6.2-noble
services:
- psql-a:
- image: postgres:17
+ *postgres_host_a:
+ image: postgres:18
env:
- POSTGRES_USER: test_username
- POSTGRES_DB: test_database
- POSTGRES_PASSWORD: test_password
+ POSTGRES_USER: *postgres_user_a
+ POSTGRES_DB: *postgres_db_a
+ POSTGRES_PASSWORD: *postgres_pass_a
POSTGRES_HOST_AUTH_METHOD: scram-sha-256
POSTGRES_INITDB_ARGS: --auth-host=scram-sha-256
- psql-b:
- image: postgres:15
+ *postgres_host_b:
+ image: postgres:16
env:
- POSTGRES_USER: test_username
- POSTGRES_DB: test_database
- POSTGRES_PASSWORD: test_password
+ POSTGRES_USER: *postgres_user_b
+ POSTGRES_DB: *postgres_db_b
+ POSTGRES_PASSWORD: *postgres_pass_b
POSTGRES_HOST_AUTH_METHOD: scram-sha-256
POSTGRES_INITDB_ARGS: --auth-host=scram-sha-256
steps:
@@ -118,10 +118,10 @@ jobs:
fail-fast: false
matrix:
include:
- - macos-version: macos-14
- xcode-version: latest-stable
- macos-version: macos-15
xcode-version: latest-stable
+ - macos-version: macos-26
+ xcode-version: latest-stable
runs-on: ${{ matrix.macos-version }}
env:
POSTGRES_HOSTNAME: 127.0.0.1
@@ -134,15 +134,15 @@ jobs:
- name: Install Postgres, setup DB and auth, and wait for server start
run: |
brew upgrade || true
- export PATH="$(brew --prefix)/opt/postgresql@13/bin:$PATH" PGDATA=/tmp/vapor-postgres-test
- brew install "postgresql@17" && brew link --force "postgresql@17"
+ export PGDATA=/tmp/vapor-postgres-test
+ brew install "postgresql@18" && brew link --force "postgresql@18"
initdb --locale=C --auth-host "scram-sha-256" -U "${POSTGRES_USER}" --pwfile=<(echo "${POSTGRES_PASSWORD}")
pg_ctl start --wait
timeout-minutes: 15
- name: Checkout code
uses: actions/checkout@v5
- name: Run local tests
- run: swift test --enable-code-coverage
+ run: swift test --enable-code-coverage --explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable
- name: Upload coverage data
uses: vapor/swift-codecov-action@v0.3
with:
@@ -150,12 +150,12 @@ jobs:
musl:
runs-on: ubuntu-latest
- container: swift:6.1-noble
+ container: swift:6.2-noble
timeout-minutes: 30
steps:
- name: Check out code
uses: actions/checkout@v5
- name: Install SDK
- run: swift sdk install https://download.swift.org/swift-6.1.2-release/static-sdk/swift-6.1.2-RELEASE/swift-6.1.2-RELEASE_static-linux-0.0.1.artifactbundle.tar.gz --checksum df0b40b9b582598e7e3d70c82ab503fd6fbfdff71fd17e7f1ab37115a0665b3b
+ run: swift sdk install https://download.swift.org/swift-6.2-release/static-sdk/swift-6.2-RELEASE/swift-6.2-RELEASE_static-linux-0.0.1.artifactbundle.tar.gz --checksum d2225840e592389ca517bbf71652f7003dbf45ac35d1e57d98b9250368769378
- name: Build
run: swift build --swift-sdk x86_64-swift-linux-musl
diff --git a/Package.swift b/Package.swift
index 324ba77..4eb6987 100644
--- a/Package.swift
+++ b/Package.swift
@@ -1,4 +1,4 @@
-// swift-tools-version:5.10
+// swift-tools-version:6.0
import PackageDescription
let package = Package(
@@ -14,7 +14,7 @@ let package = Package(
],
dependencies: [
.package(url: "https://github.com/vapor/postgres-nio.git", from: "1.27.0"),
- .package(url: "https://github.com/vapor/sql-kit.git", from: "3.33.1"),
+ .package(url: "https://github.com/vapor/sql-kit.git", from: "3.33.2"),
.package(url: "https://github.com/vapor/async-kit.git", from: "1.21.0"),
],
targets: [
@@ -40,9 +40,9 @@ let package = Package(
var swiftSettings: [SwiftSetting] { [
.enableUpcomingFeature("ExistentialAny"),
+ //.enableUpcomingFeature("InternalImportsByDefault"),
.enableUpcomingFeature("MemberImportVisibility"),
- .enableUpcomingFeature("ConciseMagicFile"),
- .enableUpcomingFeature("ForwardTrailingClosures"),
- .enableUpcomingFeature("DisableOutwardActorInference"),
- .enableExperimentalFeature("StrictConcurrency=complete"),
+ .enableUpcomingFeature("InferIsolatedConformances"),
+ //.enableUpcomingFeature("NonisolatedNonsendingByDefault"),
+ .enableUpcomingFeature("ImmutableWeakCaptures"),
] }
diff --git a/README.md b/README.md
index 4861d16..d53e3d4 100644
--- a/README.md
+++ b/README.md
@@ -6,8 +6,8 @@
-
-
+
+
diff --git a/Sources/PostgresKit/Docs.docc/theme-settings.json b/Sources/PostgresKit/Docs.docc/theme-settings.json
index 52bf00b..f13ead3 100644
--- a/Sources/PostgresKit/Docs.docc/theme-settings.json
+++ b/Sources/PostgresKit/Docs.docc/theme-settings.json
@@ -1,6 +1,6 @@
{
"theme": {
- "aside": { "border-radius": "16px", "border-style": "double", "border-width": "3px" },
+ "aside": { "border-radius": "16px", "border-width": "3px", "border-style": "double" },
"border-radius": "0",
"button": { "border-radius": "16px", "border-width": "1px", "border-style": "solid" },
"code": { "border-radius": "16px", "border-width": "1px", "border-style": "solid" },
@@ -8,9 +8,9 @@
"psqlkit": "#336791",
"documentation-intro-fill": "radial-gradient(circle at top, var(--color-psqlkit) 30%, #000 100%)",
"documentation-intro-accent": "var(--color-psqlkit)",
- "documentation-intro-eyebrow": "white",
+ "hero-eyebrow": "white",
"documentation-intro-figure": "white",
- "documentation-intro-title": "white",
+ "hero-title": "white",
"logo-base": { "dark": "#fff", "light": "#000" },
"logo-shape": { "dark": "#000", "light": "#fff" },
"fill": { "dark": "#000", "light": "#fff" }
diff --git a/Sources/PostgresKit/PostgresDataTranslation.swift b/Sources/PostgresKit/PostgresDataTranslation.swift
index eb7053c..6fb9346 100644
--- a/Sources/PostgresKit/PostgresDataTranslation.swift
+++ b/Sources/PostgresKit/PostgresDataTranslation.swift
@@ -55,11 +55,7 @@ extension URL {
}
}
-#if compiler(>=6.0)
extension URL: @retroactive PostgresNonThrowingEncodable, @retroactive PostgresDecodable {}
-#else
-extension Foundation.URL: PostgresNIO.PostgresNonThrowingEncodable, PostgresNIO.PostgresDecodable {}
-#endif
struct PostgresDataTranslation {
/// This typealias serves to limit the deprecation noise caused by `PostgresDataConvertible` to a single
diff --git a/Tests/PostgresKitTests/PostgresKitTests.swift b/Tests/PostgresKitTests/PostgresKitTests.swift
index 7253612..1b16b9f 100644
--- a/Tests/PostgresKitTests/PostgresKitTests.swift
+++ b/Tests/PostgresKitTests/PostgresKitTests.swift
@@ -3,53 +3,27 @@ import Logging
import NIOCore
import PostgresNIO
import SQLKitBenchmark
-import XCTest
+import Testing
@testable import PostgresKit
-final class PostgresKitTests: XCTestCase {
- func testSQLKitBenchmark() async throws {
- let conn = try await PostgresConnection.test(on: self.eventLoop).get()
- do {
+extension AllSuites {
+
+@Suite
+struct PostgresKitTests {
+ @Test
+ func sqlKitBenchmark() async throws {
+ let conn = try await PostgresConnection.test(on: self.eventLoop)
+
+ await #expect(throws: Never.self) {
let benchmark = SQLBenchmarker(on: conn.sql())
try await benchmark.runAllTests()
- } catch {
- try? await conn.close()
- XCTFail("Caught error: \(String(reflecting: error))")
- throw error
}
try await conn.close()
}
- // Disable for now, test is of questionable utility
- /*
- func testPerformance() throws {
- let db = PostgresConnectionSource(sqlConfiguration: .test)
- let pool = EventLoopGroupConnectionPool(
- source: db,
- maxConnectionsPerEventLoop: 2,
- on: MultiThreadedEventLoopGroup.singleton
- )
- defer { pool.shutdown() }
- // Postgres seems to take much longer on initial connections when using SCRAM-SHA-256 auth,
- // which causes XCTest to bail due to the first measurement having a very high deviation.
- // Spin the pool a bit before running the measurement to warm it up.
- for _ in 1...25 {
- _ = try pool.withConnection { conn in
- conn.query("SELECT 1")
- }.wait()
- }
- self.measure {
- for _ in 1...100 {
- _ = try! pool.withConnection { conn in
- conn.query("SELECT 1")
- }.wait()
- }
- }
- }
- */
-
- func testLeak() throws {
+ @Test
+ func leak() async throws {
struct Foo: Codable {
var id: String
var description: String?
@@ -61,29 +35,22 @@ final class PostgresKitTests: XCTestCase {
var modified_at: Date
}
- let conn = try PostgresConnection.test(on: self.eventLoop).wait()
- defer { try! conn.close().wait() }
-
+ let conn = try await PostgresConnection.test(on: self.eventLoop)
let db = conn.sql()
-
- do {
- try db.raw("DROP TABLE IF EXISTS \(ident: "foos")").run().wait()
- try db.raw("""
- CREATE TABLE \(ident: "foos") (
- \(ident: "id") TEXT PRIMARY KEY,
- \(ident: "description") TEXT,
- \(ident: "latitude") DOUBLE PRECISION,
- \(ident: "longitude") DOUBLE PRECISION,
- \(ident: "created_by") TEXT,
- \(ident: "created_at") TIMESTAMPTZ,
- \(ident: "modified_by") TEXT,
- \(ident: "modified_at") TIMESTAMPTZ
- )
- """).run().wait()
- defer {
- try? db.raw("DROP TABLE IF EXISTS \(ident: "foos")").run().wait()
- }
-
+
+ await #expect(throws: Never.self) {
+ try await db.drop(table: "foos").ifExists().run()
+ try await db.create(table: "foos")
+ .column("id", type: .text, .primaryKey(autoIncrement: false))
+ .column("description", type: .text)
+ .column("latitude", type: .custom(SQLRaw("DOUBLE PRECISION")))
+ .column("longitude", type: .custom(SQLRaw("DOUBLE PRECISION")))
+ .column("created_by", type: .text)
+ .column("created_at", type: .custom(SQLRaw("TIMESTAMPTZ")))
+ .column("modified_by", type: .text)
+ .column("modified_at", type: .custom(SQLRaw("TIMESTAMPTZ")))
+ .run()
+
for i in 0..<5_000 {
let zipcode = Foo(
id: UUID().uuidString,
@@ -95,79 +62,91 @@ final class PostgresKitTests: XCTestCase {
modified_by: "test",
modified_at: Date()
)
- try db.insert(into: "foos")
+ try await db.insert(into: "foos")
.model(zipcode)
- .run().wait()
+ .run()
}
- } catch {
- XCTFail("Caught error: \(String(reflecting: error))")
}
+ try? await db.raw("DROP TABLE IF EXISTS \(ident: "foos")").run()
+ try await conn.close()
}
- func testArrayEncoding() throws {
- let conn = try PostgresConnection.test(on: self.eventLoop).wait()
- defer { try! conn.close().wait() }
-
+ @Test
+ func arrayEncoding() async throws {
+ let conn = try await PostgresConnection.test(on: self.eventLoop)
+
struct Foo: Codable {
var bar: Int
}
- let foos: [Foo] = [.init(bar: 1), .init(bar: 2)]
- try conn.sql().raw("SELECT \(bind: foos)::JSONB[] as \(ident: "foos")")
- .run().wait()
+
+ await #expect(throws: Never.self) {
+ let foos: [Foo] = [.init(bar: 1), .init(bar: 2)]
+ try await conn.sql().raw("SELECT \(bind: foos)::JSONB[] as \(ident: "foos")").run()
+ }
+ try await conn.close()
}
- func testDecodeModelWithNil() throws {
- let conn = try PostgresConnection.test(on: self.eventLoop).wait()
- defer { try! conn.close().wait() }
+ @Test
+ func decodeModelWithNil() async throws {
+ let conn = try await PostgresConnection.test(on: self.eventLoop)
- let rows = try conn.sql().raw("SELECT \(literal: "foo")::text as \(ident: "foo"), \(SQLLiteral.null) as \(ident: "bar"), \(literal: "baz")::text as \(ident: "baz")").all().wait()
- let row = rows[0]
-
- struct Test: Codable {
- var foo: String
- var bar: String?
- var baz: String?
- }
+ await #expect(throws: Never.self) {
+ let rows = try await conn.sql().raw("SELECT \(literal: "foo")::text as \(ident: "foo"), \(SQLLiteral.null) as \(ident: "bar"), \(literal: "baz")::text as \(ident: "baz")").all()
+ let row = rows[0]
+
+ struct Test: Codable {
+ var foo: String
+ var bar: String?
+ var baz: String?
+ }
- let test = try row.decode(model: Test.self)
- XCTAssertEqual(test.foo, "foo")
- XCTAssertEqual(test.bar, nil)
- XCTAssertEqual(test.baz, "baz")
+ let test = try row.decode(model: Test.self)
+ #expect(test.foo == "foo")
+ #expect(test.bar == nil)
+ #expect(test.baz == "baz")
+ }
+ try await conn.close()
}
- func testEventLoopGroupSQL() throws {
+ @Test
+ func eventLoopGroupSQL() async throws {
var configuration = SQLPostgresConfiguration.test
configuration.searchPath = ["foo", "bar", "baz"]
let source = PostgresConnectionSource(sqlConfiguration: configuration)
let pool = EventLoopGroupConnectionPool(source: source, on: MultiThreadedEventLoopGroup.singleton)
- defer { pool.shutdown() }
let db = pool.database(logger: .init(label: "test")).sql()
- let rows = try db.raw("SELECT version()").all().wait()
- XCTAssertEqual(rows.count, 1)
+ await #expect(throws: Never.self) {
+ try await #expect(db.raw("SELECT version()").all().count == 1)
+ }
+ try await pool.shutdownAsync()
}
- func testIntegerArrayEncoding() throws {
- let connection = try PostgresConnection.test(on: self.eventLoop).wait()
- defer { try! connection.close().wait() }
- let sql = connection.sql()
- _ = try sql.raw("DROP TABLE IF EXISTS \(ident: "foo")").run().wait()
- _ = try sql.raw("CREATE TABLE \(ident: "foo") (\(ident: "bar") bigint[] not null)").run().wait()
- defer {
- _ = try! sql.raw("DROP TABLE IF EXISTS \(ident: "foo")").run().wait()
+ @Test
+ func integerArrayEncoding() async throws {
+ let connection = try await PostgresConnection.test(on: self.eventLoop)
+
+ await #expect(throws: Never.self) {
+ let sql = connection.sql()
+ _ = try await sql.raw("DROP TABLE IF EXISTS \(ident: "foo")").run()
+ try await sql.withSession { db in
+ _ = try await db.create(table: "foo").column("bar", type: .custom(SQLRaw("bigint[]")), .notNull).run()
+ _ = try await db.insert(into: "foo").columns("bar").values(SQLBind([Bar]())).run()
+ let rows = try await connection.query("SELECT bar FROM foo", logger: connection.logger).collect()
+ #expect(rows.count == 1)
+ #expect(rows.first?.count == 1)
+ #expect(rows.first?.first?.dataType == Bar.psqlArrayType)
+ #expect(try rows.first?.first?.decode([Bar].self) == [Bar]())
+ }
}
- _ = try sql.raw("INSERT INTO \(ident: "foo") (\(ident: "bar")) VALUES (\(bind: [Bar]()))").run().wait()
- let rows = try connection.query("SELECT bar FROM foo", logger: connection.logger).wait()
- XCTAssertEqual(rows.count, 1)
- XCTAssertEqual(rows.first?.count, 1)
- XCTAssertEqual(rows.first?.first?.dataType, Bar.psqlArrayType)
- XCTAssertEqual(try rows.first?.first?.decode([Bar].self), [Bar]())
+ try await connection.close()
}
/// Tests dealing with encoding of values whose `encode(to:)` implementation calls one of the `superEncoder()`
/// methods (most notably the implementation of `Codable` for Fluent's `Fields`, which we can't directly test
/// at this layer).
- func testValuesThatUseSuperEncoder() throws {
+ @Test
+ func valuesThatUseSuperEncoder() throws {
struct UnusualType: Codable {
var prop1: String, prop2: [Bool], prop3: [[Bool]]
@@ -207,53 +186,57 @@ final class PostgresKitTests: XCTestCase {
let encoded1 = try PostgresDataTranslation.encode(codingPath: [], userInfo: [:], value: instance, in: .default, file: #fileID, line: #line)
let encoded2 = try PostgresDataTranslation.encode(codingPath: [], userInfo: [:], value: [instance, instance], in: .default, file: #fileID, line: #line)
- XCTAssertEqual(encoded1.type, .jsonb)
- XCTAssertEqual(encoded2.type, .jsonbArray)
-
+ #expect(encoded1.type == .jsonb)
+ #expect(encoded2.type == .jsonbArray)
+
let decoded1 = try PostgresDataTranslation.decode(UnusualType.self, from: .init(bytes: encoded1.value, dataType: encoded1.type, format: encoded1.formatCode, columnName: "", columnIndex: -1), in: .default)
let decoded2 = try PostgresDataTranslation.decode([UnusualType].self, from: .init(bytes: encoded2.value, dataType: encoded2.type, format: encoded2.formatCode, columnName: "", columnIndex: -1), in: .default)
- XCTAssertEqual(decoded1.prop3, instance.prop3)
- XCTAssertEqual(decoded2.count, 2)
+ #expect(decoded1.prop3 == instance.prop3)
+ #expect(decoded2.count == 2)
}
-
- func testFluentWorkaroundsDecoding() throws {
+
+ @Test
+ func fluentWorkaroundsDecoding() throws {
// SQLKit benchmarks already test enum handling
// Text encoding for Decimal
let decimalBuffer = ByteBuffer(string: Decimal(12345.6789).description)
var decimalValue: Decimal?
- XCTAssertNoThrow(decimalValue = try PostgresDataTranslation.decode(Decimal.self, from: .init(bytes: decimalBuffer, dataType: .numeric, format: .text, columnName: "", columnIndex: -1), in: .default))
- XCTAssertEqual(decimalValue, Decimal(12345.6789))
-
+ #expect(throws: Never.self) { decimalValue = try PostgresDataTranslation.decode(Decimal.self, from: .init(bytes: decimalBuffer, dataType: .numeric, format: .text, columnName: "", columnIndex: -1), in: .default) }
+ #expect(decimalValue == Decimal(12345.6789))
+
// Decoding Double from NUMERIC
let numericBuffer = PostgresData(numeric: .init(decimal: 12345.6789)).value
var numericValue: Double?
- XCTAssertNoThrow(numericValue = try PostgresDataTranslation.decode(Double.self, from: .init(bytes: numericBuffer, dataType: .numeric, format: .binary, columnName: "", columnIndex: -1), in: .default))
- XCTAssertEqual(numericValue, Double(Decimal(12345.6789).description))
+ #expect(throws: Never.self) { numericValue = try PostgresDataTranslation.decode(Double.self, from: .init(bytes: numericBuffer, dataType: .numeric, format: .binary, columnName: "", columnIndex: -1), in: .default) }
+ #expect(numericValue == Double(Decimal(12345.6789).description))
}
-
- func testURLWorkaroundDecoding() throws {
+
+ @Test
+ func urlWorkaroundDecoding() throws {
let url = URL(string: "https://user:pass@www.example.com:8080/path/to/endpoint?query=value#fragment")!
let encodedNormal = try PostgresDataTranslation.encode(codingPath: [], userInfo: [:], value: url, in: .default, file: #fileID, line: #line)
- XCTAssertEqual(encodedNormal.value?.getString(at: 0, length: encodedNormal.value?.readableBytes ?? 0), url.absoluteString)
-
+ #expect(encodedNormal.value?.getString(at: 0, length: encodedNormal.value?.readableBytes ?? 0) == url.absoluteString)
+
let encodedBroken = try PostgresDataTranslation.encode(codingPath: [], userInfo: [:], value: "\"\(url.absoluteString)\"", in: .default, file: #fileID, line: #line)
- XCTAssertEqual(try PostgresDataTranslation.decode(URL.self, from: .init(with: encodedNormal), in: .default), url)
- XCTAssertEqual(try PostgresDataTranslation.decode(URL.self, from: .init(with: encodedBroken), in: .default), url)
+ #expect(try PostgresDataTranslation.decode(URL.self, from: .init(with: encodedNormal), in: .default) == url)
+ #expect(try PostgresDataTranslation.decode(URL.self, from: .init(with: encodedBroken), in: .default) == url)
}
var eventLoop: any EventLoop {
MultiThreadedEventLoopGroup.singleton.any()
}
- override class func setUp() {
- XCTAssertTrue(isLoggingConfigured)
+ init() {
+ #expect(isLoggingConfigured)
}
}
+}
+
extension PostgresCell {
fileprivate init(with data: PostgresData) {
self.init(bytes: data.value, dataType: data.type, format: data.formatCode, columnName: "", columnIndex: -1)
diff --git a/Tests/PostgresKitTests/SQLPostgresConfigurationTests.swift b/Tests/PostgresKitTests/SQLPostgresConfigurationTests.swift
index 8281122..e064839 100644
--- a/Tests/PostgresKitTests/SQLPostgresConfigurationTests.swift
+++ b/Tests/PostgresKitTests/SQLPostgresConfigurationTests.swift
@@ -1,75 +1,83 @@
import PostgresKit
-import XCTest
+import Testing
-final class SQLPostgresConfigurationTests: XCTestCase {
- func testURLHandling() throws {
+extension AllSuites {
+@Suite
+struct SQLPostgresConfigurationTests {
+ @Test
+ func urlHandling() throws {
let config1 = try SQLPostgresConfiguration(url: "postgres+tcp://test_username:test_password@test_hostname:9999/test_database?tlsmode=disable")
- XCTAssertEqual(config1.coreConfiguration.database, "test_database")
- XCTAssertEqual(config1.coreConfiguration.password, "test_password")
- XCTAssertEqual(config1.coreConfiguration.username, "test_username")
- XCTAssertEqual(config1.coreConfiguration.host, "test_hostname")
- XCTAssertEqual(config1.coreConfiguration.port, 9999)
- XCTAssertNil(config1.coreConfiguration.unixSocketPath)
- XCTAssertFalse(config1.coreConfiguration.tls.isAllowed)
- XCTAssertFalse(config1.coreConfiguration.tls.isEnforced)
+ #expect(config1.coreConfiguration.database == "test_database")
+ #expect(config1.coreConfiguration.password == "test_password")
+ #expect(config1.coreConfiguration.username == "test_username")
+ #expect(config1.coreConfiguration.host == "test_hostname")
+ #expect(config1.coreConfiguration.port == 9999)
+ #expect(config1.coreConfiguration.unixSocketPath == nil)
+ #expect(!config1.coreConfiguration.tls.isAllowed)
+ #expect(!config1.coreConfiguration.tls.isEnforced)
let config2 = try SQLPostgresConfiguration(url: "postgres+tcp://test_username@test_hostname")
- XCTAssertNil(config2.coreConfiguration.database)
- XCTAssertNil(config2.coreConfiguration.password)
- XCTAssertEqual(config2.coreConfiguration.username, "test_username")
- XCTAssertEqual(config2.coreConfiguration.host, "test_hostname")
- XCTAssertEqual(config2.coreConfiguration.port, SQLPostgresConfiguration.ianaPortNumber)
- XCTAssertNil(config2.coreConfiguration.unixSocketPath)
- XCTAssertTrue(config2.coreConfiguration.tls.isAllowed)
- XCTAssertFalse(config2.coreConfiguration.tls.isEnforced)
+ #expect(config2.coreConfiguration.database == nil)
+ #expect(config2.coreConfiguration.password == nil)
+ #expect(config2.coreConfiguration.username == "test_username")
+ #expect(config2.coreConfiguration.host == "test_hostname")
+ #expect(config2.coreConfiguration.port == SQLPostgresConfiguration.ianaPortNumber)
+ #expect(config2.coreConfiguration.unixSocketPath == nil)
+ #expect(config2.coreConfiguration.tls.isAllowed)
+ #expect(!config2.coreConfiguration.tls.isEnforced)
let config3 = try SQLPostgresConfiguration(url: "postgres+uds://test_username:test_password@localhost/tmp/postgres.sock?tlsmode=require#test_database")
- XCTAssertEqual(config3.coreConfiguration.database, "test_database")
- XCTAssertEqual(config3.coreConfiguration.password, "test_password")
- XCTAssertEqual(config3.coreConfiguration.username, "test_username")
- XCTAssertNil(config3.coreConfiguration.host)
- XCTAssertNil(config3.coreConfiguration.port)
- XCTAssertEqual(config3.coreConfiguration.unixSocketPath, "/tmp/postgres.sock")
- XCTAssertTrue(config3.coreConfiguration.tls.isAllowed)
- XCTAssertTrue(config3.coreConfiguration.tls.isEnforced)
+ #expect(config3.coreConfiguration.database == "test_database")
+ #expect(config3.coreConfiguration.password == "test_password")
+ #expect(config3.coreConfiguration.username == "test_username")
+ #expect(config3.coreConfiguration.host == nil)
+ #expect(config3.coreConfiguration.port == nil)
+ #expect(config3.coreConfiguration.unixSocketPath == "/tmp/postgres.sock")
+ #expect(config3.coreConfiguration.tls.isAllowed)
+ #expect(config3.coreConfiguration.tls.isEnforced)
let config4 = try SQLPostgresConfiguration(url: "postgres+uds://test_username@/tmp/postgres.sock")
- XCTAssertNil(config4.coreConfiguration.database)
- XCTAssertNil(config4.coreConfiguration.password)
- XCTAssertEqual(config4.coreConfiguration.username, "test_username")
- XCTAssertNil(config4.coreConfiguration.host)
- XCTAssertNil(config4.coreConfiguration.port)
- XCTAssertEqual(config4.coreConfiguration.unixSocketPath, "/tmp/postgres.sock")
- XCTAssertFalse(config4.coreConfiguration.tls.isAllowed)
- XCTAssertFalse(config4.coreConfiguration.tls.isEnforced)
-
+ #expect(config4.coreConfiguration.database == nil)
+ #expect(config4.coreConfiguration.password == nil)
+ #expect(config4.coreConfiguration.username == "test_username")
+ #expect(config4.coreConfiguration.host == nil)
+ #expect(config4.coreConfiguration.port == nil)
+ #expect(config4.coreConfiguration.unixSocketPath == "/tmp/postgres.sock")
+ #expect(!config4.coreConfiguration.tls.isAllowed)
+ #expect(!config4.coreConfiguration.tls.isEnforced)
+
for modestr in ["tlsmode=false", "tlsmode=verify-full&tlsmode=disable"] {
let config = try SQLPostgresConfiguration(url: "postgres://u@h?\(modestr)")
- XCTAssertFalse(config.coreConfiguration.tls.isAllowed)
- XCTAssertFalse(config.coreConfiguration.tls.isEnforced)
+ #expect(!config.coreConfiguration.tls.isAllowed)
+ #expect(!config.coreConfiguration.tls.isEnforced)
}
for modestr in ["tlsmode=prefer", "tlsmode=allow", "tlsmode=true"] {
let config = try SQLPostgresConfiguration(url: "postgres://u@h?\(modestr)")
- XCTAssertTrue(config.coreConfiguration.tls.isAllowed)
- XCTAssertFalse(config.coreConfiguration.tls.isEnforced)
+ #expect(config.coreConfiguration.tls.isAllowed)
+ #expect(!config.coreConfiguration.tls.isEnforced)
}
for modestr in ["tlsmode=require", "tlsmode=verify-ca", "tlsmode=verify-full", "tls=verify-full", "ssl=verify-full", "tlsmode=prefer&sslmode=verify-full"] {
let config = try SQLPostgresConfiguration(url: "postgres://u@h?\(modestr)")
- XCTAssertTrue(config.coreConfiguration.tls.isAllowed)
- XCTAssertTrue(config.coreConfiguration.tls.isEnforced)
+ #expect(config.coreConfiguration.tls.isAllowed)
+ #expect(config.coreConfiguration.tls.isEnforced)
}
- XCTAssertNoThrow(try SQLPostgresConfiguration(url: "postgresql://test_username@test_hostname"))
- XCTAssertNoThrow(try SQLPostgresConfiguration(url: "postgresql+tcp://test_username@test_hostname"))
- XCTAssertNoThrow(try SQLPostgresConfiguration(url: "postgresql+uds://test_username@/tmp/postgres.sock"))
-
- XCTAssertThrowsError(try SQLPostgresConfiguration(url: "postgres+tcp://test_hostname"), "should fail when username missing")
- XCTAssertThrowsError(try SQLPostgresConfiguration(url: "postgres+tcp://test_username@test_hostname?tlsmode=absurd"), "should fail when TLS mode invalid")
- XCTAssertThrowsError(try SQLPostgresConfiguration(url: "postgres+uds://localhost/tmp/postgres.sock?tlsmode=require"), "should fail when username missing")
- XCTAssertThrowsError(try SQLPostgresConfiguration(url: "postgres+uds:///tmp/postgres.sock"), "should fail when authority missing")
- XCTAssertThrowsError(try SQLPostgresConfiguration(url: "postgres+uds://username@localhost/"), "should fail when path missing")
- XCTAssertThrowsError(try SQLPostgresConfiguration(url: "postgres+uds://username@remotehost/tmp"), "should fail when authority not localhost or empty")
+ #expect(throws: Never.self) { try SQLPostgresConfiguration(url: "postgresql://test_username@test_hostname") }
+ #expect(throws: Never.self) { try SQLPostgresConfiguration(url: "postgresql+tcp://test_username@test_hostname") }
+ #expect(throws: Never.self) { try SQLPostgresConfiguration(url: "postgresql+uds://test_username@/tmp/postgres.sock") }
+
+ #expect(throws: (any Error).self, "should fail when username missing") { try SQLPostgresConfiguration(url: "postgres+tcp://test_hostname") }
+ #expect(throws: (any Error).self, "should fail when TLS mode invalid") { try SQLPostgresConfiguration(url: "postgres+tcp://test_username@test_hostname?tlsmode=absurd") }
+ #expect(throws: (any Error).self, "should fail when username missing") { try SQLPostgresConfiguration(url: "postgres+uds://localhost/tmp/postgres.sock?tlsmode=require") }
+ #expect(throws: (any Error).self, "should fail when authority missing") { try SQLPostgresConfiguration(url: "postgres+uds:///tmp/postgres.sock") }
+ #expect(throws: (any Error).self, "should fail when path missing") { try SQLPostgresConfiguration(url: "postgres+uds://username@localhost/") }
+ #expect(throws: (any Error).self, "should fail when authority not localhost or empty") { try SQLPostgresConfiguration(url: "postgres+uds://username@remotehost/tmp") }
}
+
+ init() {
+ #expect(isLoggingConfigured)
+ }
+}
}
diff --git a/Tests/PostgresKitTests/Utilities.swift b/Tests/PostgresKitTests/Utilities.swift
index ed2d189..ec2ad58 100644
--- a/Tests/PostgresKitTests/Utilities.swift
+++ b/Tests/PostgresKitTests/Utilities.swift
@@ -3,14 +3,14 @@ import Logging
import NIOCore
import PostgresKit
import PostgresNIO
-import XCTest
+import Testing
extension PostgresConnection {
- static func test(on eventLoop: any EventLoop) -> EventLoopFuture {
- PostgresConnectionSource(sqlConfiguration: .test).makeConnection(
+ static func test(on eventLoop: any EventLoop) async throws -> PostgresConnection {
+ try await PostgresConnectionSource(sqlConfiguration: .test).makeConnection(
logger: .init(label: "vapor.codes.postgres-kit.test"),
on: eventLoop
- )
+ ).get()
}
}
@@ -32,10 +32,39 @@ func env(_ name: String) -> String? {
}
let isLoggingConfigured: Bool = {
- LoggingSystem.bootstrap { label in
- var handler = StreamLogHandler.standardOutput(label: label)
- handler.logLevel = env("LOG_LEVEL").flatMap { .init(rawValue: $0) } ?? .info
- return handler
- }
+ LoggingSystem.bootstrap { QuickLogHandler(label: $0, level: env("LOG_LEVEL").flatMap { .init(rawValue: $0) } ?? .info) }
return true
}()
+
+struct QuickLogHandler: LogHandler {
+ private let label: String
+ var logLevel = Logger.Level.info, metadataProvider = LoggingSystem.metadataProvider, metadata = Logger.Metadata()
+ subscript(metadataKey key: String) -> Logger.Metadata.Value? { get { self.metadata[key] } set { self.metadata[key] = newValue } }
+ init(label: String, level: Logger.Level) { (self.label, self.logLevel) = (label, level) }
+ func log(level: Logger.Level, message: Logger.Message, metadata: Logger.Metadata?, source: String, file: String, function: String, line: UInt) {
+ print("\(self.timestamp()) \(level) \(self.label):\(self.prettify(metadata ?? [:]).map { " \($0)" } ?? "") [\(source)] \(message)")
+ }
+ private func prettify(_ metadata: Logger.Metadata) -> String? {
+ self.metadata.merging(self.metadataProvider?.get() ?? [:]) { $1 }.merging(metadata) { $1 }.sorted { $0.0 < $1.0 }.map { "\($0)=\($1.mvDesc)" }.joined(separator: " ")
+ }
+ private func timestamp() -> String { .init(unsafeUninitializedCapacity: 255) { buffer in
+ var timestamp = time(nil)
+ return localtime(×tamp).map { strftime(buffer.baseAddress!, buffer.count, "%Y-%m-%dT%H:%M:%S%z", $0) } ?? buffer.initialize(fromContentsOf: "".utf8)
+ } }
+}
+extension Logger.MetadataValue {
+ var mvDesc: String { switch self {
+ case .dictionary(let dict): "[\(dict.mapValues(\.mvDesc).lazy.sorted { $0.0 < $1.0 }.map { "\($0): \($1)" }.joined(separator: ", "))]"
+ case .array(let list): "[\(list.map(\.mvDesc).joined(separator: ", "))]"
+ case .string(let str): #""\#(str)""#
+ case .stringConvertible(let repr): switch repr {
+ case let repr as Bool: "\(repr)"
+ case let repr as any FixedWidthInteger: "\(repr)"
+ case let repr as any BinaryFloatingPoint: "\(repr)"
+ default: #""\#(String(describing: repr))""#
+ }
+ } }
+}
+
+@Suite(.serialized)
+struct AllSuites {}