-
-
Notifications
You must be signed in to change notification settings - Fork 80
Improve transaction handling #538
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -530,6 +530,110 @@ extension PostgresConnection { | |
throw error // rethrow with more metadata | ||
} | ||
} | ||
|
||
#if compiler(>=6.0) | ||
/// Puts the connection into an open transaction state, for the provided `closure`'s lifetime. | ||
/// | ||
/// The function starts a transaction by running a `BEGIN` query on the connection against the database. It then | ||
/// lends the connection to the user provided closure. The user can then modify the database as they wish. If the user | ||
/// provided closure returns successfully, the function will attempt to commit the changes by running a `COMMIT` | ||
/// query against the database. If the user provided closure throws an error, the function will attempt to rollback the | ||
/// changes made within the closure. | ||
/// | ||
/// - Parameters: | ||
/// - logger: The `Logger` to log into for the transaction. | ||
/// - file: The file, the transaction was started in. Used for better error reporting. | ||
/// - line: The line, the transaction was started in. Used for better error reporting. | ||
/// - closure: The user provided code to modify the database. Use the provided connection to run queries. | ||
/// The connection must stay in the transaction mode. Otherwise this method will throw! | ||
/// - Returns: The closure's return value. | ||
public func withTransaction<Result>( | ||
logger: Logger, | ||
file: String = #file, | ||
line: Int = #line, | ||
isolation: isolated (any Actor)? = #isolation, | ||
// DO NOT FIX THE WHITESPACE IN THE NEXT LINE UNTIL 5.10 IS UNSUPPORTED | ||
// https://github.com/swiftlang/swift/issues/79285 | ||
_ process: (PostgresConnection) async throws -> sending Result) async throws -> sending Result { | ||
do { | ||
try await self.query("BEGIN;", logger: logger) | ||
} catch { | ||
throw PostgresTransactionError(file: file, line: line, beginError: error) | ||
} | ||
|
||
var closureHasFinished: Bool = false | ||
do { | ||
let value = try await process(self) | ||
closureHasFinished = true | ||
try await self.query("COMMIT;", logger: logger) | ||
return value | ||
} catch { | ||
var transactionError = PostgresTransactionError(file: file, line: line) | ||
if !closureHasFinished { | ||
transactionError.closureError = error | ||
do { | ||
try await self.query("ROLLBACK;", logger: logger) | ||
} catch { | ||
transactionError.rollbackError = error | ||
} | ||
} else { | ||
transactionError.commitError = error | ||
} | ||
|
||
throw transactionError | ||
} | ||
} | ||
#else | ||
/// Puts the connection into an open transaction state, for the provided `closure`'s lifetime. | ||
/// | ||
/// The function starts a transaction by running a `BEGIN` query on the connection against the database. It then | ||
/// lends the connection to the user provided closure. The user can then modify the database as they wish. If the user | ||
/// provided closure returns successfully, the function will attempt to commit the changes by running a `COMMIT` | ||
/// query against the database. If the user provided closure throws an error, the function will attempt to rollback the | ||
/// changes made within the closure. | ||
/// | ||
/// - Parameters: | ||
/// - logger: The `Logger` to log into for the transaction. | ||
/// - file: The file, the transaction was started in. Used for better error reporting. | ||
/// - line: The line, the transaction was started in. Used for better error reporting. | ||
/// - closure: The user provided code to modify the database. Use the provided connection to run queries. | ||
/// The connection must stay in the transaction mode. Otherwise this method will throw! | ||
/// - Returns: The closure's return value. | ||
public func withTransaction<Result>( | ||
logger: Logger, | ||
file: String = #file, | ||
line: Int = #line, | ||
_ process: (PostgresConnection) async throws -> Result | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just realized for Swift 6 you probably want to have a variant that adds a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's why I wanted a review from you :) |
||
) async throws -> Result { | ||
do { | ||
try await self.query("BEGIN;", logger: logger) | ||
} catch { | ||
throw PostgresTransactionError(file: file, line: line, beginError: error) | ||
} | ||
|
||
var closureHasFinished: Bool = false | ||
do { | ||
let value = try await process(self) | ||
closureHasFinished = true | ||
try await self.query("COMMIT;", logger: logger) | ||
return value | ||
} catch { | ||
var transactionError = PostgresTransactionError(file: file, line: line) | ||
if !closureHasFinished { | ||
transactionError.closureError = error | ||
do { | ||
try await self.query("ROLLBACK;", logger: logger) | ||
} catch { | ||
transactionError.rollbackError = error | ||
} | ||
} else { | ||
transactionError.commitError = error | ||
} | ||
|
||
throw transactionError | ||
} | ||
} | ||
#endif | ||
} | ||
|
||
// MARK: EventLoopFuture interface | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
/// A wrapper around the errors that can occur during a transaction. | ||
public struct PostgresTransactionError: Error { | ||
|
||
/// The file in which the transaction was started | ||
public var file: String | ||
/// The line in which the transaction was started | ||
public var line: Int | ||
|
||
/// The error thrown when running the `BEGIN` query | ||
public var beginError: Error? | ||
/// The error thrown in the transaction closure | ||
public var closureError: Error? | ||
|
||
/// The error thrown while rolling the transaction back. If the ``closureError`` is set, | ||
/// but the ``rollbackError`` is empty, the rollback was successful. If the ``rollbackError`` | ||
/// is set, the rollback failed. | ||
public var rollbackError: Error? | ||
|
||
/// The error thrown while commiting the transaction. | ||
public var commitError: Error? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we want to have a public init for this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not for now I think. |
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs to be guarded to only apply in Swift 6, need a variant that doesn't have it for Swift 5. Same in PostgresClient.