Allow transactions in DAOs

This commit is contained in:
Simon Binder 2019-03-27 18:20:16 +01:00
parent 2e808c6ead
commit f3a0f5f230
No known key found for this signature in database
GPG Key ID: B807FDF954BA00CF
3 changed files with 42 additions and 26 deletions

View File

@ -139,6 +139,32 @@ mixin QueryEngine on DatabaseConnectionUser {
final statement = CustomSelectStatement(query, variables, tables, this); final statement = CustomSelectStatement(query, variables, tables, this);
return createStream(statement.constructFetcher()); 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. /// A base class for all generated databases.
@ -183,30 +209,4 @@ abstract class GeneratedDatabase extends DatabaseConnectionUser
final migrator = _createMigrator(executor); final migrator = _createMigrator(executor);
return migration.onUpgrade(migrator, from, to); 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();
}
}
} }

View File

@ -15,6 +15,12 @@ class Transaction extends DatabaseConnectionUser with QueryEngine {
await streams.dispatchUpdates(); 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 /// Stream query store that doesn't allow creating new streams and dispatches

View File

@ -24,6 +24,16 @@ void main() {
verify(executor.transactions.send()); 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 { test('transactions notify about table udpates after completing', () async {
when(executor.transactions.runUpdate(any, any)) when(executor.transactions.runUpdate(any, any))
.thenAnswer((_) => Future.value(2)); .thenAnswer((_) => Future.value(2));