From 4fda8d77cb4bdaf50cb0722f5b7ac53e1b69bf52 Mon Sep 17 00:00:00 2001 From: Stevenson Michel <130018170+thoven87@users.noreply.github.com> Date: Thu, 10 Oct 2024 22:48:37 -0400 Subject: [PATCH 1/6] feat(transaction): Adding withTransaction --- Sources/PostgresNIO/Pool/PostgresClient.swift | 22 ++++++++++++ .../PostgresClientTests.swift | 35 +++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/Sources/PostgresNIO/Pool/PostgresClient.swift b/Sources/PostgresNIO/Pool/PostgresClient.swift index 0907f1f8..0ba7725b 100644 --- a/Sources/PostgresNIO/Pool/PostgresClient.swift +++ b/Sources/PostgresNIO/Pool/PostgresClient.swift @@ -303,6 +303,28 @@ public final class PostgresClient: Sendable, ServiceLifecycle.Service { return try await closure(connection) } + + /// Lease a connection for the provided `closure`'s lifetime. + /// A transation starts with call to withConnection + /// A transaction should end with a call to COMMIT or ROLLBACK + /// COMMIT is called upon successful completion and ROLLBACK is called should any steps fail + /// + /// - Parameter closure: A closure that uses the passed `PostgresConnection`. The closure **must not** capture + /// the provided `PostgresConnection`. + /// - Returns: The closure's return value. + public func withTransaction(logger: Logger, _ process: (PostgresConnection) async throws -> Result) async throws -> Result { + try await withConnection { connection in + do { + try await connection.query("BEGIN;", logger: logger) + let value = try await process(connection) + try await connection.query("COMMIT;", logger: logger) + return value + } catch { + try await connection.query("ROLLBACK;", logger: logger) + throw error + } + } + } /// Run a query on the Postgres server the client is connected to. /// diff --git a/Tests/IntegrationTests/PostgresClientTests.swift b/Tests/IntegrationTests/PostgresClientTests.swift index d6d89dc3..9daae857 100644 --- a/Tests/IntegrationTests/PostgresClientTests.swift +++ b/Tests/IntegrationTests/PostgresClientTests.swift @@ -42,6 +42,41 @@ final class PostgresClientTests: XCTestCase { taskGroup.cancelAll() } } + + func testTransaction() async throws { + var mlogger = Logger(label: "test") + mlogger.logLevel = .debug + let logger = mlogger + let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 8) + self.addTeardownBlock { + try await eventLoopGroup.shutdownGracefully() + } + + let clientConfig = PostgresClient.Configuration.makeTestConfiguration() + let client = PostgresClient(configuration: clientConfig, eventLoopGroup: eventLoopGroup, backgroundLogger: logger) + + await withThrowingTaskGroup(of: Void.self) { taskGroup in + taskGroup.addTask { + await client.run() + } + + let iterations = 1000 + + for _ in 0.. Date: Mon, 21 Oct 2024 07:40:23 -0400 Subject: [PATCH 2/6] adding tests for commit and rollback --- Sources/PostgresNIO/Pool/PostgresClient.swift | 2 +- .../PostgresClientTests.swift | 83 +++++++++++++++---- 2 files changed, 68 insertions(+), 17 deletions(-) diff --git a/Sources/PostgresNIO/Pool/PostgresClient.swift b/Sources/PostgresNIO/Pool/PostgresClient.swift index 0ba7725b..9c79b9d9 100644 --- a/Sources/PostgresNIO/Pool/PostgresClient.swift +++ b/Sources/PostgresNIO/Pool/PostgresClient.swift @@ -314,8 +314,8 @@ public final class PostgresClient: Sendable, ServiceLifecycle.Service { /// - Returns: The closure's return value. public func withTransaction(logger: Logger, _ process: (PostgresConnection) async throws -> Result) async throws -> Result { try await withConnection { connection in + try await connection.query("BEGIN;", logger: logger) do { - try await connection.query("BEGIN;", logger: logger) let value = try await process(connection) try await connection.query("COMMIT;", logger: logger) return value diff --git a/Tests/IntegrationTests/PostgresClientTests.swift b/Tests/IntegrationTests/PostgresClientTests.swift index 9daae857..6e9e843d 100644 --- a/Tests/IntegrationTests/PostgresClientTests.swift +++ b/Tests/IntegrationTests/PostgresClientTests.swift @@ -52,29 +52,80 @@ final class PostgresClientTests: XCTestCase { try await eventLoopGroup.shutdownGracefully() } + let tableName = "test_client_trasactions" + let clientConfig = PostgresClient.Configuration.makeTestConfiguration() let client = PostgresClient(configuration: clientConfig, eventLoopGroup: eventLoopGroup, backgroundLogger: logger) - await withThrowingTaskGroup(of: Void.self) { taskGroup in - taskGroup.addTask { - await client.run() - } - - let iterations = 1000 - - for _ in 0.. Date: Sun, 15 Dec 2024 23:45:59 -0500 Subject: [PATCH 3/6] catch error thrown from invalid insert and rollback --- .../PostgresClientTests.swift | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/Tests/IntegrationTests/PostgresClientTests.swift b/Tests/IntegrationTests/PostgresClientTests.swift index b08a58c3..85cdadf6 100644 --- a/Tests/IntegrationTests/PostgresClientTests.swift +++ b/Tests/IntegrationTests/PostgresClientTests.swift @@ -52,7 +52,7 @@ final class PostgresClientTests: XCTestCase { try await eventLoopGroup.shutdownGracefully() } - let tableName = "test_client_trasactions" + let tableName = "test_client_transactions" let clientConfig = PostgresClient.Configuration.makeTestConfiguration() let client = PostgresClient(configuration: clientConfig, eventLoopGroup: eventLoopGroup, backgroundLogger: logger) @@ -99,13 +99,27 @@ final class PostgresClientTests: XCTestCase { /// Test roll back taskGroup.addTask { - let _ = try await client.withTransaction(logger: logger) { transaction in - try await transaction.query( - """ - INSERT INTO "\(unescaped: tableName)" (uuid) VALUES (\(iterations)); - """, - logger: logger - ) + + do { + let _ = try await client.withTransaction(logger: logger) { transaction in + /// insert valid data + try await transaction.query( + """ + INSERT INTO "\(unescaped: tableName)" (uuid) VALUES (\(UUID())); + """, + logger: logger + ) + + /// insert invalid data + try await transaction.query( + """ + INSERT INTO "\(unescaped: tableName)" (uuid) VALUES (\(iterations)); + """, + logger: logger + ) + } + } catch { + XCTAssertNotNil(error) } } From a808fff369050d82fe950ea17014131d19699128 Mon Sep 17 00:00:00 2001 From: Stevenson Michel <130018170+thoven87@users.noreply.github.com> Date: Mon, 16 Dec 2024 00:13:46 -0500 Subject: [PATCH 4/6] ensure error thrown is PSQLError --- Tests/IntegrationTests/PostgresClientTests.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Tests/IntegrationTests/PostgresClientTests.swift b/Tests/IntegrationTests/PostgresClientTests.swift index 85cdadf6..ecf009c1 100644 --- a/Tests/IntegrationTests/PostgresClientTests.swift +++ b/Tests/IntegrationTests/PostgresClientTests.swift @@ -120,6 +120,11 @@ final class PostgresClientTests: XCTestCase { } } catch { XCTAssertNotNil(error) + XCTAssertNotNil(error) + guard let error = error as? PSQLError else { return XCTFail("Unexpected error type") } + + XCTAssertEqual(error.code, .server) + XCTAssertEqual(error.serverInfo?[.severity], "ERROR") } } From 40fb108a26289db0b37b764be77dc98b7cac44c5 Mon Sep 17 00:00:00 2001 From: Stevenson Michel <130018170+thoven87@users.noreply.github.com> Date: Thu, 23 Jan 2025 09:55:53 -0500 Subject: [PATCH 5/6] use background logger. --- Sources/PostgresNIO/Pool/PostgresClient.swift | 8 ++++---- Tests/IntegrationTests/PostgresClientTests.swift | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Sources/PostgresNIO/Pool/PostgresClient.swift b/Sources/PostgresNIO/Pool/PostgresClient.swift index dc3380f2..e9e947ef 100644 --- a/Sources/PostgresNIO/Pool/PostgresClient.swift +++ b/Sources/PostgresNIO/Pool/PostgresClient.swift @@ -316,15 +316,15 @@ public final class PostgresClient: Sendable, ServiceLifecycle.Service { /// - Parameter closure: A closure that uses the passed `PostgresConnection`. The closure **must not** capture /// the provided `PostgresConnection`. /// - Returns: The closure's return value. - public func withTransaction(logger: Logger, _ process: (PostgresConnection) async throws -> Result) async throws -> Result { + public func withTransaction(_ process: (PostgresConnection) async throws -> Result) async throws -> Result { try await withConnection { connection in - try await connection.query("BEGIN;", logger: logger) + try await connection.query("BEGIN;", logger: self.backgroundLogger) do { let value = try await process(connection) - try await connection.query("COMMIT;", logger: logger) + try await connection.query("COMMIT;", logger: self.backgroundLogger) return value } catch { - try await connection.query("ROLLBACK;", logger: logger) + try await connection.query("ROLLBACK;", logger: self.backgroundLogger) throw error } } diff --git a/Tests/IntegrationTests/PostgresClientTests.swift b/Tests/IntegrationTests/PostgresClientTests.swift index ecf009c1..6a449205 100644 --- a/Tests/IntegrationTests/PostgresClientTests.swift +++ b/Tests/IntegrationTests/PostgresClientTests.swift @@ -77,7 +77,7 @@ final class PostgresClientTests: XCTestCase { for _ in 0.. Date: Tue, 4 Feb 2025 14:44:24 -0500 Subject: [PATCH 6/6] Update PostgresClientTests.swift Co-authored-by: Fabian Fett --- Tests/IntegrationTests/PostgresClientTests.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Tests/IntegrationTests/PostgresClientTests.swift b/Tests/IntegrationTests/PostgresClientTests.swift index 6a449205..167ba298 100644 --- a/Tests/IntegrationTests/PostgresClientTests.swift +++ b/Tests/IntegrationTests/PostgresClientTests.swift @@ -119,7 +119,6 @@ final class PostgresClientTests: XCTestCase { ) } } catch { - XCTAssertNotNil(error) XCTAssertNotNil(error) guard let error = error as? PSQLError else { return XCTFail("Unexpected error type") }