mirror of https://github.com/AMT-Cheif/drift.git
Document nested transactions
This commit is contained in:
parent
4959ec6235
commit
c74f5d4cb4
|
@ -0,0 +1,56 @@
|
|||
import 'package:drift/drift.dart';
|
||||
import 'tables/filename.dart';
|
||||
|
||||
extension Snippets on MyDatabase {
|
||||
// #docregion deleteCategory
|
||||
Future<void> deleteCategory(Category category) {
|
||||
return transaction(() async {
|
||||
// first, move the affected todo entries back to the default category
|
||||
await (update(todos)..where((row) => row.category.equals(category.id)))
|
||||
.write(const TodosCompanion(category: Value(null)));
|
||||
|
||||
// then, delete the category
|
||||
await delete(categories).delete(category);
|
||||
});
|
||||
}
|
||||
// #enddocregion deleteCategory
|
||||
|
||||
// #docregion nested
|
||||
Future<void> nestedTransactions() async {
|
||||
await transaction(() async {
|
||||
await into(categories)
|
||||
.insert(CategoriesCompanion.insert(description: 'first'));
|
||||
|
||||
// this is a nested transaction:
|
||||
await transaction(() async {
|
||||
// At this point, the first category is visible
|
||||
await into(categories)
|
||||
.insert(CategoriesCompanion.insert(description: 'second'));
|
||||
// Here, the second category is only visible inside this nested
|
||||
// transaction.
|
||||
});
|
||||
|
||||
// At this point, the second category is visible here as well.
|
||||
|
||||
try {
|
||||
await transaction(() async {
|
||||
// At this point, both categories are visible
|
||||
await into(categories)
|
||||
.insert(CategoriesCompanion.insert(description: 'third'));
|
||||
// The third category is only visible here.
|
||||
throw Exception('Abort in the second nested transaction');
|
||||
});
|
||||
} on Exception {
|
||||
// We're catching the exception so that this transaction isn't reverted
|
||||
// as well.
|
||||
}
|
||||
|
||||
// At this point, the third category is NOT visible, but the other two
|
||||
// are. The transaction is in the same state as before the second nested
|
||||
// `transaction()` call.
|
||||
});
|
||||
|
||||
// After the transaction, two categories are visible.
|
||||
}
|
||||
// #enddocregion nested
|
||||
}
|
|
@ -6,9 +6,11 @@ data:
|
|||
|
||||
template: layouts/docs/single
|
||||
aliases:
|
||||
- /transactions/
|
||||
- /transactions/
|
||||
---
|
||||
|
||||
{% assign snippets = "package:moor_documentation/snippets/transactions.dart.excerpt.json" | readString | json_decode %}
|
||||
|
||||
Drift has support for transactions and allows multiple statements to run atomically,
|
||||
so that none of their changes is visible to the main database until the transaction
|
||||
is finished.
|
||||
|
@ -17,28 +19,16 @@ It takes a function as an argument that will be run transactionally. In the
|
|||
following example, which deals with deleting a category, we move all todo entries
|
||||
in that category back to the default category:
|
||||
|
||||
```dart
|
||||
Future deleteCategory(Category category) {
|
||||
return transaction(() async {
|
||||
// first, move the affected todo entries back to the default category
|
||||
await customUpdate(
|
||||
'UPDATE todos SET category = NULL WHERE category = ?',
|
||||
updates: {todos},
|
||||
variables: [Variable.withInt(category.id)],
|
||||
);
|
||||
{% include "blocks/snippet" snippets = snippets name = "deleteCategory" %}
|
||||
|
||||
// then, delete the category
|
||||
await delete(categories).delete(category);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## ⚠️ Gotchas
|
||||
## ⚠️ Important things to know about transactions {#-gotchas}
|
||||
There are a couple of things that should be kept in mind when working with transactions:
|
||||
|
||||
1. __Await all calls__: All queries inside the transaction must be `await`-ed. The transaction
|
||||
will complete when the inner method completes. Without `await`, some queries might be operating
|
||||
on the transaction after it has been closed! This can cause data loss or runtime crashes.
|
||||
Drift contains some runtime checks against this misuse and will throw an exception when a transaction
|
||||
is used after being closed.
|
||||
2. __Different behavior of stream queries__: Inside a `transaction` callback, stream queries behave
|
||||
differently. If you're creating streams inside a transaction, check the next section to learn how
|
||||
they behave.
|
||||
|
@ -49,14 +39,49 @@ updates made in a transaction: All changes to tables will only be reported after
|
|||
transaction completes. Updates inside a transaction don't have an immediate effect on
|
||||
streams, so your data will always be consistent and there aren't any unnecessary updates.
|
||||
|
||||
With streams created _inside_ a `transaction` block (or a nested call in there), it's
|
||||
a different story. Notably, they
|
||||
|
||||
- reflect on changes made in the transaction immediately
|
||||
- complete when the transaction completes
|
||||
Streams created _inside_ a `transaction` block (or in a function that was called inside
|
||||
a `transaction`) block reflect changes made in a transaction immediately.
|
||||
However, such streams close when the transaction completes.
|
||||
|
||||
This behavior is useful if you're collapsing streams inside a transaction, for instance by
|
||||
calling `first` or `fold`.
|
||||
However, we recommend that streams created _inside_ a transaction are not listened to
|
||||
_outside_ of a transaction. While it's possible, it defeats the isolation principle
|
||||
of transactions as its state is exposed through the stream.
|
||||
|
||||
## Nested transactions
|
||||
|
||||
Starting from drift version 2.0, it is possible to nest transactions on most implementations.
|
||||
When calling `transaction` again inside a `transaction` block (directly or indirectly through
|
||||
method invocations), a _nested transaction_ is created. Nested transactions behave as follows:
|
||||
|
||||
- When they start, queries issued in a nested transaction see the state of the database from
|
||||
the outer transaction immediately before the nested transaction was started.
|
||||
- Writes made by a nested transaction are only visible inside the nested transaction at first.
|
||||
The outer transaction and the top-level database don't see them right away, and their stream
|
||||
queries are not updated.
|
||||
- When a nested transaction completes successfully, the outer transaction sees the changes
|
||||
made by the nested transaction as an atomic write (stream queries created in the outer
|
||||
transaction are updated once).
|
||||
- When a nested transaction throws an exception, it is reverted (so in that sense, it behaves
|
||||
just like other transactions).
|
||||
The outer transaction can catch this exception, after it will be in the same state before
|
||||
the nested transaction was started. If it does not catch that exception, it will bubble up
|
||||
and revert that transaction as well.
|
||||
|
||||
The following snippet illustrates the behavior of nested transactions:
|
||||
|
||||
{% include "blocks/snippet" snippets = snippets name = "nested" %}
|
||||
|
||||
### Supported implementations
|
||||
|
||||
Nested transactions require support by the database implementation you're using with drift.
|
||||
All popular implementations support this feature, including:
|
||||
|
||||
- A `NativeDatabase` from `package:drift/native.dart`
|
||||
- A `WasmDatabase` from `package:drift/wasm.dart`
|
||||
- The sql.js-based `WebDatabase` from `package:drift/web.dart`
|
||||
- A `SqfliteDatabase` from `package:drift_sqflite`.
|
||||
|
||||
Further, nested transactions are supported through remote database connections (e.g.
|
||||
isolates or web workers) if the server uses a database implementation that supports them.
|
||||
|
|
|
@ -428,9 +428,11 @@ abstract class DatabaseConnectionUser {
|
|||
/// outside of this transaction will not affect the stream.
|
||||
///
|
||||
/// Starting from drift version 2.0, nested transactions are supported on most
|
||||
/// database implementations (including `NativeDatabase`, TODO list). When
|
||||
/// calling [transaction] inside a [transaction] block on supported database
|
||||
/// implementations, a new transaction will be started.
|
||||
/// database implementations (including `NativeDatabase`, `WebDatabase`,
|
||||
/// `WasmDatabase`, `SqfliteQueryExecutor`, databases relayed through
|
||||
/// isolates or web workers).
|
||||
/// When calling [transaction] inside a [transaction] block on supported
|
||||
/// database implementations, a new transaction will be started.
|
||||
/// For backwards-compatibility, the current transaction will be re-used if
|
||||
/// a nested transaction is started with a database implementation not
|
||||
/// supporting nested transactions. The [requireNew] parameter can be set to
|
||||
|
|
Loading…
Reference in New Issue