transaction<R> method

Future<R> transaction<R>(
  1. Future<R> action(
    1. TSelf db
    )
)

Runs action inside a transaction, with a new TSelf over the transaction's session.

Every table getter inside action sees the same transaction session, so a read and a write in the same callback go to the same physical lease.

Three cases, and none of them silently runs action without a transaction:

  • A connection-backed database opens one on that connection.
  • A withPool database takes one lease and opens the transaction on it, so the whole callback runs on one physical connection.
  • A database already forked over an MssqlTransaction nests through a savepoint, so a failure inside action rolls back action's writes and leaves the outer transaction to its own scope.

A session that is none of those — a bare MssqlSession implementation that cannot hold a lease — is refused rather than run unprotected.

Implementation

Future<R> transaction<R>(Future<R> Function(TSelf db) action) async {
  final s = session;
  if (s is MssqlTransaction) {
    // Nested: the inner scope is a savepoint of the open transaction, not
    // a second transaction and not an unprotected run. Rolling back to the
    // savepoint undoes [action] and nothing the caller did before it.
    return s.savepoint<R>(() => action(this as TSelf));
  }
  if (s is MssqlConnection) {
    return _openOn<R>(action, (callback) => s.transaction(callback));
  }
  final borrowed = pool;
  if (borrowed != null) {
    return _openOn<R>(action, (callback) => borrowed.transaction(callback));
  }
  throw StateError(
    'This AppDatabase runs on a session that cannot open a transaction: '
    '${s.runtimeType}. Build it with AppDatabase.borrow(connection), '
    'AppDatabase.withPool(pool) or AppDatabase.open(config). Running the '
    'callback without a transaction would look like it worked and leave '
    'every statement in it separately committed.',
  );
}