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
1 change: 1 addition & 0 deletions .github/workflows/deployment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
tags:
- 'v*'
- '[0-9]*'
workflow_dispatch:

jobs:

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
format_check_enabled : true
broken_symlink_check_enabled : true
unacceptable_language_check_enabled : true
shell_check_enabled : true
shell_check_enabled : false
docs_check_enabled : false
api_breakage_check_enabled : false
license_header_check_enabled : false
Expand All @@ -27,7 +27,7 @@ jobs:
uses: BinaryBirds/github-workflows/.github/workflows/extra_soundness.yml@main
with:
local_swift_dependencies_check_enabled : true
headers_check_enabled : true
headers_check_enabled : false
docc_warnings_check_enabled : true

swiftlang_tests:
Expand Down
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ SHELL=/bin/bash

baseUrl = https://raw.githubusercontent.com/BinaryBirds/github-workflows/refs/heads/main/scripts

check: symlinks language deps lint headers
check: symlinks language deps lint headers docc-warnings package

package:
curl -s $(baseUrl)/check-swift-package.sh | bash

symlinks:
curl -s $(baseUrl)/check-broken-symlinks.sh | bash
Expand Down
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.

2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ 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.2"),
.package(url: "https://github.com/feather-framework/feather-database", exact: "1.0.0-beta.3"),
// [docc-plugin-placeholder]
],
targets: [
Expand Down
75 changes: 33 additions & 42 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,31 @@

Postgres driver implementation for the abstract [Feather Database](https://github.com/feather-framework/feather-database) Swift API package.

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

## Features

- 🤝 Postgres driver for Feather Database
- 😱 Automatic query parameter escaping via Swift string interpolation.
- 🔄 Async sequence query results with `Decodable` row support.
- 🧵 Designed for modern Swift concurrency
- 📚 DocC-based API Documentation
- Unit tests and code coverage
- Postgres driver for Feather Database
- Automatic query parameter escaping via Swift string interpolation.
- Async sequence query results with `Decodable` row support.
- Designed for modern Swift concurrency
- DocC-based API Documentation
- Unit tests and code coverage

## Requirements

![Swift 6.1+](https://img.shields.io/badge/Swift-6%2E1%2B-F05138)
![Platforms: Linux, macOS, iOS, tvOS, watchOS, visionOS](https://img.shields.io/badge/Platforms-Linux_%7C_macOS_%7C_iOS_%7C_tvOS_%7C_watchOS_%7C_visionOS-F05138)

- Swift 6.1+

- Platforms:
- Linux
- macOS 15+
- iOS 18+
- tvOS 18+
- watchOS 11+
- visionOS 2+
- Platforms:
- Linux
- macOS 15+
- iOS 18+
- tvOS 18+
- watchOS 11+
- visionOS 2+

## Installation

Expand All @@ -46,19 +42,13 @@ Then add `FeatherPostgresDatabase` to your target dependencies:
.product(name: "FeatherPostgresDatabase", package: "feather-postgres-database"),
```


## Usage

[
![DocC API documentation](https://img.shields.io/badge/DocC-API_documentation-F05138)
](
https://feather-framework.github.io/feather-postgres-database/documentation/featherpostgresdatabase/
)

API documentation is available at the following link.
API documentation is available at the link below:

> [!TIP]
> Avoid calling `database.execute` while in a transaction; use the transaction `connection` instead.
[![DocC API documentation](https://img.shields.io/badge/DocC-API_documentation-F05138)](https://feather-framework.github.io/feather-postgres-database/)

Here is a brief example:

```swift
import Logging
Expand Down Expand Up @@ -100,14 +90,16 @@ try await withThrowingTaskGroup(of: Void.self) { group in
}
// execute some query
group.addTask {
let result = try await database.execute(
query: #"""
SELECT
version() AS "version"
WHERE
1=\#(1);
"""#
)
let result = try await database.withConnection { connection in
try await connection.run(
query: #"""
SELECT
version() AS "version"
WHERE
1=\#(1);
"""#
)
}

for try await item in result {
let version = try item.decode(column: "version", as: String.self)
Expand All @@ -122,7 +114,6 @@ try await withThrowingTaskGroup(of: Void.self) { group in
> [!WARNING]
> This repository is a work in progress, things can break until it reaches v1.0.0.


## Other database drivers

The following database driver implementations are available for use:
Expand All @@ -133,12 +124,12 @@ The following database driver implementations are available for use:
## Development

- Build: `swift build`
- Test:
- local: `swift test`
- using Docker: `make docker-test`
- Test:
- local: `swift test`
- using Docker: `make docker-test`
- Format: `make format`
- Check: `make check`

## Contributing

[Pull requests](https://github.com/feather-framework/feather-postgres-database/pulls) are welcome. Please keep changes focused and include tests for new logic. 🙏
[Pull requests](https://github.com/feather-framework/feather-postgres-database/pulls) are welcome. Please keep changes focused and include tests for new logic.
37 changes: 0 additions & 37 deletions Sources/FeatherPostgresDatabase/PostgresConnection.swift

This file was deleted.

58 changes: 31 additions & 27 deletions Sources/FeatherPostgresDatabase/PostgresDatabaseClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,13 @@ import FeatherDatabase
import Logging
import PostgresNIO

/// Make Postgres transaction errors conform to `DatabaseTransactionError`.
///
/// This allows Postgres errors to flow through `DatabaseError`.
extension PostgresTransactionError: @retroactive DatabaseTransactionError {}

/// A Postgres-backed database client.
///
/// Use this client to execute queries and manage transactions on Postgres.
public struct PostgresDatabaseClient: DatabaseClient {
public typealias Connection = PostgresDatabaseConnection

var client: PostgresClient
var client: PostgresNIO.PostgresClient
var logger: Logger

/// Create a Postgres database client.
Expand All @@ -29,7 +25,7 @@ public struct PostgresDatabaseClient: DatabaseClient {
/// - client: The underlying Postgres client.
/// - logger: The logger for database operations.
public init(
client: PostgresClient,
client: PostgresNIO.PostgresClient,
logger: Logger
) {
self.client = client
Expand All @@ -41,18 +37,21 @@ public struct PostgresDatabaseClient: DatabaseClient {
/// Execute work using a managed Postgres connection.
///
/// The closure receives a Postgres connection for the duration of the call.
/// - Parameters:
/// - isolation: The actor isolation for the operation.
/// - closure: A closure that receives the connection.
/// - Parameter: closure: A closure that receives the connection.
/// - Throws: A `DatabaseError` if connection handling fails.
/// - Returns: The query result produced by the closure.
@discardableResult
public func connection<T>(
isolation: isolated (any Actor)? = #isolation,
_ closure: (PostgresConnection) async throws -> sending T,
) async throws(DatabaseError) -> sending T {
public func withConnection<T>(
_ closure: (Connection) async throws -> T,
) async throws(DatabaseError) -> T {
do {
return try await client.withConnection(closure)
return try await client.withConnection { connection in
let databaseConnection = PostgresDatabaseConnection(
connection: connection,
logger: logger
)
return try await closure(databaseConnection)
}
}
catch let error as DatabaseError {
throw error
Expand All @@ -65,25 +64,30 @@ public struct PostgresDatabaseClient: DatabaseClient {
/// Execute work inside a Postgres transaction.
///
/// The closure is wrapped in a transactional scope.
/// - Parameters:
/// - isolation: The actor isolation for the operation.
/// - closure: A closure that receives the connection.
/// - Parameter: closure: A closure that receives the connection.
/// - Throws: A `DatabaseError` if the transaction fails.
/// - Returns: The query result produced by the closure.
@discardableResult
public func transaction<T>(
isolation: isolated (any Actor)? = #isolation,
_ closure: ((PostgresConnection) async throws -> sending T),
) async throws(DatabaseError) -> sending T {
public func withTransaction<T>(
_ closure: (Connection) async throws -> T,
) async throws(DatabaseError) -> T {
do {
return try await client.withTransaction(
logger: logger,
isolation: isolation,
closure
)
logger: logger
) { connection in
let databaseConnection = PostgresDatabaseConnection(
connection: connection,
logger: logger
)
return try await closure(databaseConnection)
}
}
catch let error as PostgresTransactionError {
throw .transaction(error)
throw .transaction(
PostgresDatabaseTransactionError(
underlyingError: error
)
)
}
catch {
throw .connection(error)
Expand Down
51 changes: 51 additions & 0 deletions Sources/FeatherPostgresDatabase/PostgresDatabaseConnection.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//
// PostgresDatabaseConnection.swift
// feather-postgres-database
//
// Created by Tibor Bödecs on 2026. 01. 10.
//

import FeatherDatabase
import PostgresNIO

public struct PostgresDatabaseConnection: DatabaseConnection {

public typealias Query = PostgresDatabaseQuery
public typealias RowSequence = PostgresDatabaseRowSequence

var connection: PostgresConnection
public var logger: Logger

/// Execute a Postgres query on this connection.
///
/// This wraps `PostgresNIO` query execution and maps errors.
/// - Parameters:
/// - query: The Postgres query to execute.
/// - handler: A closure that receives the RowSequence result.
/// - Throws: A `DatabaseError` if the query fails.
/// - Returns: A query result containing the returned rows.
@discardableResult
public func run<T: Sendable>(
query: Query,
_ handler: (RowSequence) async throws -> T = { $0 }
) async throws(DatabaseError) -> T {
do {
let sequence = try await connection.query(
.init(
unsafeSQL: query.sql,
binds: query.bindings
),
logger: logger
)

return try await handler(
PostgresDatabaseRowSequence(
backingSequence: sequence
)
)
}
catch {
throw .query(error)
}
}
}
Loading