From 3403161178a602907cf125a87da43d9dee42fea9 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Sun, 27 Aug 2023 15:31:04 +0200 Subject: [PATCH] Expose `runMigrationSteps`, add docs --- docs/README.md | 1 + docs/lib/snippets/migrations/migrations.dart | 46 ++++++++++++++++++- .../docs/Advanced Features/migrations.md | 16 +++++++ drift/CHANGELOG.md | 1 + drift/lib/internal/versioned_schema.dart | 1 - .../src/runtime/query_builder/migration.dart | 43 +++++++++++++++++ 6 files changed, 106 insertions(+), 2 deletions(-) diff --git a/docs/README.md b/docs/README.md index 5cec6abd..c44fa3ce 100644 --- a/docs/README.md +++ b/docs/README.md @@ -16,5 +16,6 @@ dart run build_runner serve web:8080 --live-reload To build the website into a directory `out`, use: ``` +dart run drift_dev schema steps lib/snippets/migrations/exported_eschema/ lib/database/schema_versions.dart dart run build_runner build --release --output web:out ``` diff --git a/docs/lib/snippets/migrations/migrations.dart b/docs/lib/snippets/migrations/migrations.dart index 6920e660..c64cc466 100644 --- a/docs/lib/snippets/migrations/migrations.dart +++ b/docs/lib/snippets/migrations/migrations.dart @@ -1,7 +1,7 @@ import 'package:drift/drift.dart'; // #docregion stepbystep -// This file was generated by `drift_dev schema steps drift_schemas lib/database/schema_versions.dart +// This file was generated by `drift_dev schema steps drift_schemas lib/database/schema_versions.dart` import 'schema_versions.dart'; // #enddocregion stepbystep @@ -121,3 +121,47 @@ class StepByStep { } // #enddocregion stepbystep } + +extension StepByStep2 on GeneratedDatabase { + MigrationStrategy get migration { + return MigrationStrategy( + onCreate: (Migrator m) async { + await m.createAll(); + }, + // #docregion stepbystep2 + onUpgrade: (m, from, to) async { + // Run migration steps without foreign keys and re-enable them later + // (https://drift.simonbinder.eu/docs/advanced-features/migrations/#tips) + await customStatement('PRAGMA foreign_keys = OFF'); + + 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); + }, + ), + ); + + if (kDebugMode) { + // Fail if the migration broke foreign keys + final wrongForeignKeys = + await customSelect('PRAGMA foreign_key_check').get(); + assert(wrongForeignKeys.isEmpty, + '${wrongForeignKeys.map((e) => e.data)}'); + } + + await customStatement('PRAGMA foreign_keys = ON;'); + }, + // #enddocregion stepbystep2 + ); + } +} diff --git a/docs/pages/docs/Advanced Features/migrations.md b/docs/pages/docs/Advanced Features/migrations.md index 09eab150..0bfc2e9b 100644 --- a/docs/pages/docs/Advanced Features/migrations.md +++ b/docs/pages/docs/Advanced Features/migrations.md @@ -180,6 +180,22 @@ For instance, in the `from1To2` function, `schema` provides getters for the data The migrator passed to the function is also set up to consider that specific version by default. A call to `m.recreateAllViews()` would re-create views at the expected state of schema version 2, for instance. +#### Customizing step-by-step migrations + +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 (as described in [tips](#tips)). + +The `Migrator.runMigrationSteps` helper method can be used for that, as this example +shows: + +{% include "blocks/snippet" snippets = snippets name = 'stepbystep2' %} + +Here, foreign keys are disabled before runnign the migration and re-enabled afterwards. +A check ensuring no inconsistencies occurred helps catching issues with the migration +in debug modes. + ### Writing tests After you've exported the database schemas into a folder, you can generate old versions of your database class diff --git a/drift/CHANGELOG.md b/drift/CHANGELOG.md index a770c7bc..abb74288 100644 --- a/drift/CHANGELOG.md +++ b/drift/CHANGELOG.md @@ -2,6 +2,7 @@ - Allow using `.read()` for a column added to a join from the table, fixing a regression in drift 2.11.0. +- Make step-by-step migrations easier to customize with `Migrator.runMigrationSteps`. ## 2.11.0 diff --git a/drift/lib/internal/versioned_schema.dart b/drift/lib/internal/versioned_schema.dart index 62ebc871..70776455 100644 --- a/drift/lib/internal/versioned_schema.dart +++ b/drift/lib/internal/versioned_schema.dart @@ -91,7 +91,6 @@ abstract base class VersionedSchema { /// await customStatement('PRAGMA foreign_keys = ON;'); /// }, /// ``` - static Future runMigrationSteps({ required Migrator migrator, required int from, diff --git a/drift/lib/src/runtime/query_builder/migration.dart b/drift/lib/src/runtime/query_builder/migration.dart index 258ef79d..956f1920 100644 --- a/drift/lib/src/runtime/query_builder/migration.dart +++ b/drift/lib/src/runtime/query_builder/migration.dart @@ -514,6 +514,49 @@ class Migrator { }) { return VersionedSchema.stepByStepHelper(step: step); } + + /// Allows customizing step-by-step migrations (like the `OnUpgrade` callback + /// returned by the generated `stepByStep` function) by invoking the [steps] + /// function for each intermediate schema version from [from] until [to] is + /// reached. + /// + /// This can be used to implement a custom `OnUpgrade` callback that runs + /// additional checks before and after the migrations: + /// + /// ```dart + /// onUpgrade: (m, from, to) async { + /// await customStatement('PRAGMA foreign_keys = OFF'); + /// + /// await transaction( + /// () => m.runMigrationSteps( + /// from: from, + /// to: to, + /// steps: migrationSteps( + /// from1To2: ..., + /// ... + /// ), + /// ), + /// ); + /// + /// if (kDebugMode) { + /// final wrongForeignKeys = await customSelect('PRAGMA foreign_key_check').get(); + /// assert(wrongForeignKeys.isEmpty, '${wrongForeignKeys.map((e) => e.data)}'); + /// } + /// + /// await customStatement('PRAGMA foreign_keys = ON;'); + /// }, + /// ``` + /// + /// Here, the `migrationSteps` method is generated by drift with the + /// `drift_dev schema steps` command. For details, see the [documentation](https://drift.simonbinder.eu/docs/advanced-features/migrations/#step-by-step). + Future runMigrationSteps({ + required int from, + required int to, + required MigrationStepWithVersion steps, + }) { + return VersionedSchema.runMigrationSteps( + migrator: this, from: from, to: to, steps: steps); + } } /// Provides information about whether migrations ran before opening the