Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ let package = Package(
dependencies: [
.package(url: "https://github.com/apple/swift-log", from: "1.6.0"),
.package(url: "https://github.com/vapor/postgres-nio", from: "1.27.0"),
.package(url: "https://github.com/feather-framework/feather-database", exact: "1.0.0-beta.4"),
// .package(url: "https://github.com/feather-framework/feather-database", exact: "1.0.0-beta.4"),
.package(url: "https://github.com/feather-framework/feather-database", branch: "main"),
// [docc-plugin-placeholder]
],
targets: [
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
Postgres driver implementation for the abstract [Feather Database](https://github.com/feather-framework/feather-database) Swift API package.

[
![Release: 1.0.0-beta.3](https://img.shields.io/badge/Release-1%2E0%2E0--beta%2E3-F05138)
![Release: 1.0.0-beta.4](https://img.shields.io/badge/Release-1%2E0%2E0--beta%2E4-F05138)
](
https://github.com/feather-framework/feather-postgres-database/releases/tag/1.0.0-beta.3
https://github.com/feather-framework/feather-postgres-database/releases/tag/1.0.0-beta.4
)

## Features
Expand Down Expand Up @@ -37,7 +37,7 @@ Postgres driver implementation for the abstract [Feather Database](https://githu
Add the dependency to your `Package.swift`:

```swift
.package(url: "https://github.com/feather-framework/feather-postgres-database", exact: "1.0.0-beta.3"),
.package(url: "https://github.com/feather-framework/feather-postgres-database", exact: "1.0.0-beta.4"),
```

Then add `FeatherPostgresDatabase` to your target dependencies:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ extension DatabaseQuery {
.replacing("{{\(idx)}}", with: "$\(idx)")

switch binding.binding {
case .bool(let value):
postgresBindings.append(value)
case .int(let value):
postgresBindings.append(value)
case .double(let value):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -792,6 +792,35 @@ struct FeatherPostgresDatabaseTestSuite {
}
}

@Test
func transactionClosureErrorPropagates() async throws {
try await runUsingTestDatabaseClient { database in
enum TestError: Error, Equatable {
case boom
}

do {
_ = try await database.withTransaction { _ in
throw TestError.boom
}
Issue.record("Expected transaction error to be thrown.")
}
catch DatabaseError.transaction(let error) {
#expect(error.beginError == nil)
#expect(error.commitError == nil)
#expect(error.rollbackError == nil)
#expect((error.closureError as? TestError) == .boom)
#expect(error.file.isEmpty == false)
#expect(error.line > 0)
}
catch {
Issue.record(
"Expected database transaction error to be thrown."
)
}
}
}

@Test
func concurrentTransactionUpdates() async throws {
try await runUsingTestDatabaseClient { database in
Expand Down Expand Up @@ -1122,6 +1151,217 @@ struct FeatherPostgresDatabaseTestSuite {
}
}

@Test
func nullDecodingThrowsTypeMismatch() async throws {
try await runUsingTestDatabaseClient { database in
let suffix = randomTableSuffix()
let table = "nullable_values_\(suffix)"

try await database.withConnection { connection in

try await connection.run(
query: #"""
DROP TABLE IF EXISTS "\#(unescaped: table)" CASCADE;
"""#
)
try await connection.run(
query: #"""
CREATE TABLE "\#(unescaped: table)" (
"id" INTEGER NOT NULL PRIMARY KEY,
"value" INTEGER
);
"""#
)

try await connection.run(
query: #"""
INSERT INTO "\#(unescaped: table)"
("id", "value")
VALUES
(1, NULL);
"""#
)

let result =
try await connection.run(
query: #"""
SELECT "value"
FROM "\#(unescaped: table)";
"""#
) { try await $0.collect() }

#expect(result.count == 1)

do {
_ = try result[0].decode(column: "value", as: Int.self)
Issue.record("Expected decoding NULL as Int to throw.")
}
catch let DecodingError.typeMismatch(_, context) {
#expect(
context.debugDescription.contains(
"PostgresDecodingError"
)
)
}
catch {
Issue.record(
"Expected a typeMismatch error when decoding NULL as Int."
)
}
}
}
}

@Test
func nonPostgresDecodableTypeMismatch() async throws {
try await runUsingTestDatabaseClient { database in
let suffix = randomTableSuffix()
let table = "custom_types_\(suffix)"

struct CustomValue: Decodable, Sendable {
let value: String
}

try await database.withConnection { connection in

try await connection.run(
query: #"""
DROP TABLE IF EXISTS "\#(unescaped: table)" CASCADE;
"""#
)
try await connection.run(
query: #"""
CREATE TABLE "\#(unescaped: table)" (
"id" INTEGER NOT NULL PRIMARY KEY,
"value" TEXT NOT NULL
);
"""#
)

try await connection.run(
query: #"""
INSERT INTO "\#(unescaped: table)"
("id", "value")
VALUES
(1, 'alpha');
"""#
)

let result =
try await connection.run(
query: #"""
SELECT "value"
FROM "\#(unescaped: table)";
"""#
) { try await $0.collect() }

#expect(result.count == 1)

do {
_ = try result[0]
.decode(
column: "value",
as: CustomValue.self
)
Issue.record(
"Expected decoding non-PostgresDecodable type to throw."
)
}
catch let DecodingError.typeMismatch(_, context) {
#expect(
context.debugDescription.contains(
"Data is not convertible"
)
)
}
catch {
Issue.record(
"Expected a typeMismatch error for non-PostgresDecodable types."
)
}
}
}
}

@Test
func postgresRowDecodeMetatypeMismatch() async throws {
try await runUsingTestDatabaseClient { database in
let suffix = randomTableSuffix()
let table = "metatype_mismatch_\(suffix)"

func decodeWithMismatchedMetatype<T: Decodable, U: Decodable>(
row: DatabaseRow,
column: String,
as _: T.Type,
using _: U.Type
) throws -> T {
// Force a mismatched metatype to exercise the guard cast failure.
let mismatchedType = unsafeBitCast(U.self, to: T.Type.self)
return try row.decode(column: column, as: mismatchedType)
}

try await database.withConnection { connection in

try await connection.run(
query: #"""
DROP TABLE IF EXISTS "\#(unescaped: table)" CASCADE;
"""#
)
try await connection.run(
query: #"""
CREATE TABLE "\#(unescaped: table)" (
"id" INTEGER NOT NULL PRIMARY KEY,
"value" TEXT NOT NULL
);
"""#
)

try await connection.run(
query: #"""
INSERT INTO "\#(unescaped: table)"
("id", "value")
VALUES
(1, 'abc');
"""#
)

let result =
try await connection.run(
query: #"""
SELECT "value"
FROM "\#(unescaped: table)";
"""#
) { try await $0.collect() }

#expect(result.count == 1)

do {
_ = try decodeWithMismatchedMetatype(
row: result[0],
column: "value",
as: Int.self,
using: String.self
)
Issue.record(
"Expected mismatched metatype decode to throw."
)
}
catch let DecodingError.typeMismatch(_, context) {
#expect(
context.debugDescription.contains(
"Could not convert data"
)
)
}
catch {
Issue.record(
"Expected a typeMismatch error for metatype mismatch."
)
}
}
}
}

@Test
func queryFailureErrorText() async throws {
try await runUsingTestDatabaseClient { database in
Expand Down