runInTransaction<T extends Object> method
Future<Result<T> >
runInTransaction<T extends Object>(
- String connectionId,
- Future<
Result< action(T> >- int txnId
- IsolationLevel? isolationLevel,
- SavepointDialect? savepointDialect,
- TransactionAccessMode? accessMode,
- Duration? lockTimeout,
override
Runs action inside a transaction with automatic commit on success
and rollback on any failure (returned Failure or thrown exception).
Sprint 4.4 — ergonomic helper that captures the begin/commit/rollback
dance behind a single call so application code never has to manage
the txnId lifecycle by hand.
actionreceives the livetxnIdand returns aResult<T>. ReturningSuccess(value)triggerscommitTransaction; returningFailure(error)triggersrollbackTransactionand the original error is propagated.- When
actionthrows, the transaction is rolled back and the exception is converted to aQueryError. The original exception is preserved in the error message for diagnostics. - When the rollback itself fails, the original error wins; the rollback failure is logged via the underlying repository (which already does this in rollbackTransaction).
- Default isolation is
IsolationLevel.readCommitted, default dialect isSavepointDialect.auto, default access mode isTransactionAccessMode.readWrite— same defaults as beginTransaction.
Example:
final result = await service.runInTransaction<int>(
connId,
(txnId) async {
final r1 = await service.executeQueryParams(
connId, 'INSERT INTO logs(msg) VALUES (?)', ['hi'],
);
if (r1.isError()) return Failure(r1.exceptionOrNull()!);
return const Success(42);
},
accessMode: TransactionAccessMode.readWrite,
);
Implementation
@override
Future<Result<T>> runInTransaction<T extends Object>(
String connectionId,
Future<Result<T>> Function(int txnId) action, {
IsolationLevel? isolationLevel,
SavepointDialect? savepointDialect,
TransactionAccessMode? accessMode,
Duration? lockTimeout,
}) async {
final beginResult = await beginTransaction(
connectionId,
isolationLevel: isolationLevel,
savepointDialect: savepointDialect,
accessMode: accessMode,
lockTimeout: lockTimeout,
);
// Early-out when we couldn't even open the transaction. The wire
// format guarantees the failure carries an OdbcError, so we just
// forward it untouched.
if (beginResult.isError()) {
return Failure(beginResult.exceptionOrNull()!);
}
final txnId = beginResult.getOrNull()!;
Result<T> userResult;
try {
userResult = await action(txnId);
} on Object catch (e, st) {
// The whole point of the helper is to catch *any* throw the user
// emits and convert it into a Failure + rollback. Typed catches
// would defeat the contract — exceptions must never escape
// `runInTransaction`.
await _safelyRollback(connectionId, txnId);
return Failure(
QueryError(
message: 'runInTransaction: action threw ${e.runtimeType}: $e\n$st',
),
);
}
if (userResult.isError()) {
// The action returned a Failure. Roll back, then propagate the
// original error verbatim so the caller's diagnostics aren't
// muddied by transaction bookkeeping.
await _safelyRollback(connectionId, txnId);
return userResult;
}
final commitResult = await commitTransaction(connectionId, txnId);
if (commitResult.isError()) {
// Commit failed *after* the action succeeded. By driver contract
// the engine has rolled back (or is in an undefined state, which
// we model as rolled back). Surface the commit failure so the
// caller knows the unit of work didn't actually persist.
return Failure(commitResult.exceptionOrNull()!);
}
return userResult;
}