diff --git a/moor/lib/src/runtime/database.dart b/moor/lib/src/runtime/database.dart index 8eaa9f8d..f97d2339 100644 --- a/moor/lib/src/runtime/database.dart +++ b/moor/lib/src/runtime/database.dart @@ -139,6 +139,32 @@ mixin QueryEngine on DatabaseConnectionUser { final statement = CustomSelectStatement(query, variables, tables, this); return createStream(statement.constructFetcher()); } + + /// Executes [action] in a transaction, which means that all its queries and + /// updates will be called atomically. + /// + /// Please be aware of the following limitations of transactions: + /// 1. Inside a transaction, auto-updating streams cannot be created. This + /// operation will throw at runtime. The reason behind this is that a + /// stream might have a longer lifespan than a transaction, but it still + /// needs to know about the transaction because the data in a transaction + /// might be different than that of the "global" database instance. + /// 2. Nested transactions are not supported. Calling + /// [GeneratedDatabase.transaction] on the [QueryEngine] passed to the [action] + /// will throw. + /// 3. The code inside [action] must not call any method of this + /// [GeneratedDatabase]. Doing so will cause a dead-lock. Instead, all + /// queries and updates must be sent to the [QueryEngine] passed to the + /// [action] function. + Future transaction(Future Function(QueryEngine transaction) action) async { + final transaction = Transaction(this, executor.beginTransaction()); + + try { + await action(transaction); + } finally { + await transaction.complete(); + } + } } /// A base class for all generated databases. @@ -183,30 +209,4 @@ abstract class GeneratedDatabase extends DatabaseConnectionUser final migrator = _createMigrator(executor); return migration.onUpgrade(migrator, from, to); } - - /// Executes [action] in a transaction, which means that all its queries and - /// updates will be called atomically. - /// - /// Please be aware of the following limitations of transactions: - /// 1. Inside a transaction, auto-updating streams cannot be created. This - /// operation will throw at runtime. The reason behind this is that a - /// stream might have a longer lifespan than a transaction, but it still - /// needs to know about the transaction because the data in a transaction - /// might be different than that of the "global" database instance. - /// 2. Nested transactions are not supported. Calling - /// [GeneratedDatabase.transaction] on the [QueryEngine] passed to the [action] - /// will throw. - /// 3. The code inside [action] must not call any method of this - /// [GeneratedDatabase]. Doing so will cause a dead-lock. Instead, all - /// queries and updates must be sent to the [QueryEngine] passed to the - /// [action] function. - Future transaction(Future Function(QueryEngine transaction) action) async { - final transaction = Transaction(this, executor.beginTransaction()); - - try { - await action(transaction); - } finally { - await transaction.complete(); - } - } } diff --git a/moor/lib/src/runtime/executor/transactions.dart b/moor/lib/src/runtime/executor/transactions.dart index 1451387b..5cfb77bc 100644 --- a/moor/lib/src/runtime/executor/transactions.dart +++ b/moor/lib/src/runtime/executor/transactions.dart @@ -15,6 +15,12 @@ class Transaction extends DatabaseConnectionUser with QueryEngine { await streams.dispatchUpdates(); } + + @override + Future transaction(Function action) async { + throw StateError('Tried to call transaction() in a transaction. This is' + 'not supported'); + } } /// Stream query store that doesn't allow creating new streams and dispatches diff --git a/moor/test/transactions_test.dart b/moor/test/transactions_test.dart index d501e9a2..b9f221fd 100644 --- a/moor/test/transactions_test.dart +++ b/moor/test/transactions_test.dart @@ -24,6 +24,16 @@ void main() { verify(executor.transactions.send()); }); + test('transactions cannot be nested', () { + expect(() async { + await db.transaction((t) async { + await t.transaction((t2) { + fail('nested transactions were allowed'); + }); + }); + }, throwsStateError); + }); + test('transactions notify about table udpates after completing', () async { when(executor.transactions.runUpdate(any, any)) .thenAnswer((_) => Future.value(2));