Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 24 additions & 2 deletions docs/content/migrations/step_by_step.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ This simplifies writing migrations a lot, since:

The `stepByStep` function generated by the `drift_dev schema steps` command gives you an
`OnUpgrade` callback.
But you might want to customize the upgrade behavior, for instance by adding foreign key
checks afterwards.
But you might want to customize the upgrade behavior, for instance by running each step in a
transaction or by adding foreign key checks afterwards.

The `Migrator.runMigrationSteps` helper method can be used for that, as this example
shows:
Expand All @@ -81,6 +81,28 @@ Here, foreign keys are disabled before running the migration and re-enabled afte
A check ensuring no inconsistencies occurred helps to catch issues with the migration
in debug modes.

### Transactions

Drift doesn't run migrations in a transaction by default, since some pragmas can't be changed
inside a transaction. Without one, a step that throws or gets interrupted (for instance because
the app was terminated) leaves the database partially migrated: the statements of that step that
already ran are kept, but the stored schema version is still the one from before that step.
The next time the database is opened, the step runs again and can fail, for instance because a
column it renames no longer exists under its old name.

Running each step in its own transaction, as the example above does, avoids this: either all
changes of a step are applied or none of them are. Since drift updates the schema version
(`PRAGMA user_version`) after each step, the next attempt starts with the step that didn't
complete. To apply the entire upgrade atomically instead, wrap the `runMigrationSteps` call in
a single `transaction`.

Pragmas that can't be changed in a transaction need to be set before the migration steps run.
In particular, `PRAGMA foreign_keys` has no effect inside a transaction, which is why the example
disables foreign keys before calling `runMigrationSteps`. This also matters for `alterTable`: it
disables foreign keys while re-creating a table, which doesn't work inside a transaction. Unless
foreign keys were disabled before the transaction started, dropping the old table fails or runs
`ON DELETE` actions on tables referencing it.

## Moving to step-by-step migrations

If you've been using drift before `stepByStep` was added to the library, or if you've never exported a schema,
Expand Down
2 changes: 1 addition & 1 deletion docs/lib/src/generated_snippets.dart

Large diffs are not rendered by default.

29 changes: 17 additions & 12 deletions docs/lib/src/snippets/migrations/step_by_step.dart
Original file line number Diff line number Diff line change
Expand Up @@ -47,21 +47,26 @@ extension StepByStep2 on GeneratedDatabase {
// (https://drift.simonbinder.eu/docs/advanced-features/migrations/#tips)
await customStatement('PRAGMA foreign_keys = OFF');

final steps = migrationSteps(
from1To2: (m, schema) async {
// we added the dueDate property in the change from version 1 to
// version 2
await m.addColumn(schema.todos, schema.todos.dueDate);
},
from2To3: (m, schema) async {
// we added the priority property in the change from version 1 or 2
// to version 3
await m.addColumn(schema.todos, schema.todos.priority);
},
);

await m.runMigrationSteps(
from: from,
to: to,
steps: migrationSteps(
from1To2: (m, schema) async {
// we added the dueDate property in the change from version 1 to
// version 2
await m.addColumn(schema.todos, schema.todos.dueDate);
},
from2To3: (m, schema) async {
// we added the priority property in the change from version 1 or 2
// to version 3
await m.addColumn(schema.todos, schema.todos.priority);
},
),
// Running each step in a transaction ensures that a step is either
// applied completely or not at all.
steps: (currentVersion, database) =>
database.transaction(() => steps(currentVersion, database)),
);

if (kDebugMode) {
Expand Down
Loading