Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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.

18 changes: 16 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,30 @@ let package = Package(
dependencies: [
.package(url: "https://github.com/apple/swift-log", from: "1.6.0"),
.package(url: "https://github.com/vapor/sqlite-nio", from: "1.12.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: [
.target(
name: "FeatherSQLiteDatabase",
name: "SQLiteNIOExtras",
dependencies: [
.product(name: "Logging", package: "swift-log"),
.product(name: "SQLiteNIO", package: "sqlite-nio"),
],
swiftSettings: defaultSwiftSettings
),
.target(
name: "FeatherSQLiteDatabase",
dependencies: [
.product(name: "FeatherDatabase", package: "feather-database"),
.target(name: "SQLiteNIOExtras"),
],
swiftSettings: defaultSwiftSettings
),
.testTarget(
name: "SQLiteNIOExtrasTests",
dependencies: [
.target(name: "SQLiteNIOExtras"),
],
swiftSettings: defaultSwiftSettings
),
Expand Down
39 changes: 22 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
SQLite 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)
![Release: 1.0.0-beta.3](https://img.shields.io/badge/Release-1%2E0%2E0--beta%2E3-F05138)
](
https://github.com/feather-framework/feather-sqlite-database/releases/tag/1.0.0-beta.2
https://github.com/feather-framework/feather-sqlite-database/releases/tag/1.0.0-beta.3
)

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

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

Then add `FeatherSQLiteDatabase` to your target dependencies:
Expand All @@ -49,16 +49,15 @@ Then add `FeatherSQLiteDatabase` to your target dependencies:

## Usage

API documentation is available at the link below:

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

API documentation is available at the following link.

> [!TIP]
> Avoid calling `database.execute` while in a transaction; use the transaction `connection` instead.
Here is a brief example:

```swift
import Logging
Expand All @@ -75,19 +74,25 @@ let configuration = SQLiteClient.Configuration(
)

let client = SQLiteClient(configuration: configuration)
try await client.run()

let database = SQLiteDatabaseClient(client: client)

let result = try await database.execute(
query: #"""
SELECT
sqlite_version() AS "version"
WHERE
1=\#(1);
"""#
let database = SQLiteDatabaseClient(
client: client,
logger: logger
)

try await client.run()

try await database.withConnection { connection in
try await connection.run(
query: #"""
SELECT
sqlite_version() AS "version"
WHERE
1=\#(1);
"""#
)
}

for try await item in result {
let version = try item.decode(column: "version", as: String.self)
print(version)
Expand Down
48 changes: 0 additions & 48 deletions Sources/FeatherSQLiteDatabase/SQLiteConnection.swift

This file was deleted.

92 changes: 55 additions & 37 deletions Sources/FeatherSQLiteDatabase/SQLiteDatabaseClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,71 +6,89 @@
//

import FeatherDatabase
import SQLiteNIO
import Logging
import SQLiteNIOExtras

/// A SQLite-backed database client.
///
/// Use this client to execute queries and manage transactions on SQLite.
public struct SQLiteDatabaseClient: DatabaseClient {

private let client: SQLiteClient
public typealias Connection = SQLiteDatabaseConnection

/// Create a SQLite database client backed by a connection pool.
///
/// - Parameter client: The SQLite client to use.
public init(client: SQLiteClient) {
self.client = client
}
let client: SQLiteClient
var logger: Logger

/// Create a SQLite database client backed by a connection pool.
///
/// - Parameter configuration: The SQLite client configuration.
public init(configuration: SQLiteClient.Configuration) {
self.client = SQLiteClient(configuration: configuration)
}

/// Pre-open the minimum number of connections.
public func run() async throws {
try await client.run()
}

/// Close all pooled connections and refuse new leases.
public func shutdown() async {
await client.shutdown()
/// - Parameters:
/// - client: The SQLite client to use.
/// - logger: The logger to use.
public init(
client: SQLiteClient,
logger: Logger
) {
self.client = client
self.logger = logger
}

// MARK: - database api

/// Execute work using a leased connection.
///
/// The closure is executed with a pooled connection.
/// - Parameters:
/// - isolation: The actor isolation to use for the closure.
/// - closure: A closure that receives the SQLite connection.
/// - Parameters closure: A closure that receives the SQLite connection.
/// - Throws: A `DatabaseError` if the connection fails.
/// - Returns: The query result produced by the closure.
@discardableResult
public func connection<T>(
isolation: isolated (any Actor)? = #isolation,
_ closure: (SQLiteConnection) async throws -> sending T
) async throws(DatabaseError) -> sending T {
try await client.connection(isolation: isolation, closure)
public func withConnection<T>(
_ closure: (Connection) async throws -> T
) async throws(DatabaseError) -> T {
do {
return try await client.withConnection { connection in
try await closure(
SQLiteDatabaseConnection(
connection: connection,
logger: logger
)
)
}
}
catch {
throw .connection(error)
}
}

/// Execute work inside a SQLite transaction.
///
/// The closure runs between `BEGIN` and `COMMIT` with rollback on failure.
/// - Parameters:
/// - isolation: The actor isolation to use for the closure.
/// - closure: A closure that receives the SQLite connection.
/// - Parameters closure: A closure that receives the SQLite connection.
/// - Throws: A `DatabaseError` if transaction handling fails.
/// - Returns: The query result produced by the closure.
@discardableResult
public func transaction<T>(
isolation: isolated (any Actor)? = #isolation,
_ closure: (SQLiteConnection) async throws -> sending T
) async throws(DatabaseError) -> sending T {
try await client.transaction(isolation: isolation, closure)
public func withTransaction<T>(
_ closure: (Connection) async throws -> T
) async throws(DatabaseError) -> T {
do {
return try await client.withTransaction { connection in
try await closure(
SQLiteDatabaseConnection(
connection: connection,
logger: logger
)
)
}
}
catch let error as SQLiteTransactionError {
throw .transaction(
SQLiteDatabaseTransactionError(
underlyingError: error
)
)
}
catch {
throw .connection(error)
}
}

}
50 changes: 50 additions & 0 deletions Sources/FeatherSQLiteDatabase/SQLiteDatabaseConnection.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//
// SQLiteConnection.swift
// feather-sqlite-database
//
// Created by Tibor Bödecs on 2026. 01. 10..
//

import FeatherDatabase
import Logging
import SQLiteNIO

public struct SQLiteDatabaseConnection: DatabaseConnection {

public typealias Query = SQLiteDatabaseQuery
public typealias RowSequence = SQLiteDatabaseRowSequence

var connection: SQLiteConnection
public var logger: Logger

/// Execute a SQLite query on this connection.
///
/// This wraps `SQLiteNIO` query execution and maps errors.
/// - Parameters:
/// - query: The SQLite query to execute.
/// - handler: The handler to process the result sequence.
/// - 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 result = try await connection.query(
query.sql,
query.bindings
)
return try await handler(
SQLiteDatabaseRowSequence(
elements: result.map {
.init(row: $0)
}
)
)
}
catch {
throw .query(error)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import SQLiteNIO
/// A SQLite query with SQL text and bound parameters.
///
/// Use this type to construct SQLite queries safely.
public struct SQLiteQuery: DatabaseQuery {
public struct SQLiteDatabaseQuery: DatabaseQuery {
/// The SQL text to execute.
///
/// This is the raw SQL string for the query.
Expand All @@ -36,7 +36,7 @@ public struct SQLiteQuery: DatabaseQuery {
}
}

extension SQLiteQuery: ExpressibleByStringInterpolation {
extension SQLiteDatabaseQuery: ExpressibleByStringInterpolation {

/// A string interpolation builder for SQLite queries.
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
import FeatherDatabase
import SQLiteNIO

extension SQLiteRow: @retroactive DatabaseRow {
public struct SQLiteDatabaseRow: DatabaseRow {
var row: SQLiteRow

struct SingleValueDecoder: Decoder, SingleValueDecodingContainer {

Expand Down Expand Up @@ -91,7 +92,7 @@ extension SQLiteRow: @retroactive DatabaseRow {
column: String,
as type: T.Type
) throws(DecodingError) -> T {
guard let data = self.column(column) else {
guard let data = row.column(column) else {
throw .dataCorrupted(
.init(
codingPath: [],
Expand Down
Loading
Loading