From fe451e9fff01ef3a90ebfad54d56777fd9783baa Mon Sep 17 00:00:00 2001 From: Tibor Bodecs Date: Sat, 31 Jan 2026 17:17:04 +0100 Subject: [PATCH 1/8] fix query result consumption issue --- Sources/FeatherDatabase/DatabaseClient.swift | 28 +++++++++++++++---- .../FeatherDatabase/DatabaseConnection.swift | 12 ++++---- .../FeatherDatabaseTestSuite.swift | 2 +- .../Mocks/MockDatabaseConnection.swift | 9 +++--- 4 files changed, 36 insertions(+), 15 deletions(-) diff --git a/Sources/FeatherDatabase/DatabaseClient.swift b/Sources/FeatherDatabase/DatabaseClient.swift index ca4b599..fa03cfb 100644 --- a/Sources/FeatherDatabase/DatabaseClient.swift +++ b/Sources/FeatherDatabase/DatabaseClient.swift @@ -49,13 +49,21 @@ public protocol DatabaseClient: Sendable { /// - Parameters: /// - isolation: The actor isolation to use for the duration of the call. /// - query: The query to execute. + /// - handler: A closure that transforms the result into a generic value. /// - Throws: A `DatabaseError` if execution fails. /// - Returns: The query result. @discardableResult - func execute( + func execute( isolation: isolated (any Actor)?, query: Connection.Query, - ) async throws(DatabaseError) -> Connection.Result + _ handler: + @Sendable (Connection.Result) async throws(DatabaseError) -> T + ) async throws(DatabaseError) -> T + + func execute( + isolation: isolated (any Actor)?, + query: Connection.Query + ) async throws(DatabaseError) } @@ -67,15 +75,25 @@ extension DatabaseClient { /// - Parameters: /// - isolation: The actor isolation to use for the duration of the call. /// - query: The query to execute. + /// - handler: A closure that transforms the result into a generic value. /// - Throws: A `DatabaseError` if execution fails. /// - Returns: The query result. @discardableResult - public func execute( + public func execute( isolation: isolated (any Actor)? = #isolation, query: Connection.Query, - ) async throws(DatabaseError) -> Connection.Result { + _ handler: + @Sendable (Connection.Result) async throws(DatabaseError) -> T + ) async throws(DatabaseError) -> T { try await connection(isolation: isolation) { connection in - try await connection.execute(query: query) + try await connection.execute(query: query, handler) } } + + public func execute( + isolation: isolated (any Actor)? = #isolation, + query: Connection.Query + ) async throws(DatabaseError) { + try await execute(isolation: isolation, query: query, { _ in }) + } } diff --git a/Sources/FeatherDatabase/DatabaseConnection.swift b/Sources/FeatherDatabase/DatabaseConnection.swift index dfa4e3e..10cae61 100644 --- a/Sources/FeatherDatabase/DatabaseConnection.swift +++ b/Sources/FeatherDatabase/DatabaseConnection.swift @@ -28,12 +28,14 @@ public protocol DatabaseConnection { /// Execute a query against the connection. /// - /// Implementations should translate errors to `DatabaseError`. - /// - Parameter query: The query to execute. + /// - Parameters: + /// - query: The query to execute. + /// - handler: A closure that transforms the result into a generic value. /// - Throws: A `DatabaseError` if execution fails. /// - Returns: The result of the query execution. @discardableResult - func execute( - query: Query - ) async throws(DatabaseError) -> Result + func execute( + query: Query, + _ handler: (Result) async throws(DatabaseError) -> T + ) async throws -> T } diff --git a/Tests/FeatherDatabaseTests/FeatherDatabaseTestSuite.swift b/Tests/FeatherDatabaseTests/FeatherDatabaseTestSuite.swift index cd0faab..a288b22 100644 --- a/Tests/FeatherDatabaseTests/FeatherDatabaseTestSuite.swift +++ b/Tests/FeatherDatabaseTests/FeatherDatabaseTestSuite.swift @@ -29,7 +29,7 @@ struct FeatherDatabaseTestSuite { let client = MockDatabaseClient(state: state, connection: connection) let query = MockDatabaseQuery(sql: "SELECT 1", bindings: []) - _ = try await client.execute(query: query) + try await client.execute(query: query) #expect(await state.connectionCount() == 1) let executedQueries = await state.executedQueryList() diff --git a/Tests/FeatherDatabaseTests/Mocks/MockDatabaseConnection.swift b/Tests/FeatherDatabaseTests/Mocks/MockDatabaseConnection.swift index 18fc9ec..58d0111 100644 --- a/Tests/FeatherDatabaseTests/Mocks/MockDatabaseConnection.swift +++ b/Tests/FeatherDatabaseTests/Mocks/MockDatabaseConnection.swift @@ -14,10 +14,11 @@ struct MockDatabaseConnection: DatabaseConnection { let state: MockDatabaseState let result: MockDatabaseQueryResult - func execute( - query: MockDatabaseQuery - ) async throws(DatabaseError) -> MockDatabaseQueryResult { + func execute( + query: MockDatabaseQuery, + _ handler: (MockDatabaseQueryResult) async throws(DatabaseError) -> T + ) async throws -> T { await state.recordExecution(query) - return result + return try await handler(result) } } From 455d7efd76975f4acbfa56419253ef125f984210 Mon Sep 17 00:00:00 2001 From: Tibor Bodecs Date: Sat, 31 Jan 2026 17:37:03 +0100 Subject: [PATCH 2/8] result typed throws --- Sources/FeatherDatabase/DatabaseError.swift | 8 ++++++++ .../FeatherDatabase/DatabaseQueryResult.swift | 17 +++++++++++------ .../Mocks/MockDatabaseQueryResult.swift | 2 +- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/Sources/FeatherDatabase/DatabaseError.swift b/Sources/FeatherDatabase/DatabaseError.swift index 04acbf4..d920a67 100644 --- a/Sources/FeatherDatabase/DatabaseError.swift +++ b/Sources/FeatherDatabase/DatabaseError.swift @@ -40,14 +40,22 @@ public protocol DatabaseTransactionError: Error, Sendable { /// /// Use these cases to represent connection, query, and transaction failures. public enum DatabaseError: Error, Sendable { + /// A connection-level failure. /// /// The associated error provides the underlying cause. case connection(Error) + /// A query execution failure. /// /// The associated error provides the underlying cause. case query(Error) + + /// A query result related failure. + /// + /// The associated error provides the underlying cause. + case queryResult(Error) + /// A transaction failure. /// /// The associated error includes phase-specific details. diff --git a/Sources/FeatherDatabase/DatabaseQueryResult.swift b/Sources/FeatherDatabase/DatabaseQueryResult.swift index 00d8de5..6f5adde 100644 --- a/Sources/FeatherDatabase/DatabaseQueryResult.swift +++ b/Sources/FeatherDatabase/DatabaseQueryResult.swift @@ -24,14 +24,14 @@ where /// This method consumes the sequence and returns all elements. /// - Throws: An error if iteration fails. /// - Returns: An array of all rows produced by the query. - func collect() async throws -> [Element] + func collect() async throws(DatabaseError) -> [Element] /// Collect the first available row from the sequence. /// /// This method short-circuits after the first element is received. /// - Throws: An error if iteration fails. /// - Returns: The first row, or `nil` when the sequence is empty. - func collectFirst() async throws -> Element? + func collectFirst() async throws(DatabaseError) -> Element? } extension DatabaseQueryResult { @@ -41,10 +41,15 @@ extension DatabaseQueryResult { /// This default implementation iterates until it finds the first element. /// - Throws: An error if iteration fails. /// - Returns: The first row, or `nil` when the sequence is empty. - public func collectFirst() async throws -> Element? { - for try await item in self { - return item + public func collectFirst() async throws(DatabaseError) -> Element? { + do { + for try await item in self { + return item + } + return nil + } + catch { + throw .queryResult(error) } - return nil } } diff --git a/Tests/FeatherDatabaseTests/Mocks/MockDatabaseQueryResult.swift b/Tests/FeatherDatabaseTests/Mocks/MockDatabaseQueryResult.swift index 71f5694..c55566a 100644 --- a/Tests/FeatherDatabaseTests/Mocks/MockDatabaseQueryResult.swift +++ b/Tests/FeatherDatabaseTests/Mocks/MockDatabaseQueryResult.swift @@ -29,7 +29,7 @@ struct MockDatabaseQueryResult: DatabaseQueryResult { } } - func collect() async throws -> [Element] { + func collect() async throws(DatabaseError) -> [Element] { rows } From 8b39787b7cc7fe68964f6f882c97738d220bb7e6 Mon Sep 17 00:00:00 2001 From: Tibor Bodecs Date: Sat, 31 Jan 2026 18:27:40 +0100 Subject: [PATCH 3/8] fix typed throws --- Sources/FeatherDatabase/DatabaseClient.swift | 21 ++++++++++----- .../FeatherDatabase/DatabaseConnection.swift | 2 +- Sources/FeatherDatabase/DatabaseError.swift | 12 ++++----- .../FeatherDatabase/DatabaseQueryResult.swift | 17 +++++------- .../FeatherDatabaseTestSuite.swift | 4 ++- .../Mocks/MockDatabaseClient.swift | 27 +++++-------------- .../Mocks/MockDatabaseConnection.swift | 14 +++++++--- 7 files changed, 49 insertions(+), 48 deletions(-) diff --git a/Sources/FeatherDatabase/DatabaseClient.swift b/Sources/FeatherDatabase/DatabaseClient.swift index fa03cfb..222d442 100644 --- a/Sources/FeatherDatabase/DatabaseClient.swift +++ b/Sources/FeatherDatabase/DatabaseClient.swift @@ -26,7 +26,7 @@ public protocol DatabaseClient: Sendable { @discardableResult func connection( isolation: isolated (any Actor)?, - _ closure: (Connection) async throws -> sending T, + _ closure: (Connection) async throws(DatabaseError) -> sending T, ) async throws(DatabaseError) -> sending T /// Execute work inside a transaction. @@ -40,7 +40,7 @@ public protocol DatabaseClient: Sendable { @discardableResult func transaction( isolation: isolated (any Actor)?, - _ closure: (Connection) async throws -> sending T, + _ closure: (Connection) async throws(DatabaseError) -> sending T, ) async throws(DatabaseError) -> sending T /// Execute a query using a managed connection. @@ -57,7 +57,7 @@ public protocol DatabaseClient: Sendable { isolation: isolated (any Actor)?, query: Connection.Query, _ handler: - @Sendable (Connection.Result) async throws(DatabaseError) -> T + @Sendable (Connection.Result) async throws -> T ) async throws(DatabaseError) -> T func execute( @@ -83,10 +83,19 @@ extension DatabaseClient { isolation: isolated (any Actor)? = #isolation, query: Connection.Query, _ handler: - @Sendable (Connection.Result) async throws(DatabaseError) -> T + @Sendable (Connection.Result) async throws -> T ) async throws(DatabaseError) -> T { - try await connection(isolation: isolation) { connection in - try await connection.execute(query: query, handler) + try await connection(isolation: isolation) { + connection async throws(DatabaseError) in + do { + return try await connection.execute(query: query, handler) + } + catch let error as DatabaseError { + throw error + } + catch { + throw .result(error) + } } } diff --git a/Sources/FeatherDatabase/DatabaseConnection.swift b/Sources/FeatherDatabase/DatabaseConnection.swift index 10cae61..362a321 100644 --- a/Sources/FeatherDatabase/DatabaseConnection.swift +++ b/Sources/FeatherDatabase/DatabaseConnection.swift @@ -36,6 +36,6 @@ public protocol DatabaseConnection { @discardableResult func execute( query: Query, - _ handler: (Result) async throws(DatabaseError) -> T + _ handler: (Result) async throws -> T ) async throws -> T } diff --git a/Sources/FeatherDatabase/DatabaseError.swift b/Sources/FeatherDatabase/DatabaseError.swift index d920a67..ed8700d 100644 --- a/Sources/FeatherDatabase/DatabaseError.swift +++ b/Sources/FeatherDatabase/DatabaseError.swift @@ -46,6 +46,11 @@ public enum DatabaseError: Error, Sendable { /// The associated error provides the underlying cause. case connection(Error) + /// A transaction failure. + /// + /// The associated error includes phase-specific details. + case transaction(DatabaseTransactionError) + /// A query execution failure. /// /// The associated error provides the underlying cause. @@ -54,10 +59,5 @@ public enum DatabaseError: Error, Sendable { /// A query result related failure. /// /// The associated error provides the underlying cause. - case queryResult(Error) - - /// A transaction failure. - /// - /// The associated error includes phase-specific details. - case transaction(DatabaseTransactionError) + case result(Error) } diff --git a/Sources/FeatherDatabase/DatabaseQueryResult.swift b/Sources/FeatherDatabase/DatabaseQueryResult.swift index 6f5adde..00d8de5 100644 --- a/Sources/FeatherDatabase/DatabaseQueryResult.swift +++ b/Sources/FeatherDatabase/DatabaseQueryResult.swift @@ -24,14 +24,14 @@ where /// This method consumes the sequence and returns all elements. /// - Throws: An error if iteration fails. /// - Returns: An array of all rows produced by the query. - func collect() async throws(DatabaseError) -> [Element] + func collect() async throws -> [Element] /// Collect the first available row from the sequence. /// /// This method short-circuits after the first element is received. /// - Throws: An error if iteration fails. /// - Returns: The first row, or `nil` when the sequence is empty. - func collectFirst() async throws(DatabaseError) -> Element? + func collectFirst() async throws -> Element? } extension DatabaseQueryResult { @@ -41,15 +41,10 @@ extension DatabaseQueryResult { /// This default implementation iterates until it finds the first element. /// - Throws: An error if iteration fails. /// - Returns: The first row, or `nil` when the sequence is empty. - public func collectFirst() async throws(DatabaseError) -> Element? { - do { - for try await item in self { - return item - } - return nil - } - catch { - throw .queryResult(error) + public func collectFirst() async throws -> Element? { + for try await item in self { + return item } + return nil } } diff --git a/Tests/FeatherDatabaseTests/FeatherDatabaseTestSuite.swift b/Tests/FeatherDatabaseTests/FeatherDatabaseTestSuite.swift index a288b22..a4db3c0 100644 --- a/Tests/FeatherDatabaseTests/FeatherDatabaseTestSuite.swift +++ b/Tests/FeatherDatabaseTests/FeatherDatabaseTestSuite.swift @@ -29,7 +29,9 @@ struct FeatherDatabaseTestSuite { let client = MockDatabaseClient(state: state, connection: connection) let query = MockDatabaseQuery(sql: "SELECT 1", bindings: []) - try await client.execute(query: query) + try await client.execute(query: query) { result in + try await result.collect() + } #expect(await state.connectionCount() == 1) let executedQueries = await state.executedQueryList() diff --git a/Tests/FeatherDatabaseTests/Mocks/MockDatabaseClient.swift b/Tests/FeatherDatabaseTests/Mocks/MockDatabaseClient.swift index f1389f8..ec3ddda 100644 --- a/Tests/FeatherDatabaseTests/Mocks/MockDatabaseClient.swift +++ b/Tests/FeatherDatabaseTests/Mocks/MockDatabaseClient.swift @@ -14,34 +14,21 @@ struct MockDatabaseClient: DatabaseClient { func connection( isolation: isolated (any Actor)? = #isolation, - _ closure: (MockDatabaseConnection) async throws -> sending T + _ closure: (MockDatabaseConnection) async throws(DatabaseError) -> + sending T ) async throws(DatabaseError) -> sending T { await state.recordConnection() - do { - return try await closure(connection) - } - catch let error as DatabaseError { - throw error - } - catch { - throw .connection(error) - } + + return try await closure(connection) } func transaction( isolation: isolated (any Actor)? = #isolation, - _ closure: (MockDatabaseConnection) async throws -> sending T + _ closure: (MockDatabaseConnection) async throws(DatabaseError) -> + sending T ) async throws(DatabaseError) -> sending T { await state.recordConnection() - do { - return try await closure(connection) - } - catch let error as DatabaseError { - throw error - } - catch { - throw .connection(error) - } + return try await closure(connection) } } diff --git a/Tests/FeatherDatabaseTests/Mocks/MockDatabaseConnection.swift b/Tests/FeatherDatabaseTests/Mocks/MockDatabaseConnection.swift index 58d0111..e352746 100644 --- a/Tests/FeatherDatabaseTests/Mocks/MockDatabaseConnection.swift +++ b/Tests/FeatherDatabaseTests/Mocks/MockDatabaseConnection.swift @@ -16,9 +16,17 @@ struct MockDatabaseConnection: DatabaseConnection { func execute( query: MockDatabaseQuery, - _ handler: (MockDatabaseQueryResult) async throws(DatabaseError) -> T - ) async throws -> T { + _ handler: (MockDatabaseQueryResult) async throws -> T + ) async throws(DatabaseError) -> T { await state.recordExecution(query) - return try await handler(result) + do { + return try await handler(result) + } + catch let error as DatabaseError { + throw error + } + catch { + throw .result(error) + } } } From 4174694d370b1fda06acdacdc5331f9cf902e190 Mon Sep 17 00:00:00 2001 From: Tibor Bodecs Date: Sun, 1 Feb 2026 19:59:00 +0100 Subject: [PATCH 4/8] rework api to fix consumption issues --- Sources/FeatherDatabase/DatabaseClient.swift | 71 ++----------------- .../FeatherDatabase/DatabaseConnection.swift | 15 ++-- Sources/FeatherDatabase/DatabaseError.swift | 35 --------- .../FeatherDatabase/DatabaseQueryResult.swift | 29 -------- .../DatabaseTransactionError.swift | 37 ++++++++++ .../FeatherDatabaseTestSuite.swift | 35 +++++++-- .../Mocks/MockDatabaseClient.swift | 35 ++++++--- .../Mocks/MockDatabaseConnection.swift | 41 +++++++++-- 8 files changed, 140 insertions(+), 158 deletions(-) create mode 100644 Sources/FeatherDatabase/DatabaseTransactionError.swift diff --git a/Sources/FeatherDatabase/DatabaseClient.swift b/Sources/FeatherDatabase/DatabaseClient.swift index 222d442..9f58a39 100644 --- a/Sources/FeatherDatabase/DatabaseClient.swift +++ b/Sources/FeatherDatabase/DatabaseClient.swift @@ -24,9 +24,9 @@ public protocol DatabaseClient: Sendable { /// - Throws: A `DatabaseError` if acquiring or using the connection fails. /// - Returns: The result returned by the closure. @discardableResult - func connection( + func withConnection( isolation: isolated (any Actor)?, - _ closure: (Connection) async throws(DatabaseError) -> sending T, + _ closure: (Connection) async throws -> sending T, ) async throws(DatabaseError) -> sending T /// Execute work inside a transaction. @@ -38,71 +38,8 @@ public protocol DatabaseClient: Sendable { /// - Throws: A `DatabaseError` if the transaction fails. /// - Returns: The result returned by the closure. @discardableResult - func transaction( + func withTransaction( isolation: isolated (any Actor)?, - _ closure: (Connection) async throws(DatabaseError) -> sending T, + _ closure: (Connection) async throws -> sending T, ) async throws(DatabaseError) -> sending T - - /// Execute a query using a managed connection. - /// - /// This is a convenience wrapper around `connection(_:)`. - /// - Parameters: - /// - isolation: The actor isolation to use for the duration of the call. - /// - query: The query to execute. - /// - handler: A closure that transforms the result into a generic value. - /// - Throws: A `DatabaseError` if execution fails. - /// - Returns: The query result. - @discardableResult - func execute( - isolation: isolated (any Actor)?, - query: Connection.Query, - _ handler: - @Sendable (Connection.Result) async throws -> T - ) async throws(DatabaseError) -> T - - func execute( - isolation: isolated (any Actor)?, - query: Connection.Query - ) async throws(DatabaseError) - -} - -extension DatabaseClient { - - /// Execute a query using a managed connection. - /// - /// This default implementation executes the query inside `connection(_:)`. - /// - Parameters: - /// - isolation: The actor isolation to use for the duration of the call. - /// - query: The query to execute. - /// - handler: A closure that transforms the result into a generic value. - /// - Throws: A `DatabaseError` if execution fails. - /// - Returns: The query result. - @discardableResult - public func execute( - isolation: isolated (any Actor)? = #isolation, - query: Connection.Query, - _ handler: - @Sendable (Connection.Result) async throws -> T - ) async throws(DatabaseError) -> T { - try await connection(isolation: isolation) { - connection async throws(DatabaseError) in - do { - return try await connection.execute(query: query, handler) - } - catch let error as DatabaseError { - throw error - } - catch { - throw .result(error) - } - } - } - - public func execute( - isolation: isolated (any Actor)? = #isolation, - query: Connection.Query - ) async throws(DatabaseError) { - try await execute(isolation: isolation, query: query, { _ in }) - } } diff --git a/Sources/FeatherDatabase/DatabaseConnection.swift b/Sources/FeatherDatabase/DatabaseConnection.swift index 362a321..79c9cdc 100644 --- a/Sources/FeatherDatabase/DatabaseConnection.swift +++ b/Sources/FeatherDatabase/DatabaseConnection.swift @@ -26,7 +26,7 @@ public protocol DatabaseConnection { /// This is used to record database-related diagnostics. var logger: Logger { get } - /// Execute a query against the connection. + /// Runs a query using the database connection. /// /// - Parameters: /// - query: The query to execute. @@ -34,8 +34,13 @@ public protocol DatabaseConnection { /// - Throws: A `DatabaseError` if execution fails. /// - Returns: The result of the query execution. @discardableResult - func execute( - query: Query, - _ handler: (Result) async throws -> T - ) async throws -> T + func run( + _ query: Query, + _ handler: (Result.Row) async throws -> T + ) async throws(DatabaseError) -> [T] + + func run( + _ query: Query, + _ handler: (Result.Row) async throws -> Void + ) async throws(DatabaseError) } diff --git a/Sources/FeatherDatabase/DatabaseError.swift b/Sources/FeatherDatabase/DatabaseError.swift index ed8700d..7c30349 100644 --- a/Sources/FeatherDatabase/DatabaseError.swift +++ b/Sources/FeatherDatabase/DatabaseError.swift @@ -5,37 +5,6 @@ // Created by Tibor Bödecs on 2026. 01. 14.. // -/// A transaction error that captures failure details. -/// -/// Use this protocol to report errors from transaction phases. -public protocol DatabaseTransactionError: Error, Sendable { - /// The source file where the transaction error was created. - /// - /// This is typically populated using `#fileID`. - var file: String { get } - /// The source line where the transaction error was created. - /// - /// This is typically populated using `#line`. - var line: Int { get } - - /// The error thrown while beginning the transaction. - /// - /// This is set when the begin step fails. - var beginError: Error? { get set } - /// The error thrown inside the transaction closure. - /// - /// This is set when the closure fails before commit. - var closureError: Error? { get set } - /// The error thrown while committing the transaction. - /// - /// This is set when the commit step fails. - var commitError: Error? { get set } - /// The error thrown while rolling back the transaction. - /// - /// This is set when the rollback step fails. - var rollbackError: Error? { get set } -} - /// High-level database errors surfaced by the client API. /// /// Use these cases to represent connection, query, and transaction failures. @@ -56,8 +25,4 @@ public enum DatabaseError: Error, Sendable { /// The associated error provides the underlying cause. case query(Error) - /// A query result related failure. - /// - /// The associated error provides the underlying cause. - case result(Error) } diff --git a/Sources/FeatherDatabase/DatabaseQueryResult.swift b/Sources/FeatherDatabase/DatabaseQueryResult.swift index 00d8de5..9e3fa2c 100644 --- a/Sources/FeatherDatabase/DatabaseQueryResult.swift +++ b/Sources/FeatherDatabase/DatabaseQueryResult.swift @@ -18,33 +18,4 @@ where /// /// This row must conform to `DatabaseRow` for decoding support. associatedtype Row: DatabaseRow - - /// Collect all rows from the sequence. - /// - /// This method consumes the sequence and returns all elements. - /// - Throws: An error if iteration fails. - /// - Returns: An array of all rows produced by the query. - func collect() async throws -> [Element] - - /// Collect the first available row from the sequence. - /// - /// This method short-circuits after the first element is received. - /// - Throws: An error if iteration fails. - /// - Returns: The first row, or `nil` when the sequence is empty. - func collectFirst() async throws -> Element? -} - -extension DatabaseQueryResult { - - /// Collect the first available row from the sequence. - /// - /// This default implementation iterates until it finds the first element. - /// - Throws: An error if iteration fails. - /// - Returns: The first row, or `nil` when the sequence is empty. - public func collectFirst() async throws -> Element? { - for try await item in self { - return item - } - return nil - } } diff --git a/Sources/FeatherDatabase/DatabaseTransactionError.swift b/Sources/FeatherDatabase/DatabaseTransactionError.swift new file mode 100644 index 0000000..5a5a61d --- /dev/null +++ b/Sources/FeatherDatabase/DatabaseTransactionError.swift @@ -0,0 +1,37 @@ +// +// DatabaseTransactionError.swift +// feather-database +// +// Created by Tibor Bödecs on 2026. 02. 01.. +// + +/// A transaction error that captures failure details. +/// +/// Use this protocol to report errors from transaction phases. +public protocol DatabaseTransactionError: Error, Sendable { + /// The source file where the transaction error was created. + /// + /// This is typically populated using `#fileID`. + var file: String { get } + /// The source line where the transaction error was created. + /// + /// This is typically populated using `#line`. + var line: Int { get } + + /// The error thrown while beginning the transaction. + /// + /// This is set when the begin step fails. + var beginError: Error? { get set } + /// The error thrown inside the transaction closure. + /// + /// This is set when the closure fails before commit. + var closureError: Error? { get set } + /// The error thrown while committing the transaction. + /// + /// This is set when the commit step fails. + var commitError: Error? { get set } + /// The error thrown while rolling back the transaction. + /// + /// This is set when the rollback step fails. + var rollbackError: Error? { get set } +} diff --git a/Tests/FeatherDatabaseTests/FeatherDatabaseTestSuite.swift b/Tests/FeatherDatabaseTests/FeatherDatabaseTestSuite.swift index a4db3c0..de577d6 100644 --- a/Tests/FeatherDatabaseTests/FeatherDatabaseTestSuite.swift +++ b/Tests/FeatherDatabaseTests/FeatherDatabaseTestSuite.swift @@ -24,18 +24,39 @@ struct FeatherDatabaseTestSuite { let connection = MockDatabaseConnection( logger: Logger(label: "test"), state: state, - result: result + mockResult: result.rows ) let client = MockDatabaseClient(state: state, connection: connection) let query = MockDatabaseQuery(sql: "SELECT 1", bindings: []) - try await client.execute(query: query) { result in - try await result.collect() + let decodedRows = try await client.withConnection { connection in + try await connection.run(query) { row in + try row.decode(column: "name", as: String.self) + } } + print(decodedRows) - #expect(await state.connectionCount() == 1) + var items: [String] = [] + try await client.withConnection { connection in + try await connection.run(query) { row in + items.append(try row.decode(column: "name", as: String.self)) + } + } + print(items) + + try await client.withTransaction { connection in + try await connection.run(query) { row in + try row.decode(column: "name", as: String.self) + } + try await connection.run(query) { row in + try row.decode(column: "name", as: String.self) + } + return "ok" + } + + #expect(await state.connectionCount() == 3) let executedQueries = await state.executedQueryList() - #expect(executedQueries.count == 1) + #expect(executedQueries.count == 4) #expect(executedQueries.first?.sql == query.sql) } @@ -47,7 +68,7 @@ struct FeatherDatabaseTestSuite { ] let result = MockDatabaseQueryResult(rows: rows) - let first = try await result.collectFirst() + let first = try await result.collect().first #expect(first != nil) #expect( @@ -59,7 +80,7 @@ struct FeatherDatabaseTestSuite { func collectFirstReturnsNilWhenEmpty() async throws { let result = MockDatabaseQueryResult(rows: []) - let first = try await result.collectFirst() + let first = try await result.collect().first #expect(first == nil) } diff --git a/Tests/FeatherDatabaseTests/Mocks/MockDatabaseClient.swift b/Tests/FeatherDatabaseTests/Mocks/MockDatabaseClient.swift index ec3ddda..dff51d8 100644 --- a/Tests/FeatherDatabaseTests/Mocks/MockDatabaseClient.swift +++ b/Tests/FeatherDatabaseTests/Mocks/MockDatabaseClient.swift @@ -12,23 +12,42 @@ struct MockDatabaseClient: DatabaseClient { let state: MockDatabaseState let connection: MockDatabaseConnection - func connection( + @discardableResult + func withConnection( isolation: isolated (any Actor)? = #isolation, - _ closure: (MockDatabaseConnection) async throws(DatabaseError) -> - sending T + _ closure: (MockDatabaseConnection) async throws -> sending T ) async throws(DatabaseError) -> sending T { await state.recordConnection() - return try await closure(connection) + do { + return try await closure(connection) + } + catch let error as DatabaseError { + throw error + } + catch { + throw .connection(error) + } } - func transaction( + @discardableResult + func withTransaction( isolation: isolated (any Actor)? = #isolation, - _ closure: (MockDatabaseConnection) async throws(DatabaseError) -> - sending T + _ closure: (MockDatabaseConnection) async throws -> sending T ) async throws(DatabaseError) -> sending T { await state.recordConnection() - return try await closure(connection) + do { + return try await closure(connection) + } + catch let error as DatabaseError { + throw error + } + catch let error as DatabaseTransactionError { + throw .transaction(error) + } + catch { + throw .query(error) + } } } diff --git a/Tests/FeatherDatabaseTests/Mocks/MockDatabaseConnection.swift b/Tests/FeatherDatabaseTests/Mocks/MockDatabaseConnection.swift index e352746..f9a20dc 100644 --- a/Tests/FeatherDatabaseTests/Mocks/MockDatabaseConnection.swift +++ b/Tests/FeatherDatabaseTests/Mocks/MockDatabaseConnection.swift @@ -10,23 +10,50 @@ import Logging struct MockDatabaseConnection: DatabaseConnection { + typealias Query = MockDatabaseQuery + typealias Result = MockDatabaseQueryResult + let logger: Logger let state: MockDatabaseState - let result: MockDatabaseQueryResult + let mockResult: [MockDatabaseQueryResult.Row] + + @discardableResult + func run( + _ query: MockDatabaseQuery, + _ handler: (MockDatabaseQueryResult.Row) async throws -> T + ) async throws(DatabaseError) -> [T] { + await state.recordExecution(query) + do { + var result: [T] = [] + for item in mockResult { + result.append(try await handler(item)) + } + return result + } + catch let error as DatabaseError { + throw error + } + catch { + throw .query(error) + } + } - func execute( - query: MockDatabaseQuery, - _ handler: (MockDatabaseQueryResult) async throws -> T - ) async throws(DatabaseError) -> T { + func run( + _ query: Query, + _ handler: (Result.Row) async throws -> Void + ) async throws(DatabaseError) { await state.recordExecution(query) do { - return try await handler(result) + for item in mockResult { + try await handler(item) + } } catch let error as DatabaseError { throw error } catch { - throw .result(error) + throw .query(error) } } + } From 8de8b87ec6bf10a67d201b9c4b14796a0711a155 Mon Sep 17 00:00:00 2001 From: Tibor Bodecs Date: Sun, 1 Feb 2026 23:15:44 +0100 Subject: [PATCH 5/8] query label consistency --- Sources/FeatherDatabase/DatabaseConnection.swift | 4 ++-- Tests/FeatherDatabaseTests/FeatherDatabaseTestSuite.swift | 8 ++++---- .../Mocks/MockDatabaseConnection.swift | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Sources/FeatherDatabase/DatabaseConnection.swift b/Sources/FeatherDatabase/DatabaseConnection.swift index 79c9cdc..94f2187 100644 --- a/Sources/FeatherDatabase/DatabaseConnection.swift +++ b/Sources/FeatherDatabase/DatabaseConnection.swift @@ -35,12 +35,12 @@ public protocol DatabaseConnection { /// - Returns: The result of the query execution. @discardableResult func run( - _ query: Query, + query: Query, _ handler: (Result.Row) async throws -> T ) async throws(DatabaseError) -> [T] func run( - _ query: Query, + query: Query, _ handler: (Result.Row) async throws -> Void ) async throws(DatabaseError) } diff --git a/Tests/FeatherDatabaseTests/FeatherDatabaseTestSuite.swift b/Tests/FeatherDatabaseTests/FeatherDatabaseTestSuite.swift index de577d6..8b6e820 100644 --- a/Tests/FeatherDatabaseTests/FeatherDatabaseTestSuite.swift +++ b/Tests/FeatherDatabaseTests/FeatherDatabaseTestSuite.swift @@ -30,7 +30,7 @@ struct FeatherDatabaseTestSuite { let query = MockDatabaseQuery(sql: "SELECT 1", bindings: []) let decodedRows = try await client.withConnection { connection in - try await connection.run(query) { row in + try await connection.run(query: query) { row in try row.decode(column: "name", as: String.self) } } @@ -38,17 +38,17 @@ struct FeatherDatabaseTestSuite { var items: [String] = [] try await client.withConnection { connection in - try await connection.run(query) { row in + try await connection.run(query: query) { row in items.append(try row.decode(column: "name", as: String.self)) } } print(items) try await client.withTransaction { connection in - try await connection.run(query) { row in + try await connection.run(query: query) { row in try row.decode(column: "name", as: String.self) } - try await connection.run(query) { row in + try await connection.run(query: query) { row in try row.decode(column: "name", as: String.self) } return "ok" diff --git a/Tests/FeatherDatabaseTests/Mocks/MockDatabaseConnection.swift b/Tests/FeatherDatabaseTests/Mocks/MockDatabaseConnection.swift index f9a20dc..2e1f4d5 100644 --- a/Tests/FeatherDatabaseTests/Mocks/MockDatabaseConnection.swift +++ b/Tests/FeatherDatabaseTests/Mocks/MockDatabaseConnection.swift @@ -19,8 +19,8 @@ struct MockDatabaseConnection: DatabaseConnection { @discardableResult func run( - _ query: MockDatabaseQuery, - _ handler: (MockDatabaseQueryResult.Row) async throws -> T + query: MockDatabaseQuery, + _ handler: (MockDatabaseQueryResult.Row) async throws -> T = { $0 } ) async throws(DatabaseError) -> [T] { await state.recordExecution(query) do { @@ -39,8 +39,8 @@ struct MockDatabaseConnection: DatabaseConnection { } func run( - _ query: Query, - _ handler: (Result.Row) async throws -> Void + query: Query, + _ handler: (Result.Row) async throws -> Void = { _ in } ) async throws(DatabaseError) { await state.recordExecution(query) do { From 2e7c7461fadae813f6d8faad63ef8c3ba4b121ec Mon Sep 17 00:00:00 2001 From: Tibor Bodecs Date: Mon, 2 Feb 2026 16:41:17 +0100 Subject: [PATCH 6/8] API changes based on feedbacks --- Sources/FeatherDatabase/DatabaseClient.swift | 18 +++---- .../FeatherDatabase/DatabaseConnection.swift | 16 +++---- .../FeatherDatabase/DatabaseQueryResult.swift | 21 -------- .../FeatherDatabase/DatabaseRowSequence.swift | 28 +++++++++++ .../FeatherDatabaseTestSuite.swift | 48 ++++++++++++------- .../Mocks/MockDatabaseClient.swift | 10 ++-- .../Mocks/MockDatabaseConnection.swift | 32 ++----------- ...lt.swift => MockDatabaseRowSequence.swift} | 4 +- 8 files changed, 83 insertions(+), 94 deletions(-) delete mode 100644 Sources/FeatherDatabase/DatabaseQueryResult.swift create mode 100644 Sources/FeatherDatabase/DatabaseRowSequence.swift rename Tests/FeatherDatabaseTests/Mocks/{MockDatabaseQueryResult.swift => MockDatabaseRowSequence.swift} (89%) diff --git a/Sources/FeatherDatabase/DatabaseClient.swift b/Sources/FeatherDatabase/DatabaseClient.swift index 9f58a39..8cfeebc 100644 --- a/Sources/FeatherDatabase/DatabaseClient.swift +++ b/Sources/FeatherDatabase/DatabaseClient.swift @@ -18,28 +18,22 @@ public protocol DatabaseClient: Sendable { /// Execute work using a managed connection. /// /// The connection is provided to the closure for the duration of the call. - /// - Parameters: - /// - isolation: The actor isolation to use for the duration of the call. - /// - closure: A closure that receives a connection and returns a result. + /// - Parameter: closure: A closure that receives a connection and returns a result. /// - Throws: A `DatabaseError` if acquiring or using the connection fails. /// - Returns: The result returned by the closure. @discardableResult func withConnection( - isolation: isolated (any Actor)?, - _ closure: (Connection) async throws -> sending T, - ) async throws(DatabaseError) -> sending T + _ closure: (Connection) async throws -> T, + ) async throws(DatabaseError) -> T /// Execute work inside a transaction. /// /// Implementations should wrap the closure in a transaction boundary. - /// - Parameters: - /// - isolation: The actor isolation to use for the duration of the call. - /// - closure: A closure that receives a connection and returns a result. + /// - Parameter: closure: A closure that receives a connection and returns a result. /// - Throws: A `DatabaseError` if the transaction fails. /// - Returns: The result returned by the closure. @discardableResult func withTransaction( - isolation: isolated (any Actor)?, - _ closure: (Connection) async throws -> sending T, - ) async throws(DatabaseError) -> sending T + _ closure: (Connection) async throws -> T, + ) async throws(DatabaseError) -> T } diff --git a/Sources/FeatherDatabase/DatabaseConnection.swift b/Sources/FeatherDatabase/DatabaseConnection.swift index 94f2187..ca6660d 100644 --- a/Sources/FeatherDatabase/DatabaseConnection.swift +++ b/Sources/FeatherDatabase/DatabaseConnection.swift @@ -16,10 +16,11 @@ public protocol DatabaseConnection { /// /// Use this to define the SQL and bindings type. associatedtype Query: DatabaseQuery - /// The query result type produced by this connection. + + /// The row sequence type produced by this connection. /// - /// The result must conform to `DatabaseQueryResult`. - associatedtype Result: DatabaseQueryResult + /// The result must conform to `DatabaseRowSequence`. + associatedtype RowSequence: DatabaseRowSequence /// The logger used for connection operations. /// @@ -36,11 +37,6 @@ public protocol DatabaseConnection { @discardableResult func run( query: Query, - _ handler: (Result.Row) async throws -> T - ) async throws(DatabaseError) -> [T] - - func run( - query: Query, - _ handler: (Result.Row) async throws -> Void - ) async throws(DatabaseError) + _ handler: (RowSequence) async throws -> T + ) async throws(DatabaseError) -> T } diff --git a/Sources/FeatherDatabase/DatabaseQueryResult.swift b/Sources/FeatherDatabase/DatabaseQueryResult.swift deleted file mode 100644 index 9e3fa2c..0000000 --- a/Sources/FeatherDatabase/DatabaseQueryResult.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// DatabaseQueryResult.swift -// feather-database -// -// Created by Tibor Bödecs on 2026. 01. 10.. -// - -/// A result type returned from a database query. -/// -/// Use this protocol to iterate rows asynchronously or collect them eagerly. -public protocol DatabaseQueryResult: - AsyncSequence, - Sendable -where - Element == Row -{ - /// A row type produced by the query. - /// - /// This row must conform to `DatabaseRow` for decoding support. - associatedtype Row: DatabaseRow -} diff --git a/Sources/FeatherDatabase/DatabaseRowSequence.swift b/Sources/FeatherDatabase/DatabaseRowSequence.swift new file mode 100644 index 0000000..54af26d --- /dev/null +++ b/Sources/FeatherDatabase/DatabaseRowSequence.swift @@ -0,0 +1,28 @@ +// +// DatabaseRowSequence.swift +// feather-database +// +// Created by Tibor Bödecs on 2026. 01. 10.. +// + +/// A sequence returned from a database query. +/// +/// Use this protocol to iterate rows asynchronously or collect them eagerly. +public protocol DatabaseRowSequence: + AsyncSequence, + Sendable +where + Element == Row +{ + /// A row type produced by the query. + /// + /// This row must conform to `DatabaseRow` for decoding support. + associatedtype Row: DatabaseRow + + /// Collect all rows from the sequence. + /// + /// This method consumes the sequence and returns all elements. + /// - Throws: An error if iteration fails. + /// - Returns: An array of all rows produced by the query. + func collect() async throws -> [Element] +} diff --git a/Tests/FeatherDatabaseTests/FeatherDatabaseTestSuite.swift b/Tests/FeatherDatabaseTests/FeatherDatabaseTestSuite.swift index 8b6e820..b17abdd 100644 --- a/Tests/FeatherDatabaseTests/FeatherDatabaseTestSuite.swift +++ b/Tests/FeatherDatabaseTests/FeatherDatabaseTestSuite.swift @@ -16,7 +16,7 @@ struct FeatherDatabaseTestSuite { @Test func executeUsesConnection() async throws { let state = MockDatabaseState() - let result = MockDatabaseQueryResult( + let sequence = MockDatabaseRowSequence( rows: [ MockDatabaseRow(storage: ["name": .string("alpha")]) ] @@ -24,39 +24,55 @@ struct FeatherDatabaseTestSuite { let connection = MockDatabaseConnection( logger: Logger(label: "test"), state: state, - mockResult: result.rows + mockSequence: sequence ) let client = MockDatabaseClient(state: state, connection: connection) let query = MockDatabaseQuery(sql: "SELECT 1", bindings: []) let decodedRows = try await client.withConnection { connection in - try await connection.run(query: query) { row in - try row.decode(column: "name", as: String.self) + try await connection.run(query: query) { sequence in + try await sequence.collect() + .map { + try $0.decode(column: "name", as: String.self) + } } } print(decodedRows) - var items: [String] = [] try await client.withConnection { connection in - try await connection.run(query: query) { row in - items.append(try row.decode(column: "name", as: String.self)) + try await connection.run(query: query) { sequence in + try await sequence.collect() + .map { + try $0.decode(column: "name", as: String.self) + } + } + } + + try await client.withConnection { connection in + try await connection.run(query: query) { _ in + // no value returned, no sequence iteration } } - print(items) try await client.withTransaction { connection in - try await connection.run(query: query) { row in - try row.decode(column: "name", as: String.self) + try await connection.run(query: query) { sequence in + try await sequence.collect() + .map { + try $0.decode(column: "name", as: String.self) + } } - try await connection.run(query: query) { row in - try row.decode(column: "name", as: String.self) + try await connection.run(query: query) { sequence in + try await sequence.collect() + .map { + try $0.decode(column: "name", as: String.self) + } } return "ok" } - #expect(await state.connectionCount() == 3) + #expect(await state.connectionCount() == 4) let executedQueries = await state.executedQueryList() - #expect(executedQueries.count == 4) + #expect(executedQueries.count == 5) #expect(executedQueries.first?.sql == query.sql) } @@ -66,7 +82,7 @@ struct FeatherDatabaseTestSuite { MockDatabaseRow(storage: ["name": .string("alpha")]), MockDatabaseRow(storage: ["name": .string("beta")]), ] - let result = MockDatabaseQueryResult(rows: rows) + let result = MockDatabaseRowSequence(rows: rows) let first = try await result.collect().first @@ -78,7 +94,7 @@ struct FeatherDatabaseTestSuite { @Test func collectFirstReturnsNilWhenEmpty() async throws { - let result = MockDatabaseQueryResult(rows: []) + let result = MockDatabaseRowSequence(rows: []) let first = try await result.collect().first diff --git a/Tests/FeatherDatabaseTests/Mocks/MockDatabaseClient.swift b/Tests/FeatherDatabaseTests/Mocks/MockDatabaseClient.swift index dff51d8..d076863 100644 --- a/Tests/FeatherDatabaseTests/Mocks/MockDatabaseClient.swift +++ b/Tests/FeatherDatabaseTests/Mocks/MockDatabaseClient.swift @@ -14,9 +14,8 @@ struct MockDatabaseClient: DatabaseClient { @discardableResult func withConnection( - isolation: isolated (any Actor)? = #isolation, - _ closure: (MockDatabaseConnection) async throws -> sending T - ) async throws(DatabaseError) -> sending T { + _ closure: (MockDatabaseConnection) async throws -> T + ) async throws(DatabaseError) -> T { await state.recordConnection() do { @@ -32,9 +31,8 @@ struct MockDatabaseClient: DatabaseClient { @discardableResult func withTransaction( - isolation: isolated (any Actor)? = #isolation, - _ closure: (MockDatabaseConnection) async throws -> sending T - ) async throws(DatabaseError) -> sending T { + _ closure: (MockDatabaseConnection) async throws -> T + ) async throws(DatabaseError) -> T { await state.recordConnection() do { return try await closure(connection) diff --git a/Tests/FeatherDatabaseTests/Mocks/MockDatabaseConnection.swift b/Tests/FeatherDatabaseTests/Mocks/MockDatabaseConnection.swift index 2e1f4d5..ab4ff8d 100644 --- a/Tests/FeatherDatabaseTests/Mocks/MockDatabaseConnection.swift +++ b/Tests/FeatherDatabaseTests/Mocks/MockDatabaseConnection.swift @@ -11,42 +11,20 @@ import Logging struct MockDatabaseConnection: DatabaseConnection { typealias Query = MockDatabaseQuery - typealias Result = MockDatabaseQueryResult + typealias RowSequence = MockDatabaseRowSequence let logger: Logger let state: MockDatabaseState - let mockResult: [MockDatabaseQueryResult.Row] + let mockSequence: RowSequence @discardableResult func run( - query: MockDatabaseQuery, - _ handler: (MockDatabaseQueryResult.Row) async throws -> T = { $0 } - ) async throws(DatabaseError) -> [T] { - await state.recordExecution(query) - do { - var result: [T] = [] - for item in mockResult { - result.append(try await handler(item)) - } - return result - } - catch let error as DatabaseError { - throw error - } - catch { - throw .query(error) - } - } - - func run( query: Query, - _ handler: (Result.Row) async throws -> Void = { _ in } - ) async throws(DatabaseError) { + _ handler: (RowSequence) async throws -> T + ) async throws(DatabaseError) -> T { await state.recordExecution(query) do { - for item in mockResult { - try await handler(item) - } + return try await handler(mockSequence) } catch let error as DatabaseError { throw error diff --git a/Tests/FeatherDatabaseTests/Mocks/MockDatabaseQueryResult.swift b/Tests/FeatherDatabaseTests/Mocks/MockDatabaseRowSequence.swift similarity index 89% rename from Tests/FeatherDatabaseTests/Mocks/MockDatabaseQueryResult.swift rename to Tests/FeatherDatabaseTests/Mocks/MockDatabaseRowSequence.swift index c55566a..e80ada7 100644 --- a/Tests/FeatherDatabaseTests/Mocks/MockDatabaseQueryResult.swift +++ b/Tests/FeatherDatabaseTests/Mocks/MockDatabaseRowSequence.swift @@ -1,5 +1,5 @@ // -// MockDatabaseQueryResult.swift +// MockDatabaseRowSequence.swift // feather-database // // Created by Tibor Bodecs on 2026. 01. 10.. @@ -7,7 +7,7 @@ import FeatherDatabase -struct MockDatabaseQueryResult: DatabaseQueryResult { +struct MockDatabaseRowSequence: DatabaseRowSequence { let rows: [MockDatabaseRow] From 327c474c3874ecbd18b4ae15dadc286a84488814 Mon Sep 17 00:00:00 2001 From: Tibor Bodecs Date: Mon, 2 Feb 2026 17:16:15 +0100 Subject: [PATCH 7/8] fix transaction error protocol --- Sources/FeatherDatabase/DatabaseTransactionError.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/FeatherDatabase/DatabaseTransactionError.swift b/Sources/FeatherDatabase/DatabaseTransactionError.swift index 5a5a61d..522b0b7 100644 --- a/Sources/FeatherDatabase/DatabaseTransactionError.swift +++ b/Sources/FeatherDatabase/DatabaseTransactionError.swift @@ -8,7 +8,7 @@ /// A transaction error that captures failure details. /// /// Use this protocol to report errors from transaction phases. -public protocol DatabaseTransactionError: Error, Sendable { +public protocol DatabaseTransactionError: Error { /// The source file where the transaction error was created. /// /// This is typically populated using `#fileID`. @@ -21,17 +21,17 @@ public protocol DatabaseTransactionError: Error, Sendable { /// The error thrown while beginning the transaction. /// /// This is set when the begin step fails. - var beginError: Error? { get set } + var beginError: Error? { get } /// The error thrown inside the transaction closure. /// /// This is set when the closure fails before commit. - var closureError: Error? { get set } + var closureError: Error? { get } /// The error thrown while committing the transaction. /// /// This is set when the commit step fails. - var commitError: Error? { get set } + var commitError: Error? { get } /// The error thrown while rolling back the transaction. /// /// This is set when the rollback step fails. - var rollbackError: Error? { get set } + var rollbackError: Error? { get } } From 2d2e5f46f286599defa83086806da42dc7727961 Mon Sep 17 00:00:00 2001 From: Tibor Bodecs Date: Wed, 4 Feb 2026 11:28:51 +0100 Subject: [PATCH 8/8] final changes for beta.3 --- README.md | 9 +++------ Sources/FeatherDatabase/DatabaseConnection.swift | 2 +- Sources/FeatherDatabase/DatabaseError.swift | 2 +- .../FeatherDatabaseTests/FeatherDatabaseTestSuite.swift | 2 +- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index a2a88ec..0861854 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,9 @@ Abstract database component, providing a shared API surface for database drivers written in Swift. [ - ![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%3E2-F05138) ]( - https://github.com/feather-framework/feather-database/releases/tag/1.0.0-beta.2 + https://github.com/feather-framework/feather-database/releases/tag/1.0.0-beta.3 ) ## Features @@ -35,7 +35,7 @@ Abstract database component, providing a shared API surface for database drivers Use Swift Package Manager; add the dependency to your `Package.swift` file: ```swift -.package(url: "https://github.com/feather-framework/feather-database", exact: "1.0.0-beta.1"), +.package(url: "https://github.com/feather-framework/feather-database", exact: "1.0.0-beta.3"), ``` Then add `FeatherDatabase` to your target dependencies: @@ -55,9 +55,6 @@ Then add `FeatherDatabase` to your target dependencies: API documentation is available at the following link. Refer to the mock objects in the Tests directory if you want to build a custom database driver implementation. -> [!TIP] -> Avoid calling `database.execute` while in a transaction; use the transaction `connection.execute` instead. - > [!WARNING] > This repository is a work in progress, things can break until it reaches v1.0.0. diff --git a/Sources/FeatherDatabase/DatabaseConnection.swift b/Sources/FeatherDatabase/DatabaseConnection.swift index ca6660d..4de5106 100644 --- a/Sources/FeatherDatabase/DatabaseConnection.swift +++ b/Sources/FeatherDatabase/DatabaseConnection.swift @@ -10,7 +10,7 @@ import Logging /// A connection that can execute database queries. /// /// Implementations provide query execution and lifecycle management. -public protocol DatabaseConnection { +public protocol DatabaseConnection: Sendable { /// The query type supported by this connection. /// diff --git a/Sources/FeatherDatabase/DatabaseError.swift b/Sources/FeatherDatabase/DatabaseError.swift index 7c30349..22253f8 100644 --- a/Sources/FeatherDatabase/DatabaseError.swift +++ b/Sources/FeatherDatabase/DatabaseError.swift @@ -8,7 +8,7 @@ /// High-level database errors surfaced by the client API. /// /// Use these cases to represent connection, query, and transaction failures. -public enum DatabaseError: Error, Sendable { +public enum DatabaseError: Error { /// A connection-level failure. /// diff --git a/Tests/FeatherDatabaseTests/FeatherDatabaseTestSuite.swift b/Tests/FeatherDatabaseTests/FeatherDatabaseTestSuite.swift index b17abdd..76e3e2a 100644 --- a/Tests/FeatherDatabaseTests/FeatherDatabaseTestSuite.swift +++ b/Tests/FeatherDatabaseTests/FeatherDatabaseTestSuite.swift @@ -37,7 +37,7 @@ struct FeatherDatabaseTestSuite { } } } - print(decodedRows) + #expect(decodedRows.count == 1) try await client.withConnection { connection in try await connection.run(query: query) { sequence in