diff --git a/drift/CHANGELOG.md b/drift/CHANGELOG.md index bf41ecbd..4f8ef99b 100644 --- a/drift/CHANGELOG.md +++ b/drift/CHANGELOG.md @@ -8,6 +8,8 @@ generate a mapping to the new `DriftAny` type. - Fix `UNIQUE` keys declared in drift files being written twice. - Fix `customConstraints` not appearing in dumped database schema files. +- Work-around an issue causing complex migrations via `Migrator.alterTable` not to + work if a view referenced the altered table. ## 2.3.0-dev diff --git a/drift/lib/src/runtime/query_builder/migration.dart b/drift/lib/src/runtime/query_builder/migration.dart index f6b97107..8d2124e9 100644 --- a/drift/lib/src/runtime/query_builder/migration.dart +++ b/drift/lib/src/runtime/query_builder/migration.dart @@ -146,6 +146,9 @@ class Migrator { final foreignKeysEnabled = (await _db.customSelect('PRAGMA foreign_keys').getSingle()) .read('foreign_keys'); + final legacyAlterTable = + (await _db.customSelect('PRAGMA legacy_alter_table').getSingle()) + .read('legacy_alter_table'); if (foreignKeysEnabled) { await _db.customStatement('PRAGMA foreign_keys = OFF;'); @@ -235,11 +238,24 @@ class Migrator { // Step 6: Drop the old table await _issueCustomQuery('DROP TABLE ${context.identifier(tableName)}'); + // This step is not mentioned in the documentation, but: If we use `ALTER` + // on an inconsistent schema (and it is inconsistent right now because + // we've just dropped the original table), we need to enable the legacy + // option which skips the integrity check. + // See also: https://sqlite.org/forum/forumpost/0e2390093fbb8fd6 + if (!legacyAlterTable) { + await _issueCustomQuery('pragma legacy_alter_table = 1;'); + } + // Step 7: Rename the new table to the old name await _issueCustomQuery( 'ALTER TABLE ${context.identifier(temporaryName)} ' 'RENAME TO ${context.identifier(tableName)}'); + if (!legacyAlterTable) { + await _issueCustomQuery('pragma legacy_alter_table = 0;'); + } + // Step 8: Re-create associated indexes, triggers and views for (final stmt in createAffected) { await _issueCustomQuery(stmt); diff --git a/drift/test/integration_tests/migrations_integration_test.dart b/drift/test/integration_tests/migrations_integration_test.dart index 9ca31ce6..6ec2fd97 100644 --- a/drift/test/integration_tests/migrations_integration_test.dart +++ b/drift/test/integration_tests/migrations_integration_test.dart @@ -15,7 +15,8 @@ void main() { test('change column types', () async { // Create todos table with category as text (it's an int? in Dart). final executor = NativeDatabase.memory(setup: (db) { - db.execute(''' + db + ..execute(''' CREATE TABLE todos ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, @@ -25,14 +26,12 @@ void main() { status TEXT NULL, UNIQUE(title, category) ); - '''); - - db.execute('CREATE INDEX my_index ON todos (content);'); - - db.execute('INSERT INTO todos (title, content, target_date, category) ' - "VALUES ('title', 'content', 0, '12')"); - - db.execute('PRAGMA foreign_keys = ON'); + ''') + ..execute('CREATE INDEX my_index ON todos (content);') + ..execute('INSERT INTO todos (title, content, target_date, category) ' + "VALUES ('title', 'content', 0, '12')") + ..execute('CREATE VIEW todo_categories AS SELECT category FROM todos;') + ..execute('PRAGMA foreign_keys = ON'); }); final db = TodoDb(executor); @@ -63,6 +62,11 @@ void main() { final foreignKeysResult = await db.customSelect('PRAGMA foreign_keys').getSingle(); expect(foreignKeysResult.read('foreign_keys'), isTrue); + + // Similarly, the legacy_alter_table behavior should be disabled. + final legacyAlterTable = + await db.customSelect('PRAGMA legacy_alter_table').getSingle(); + expect(legacyAlterTable.read('legacy_alter_table'), isFalse); }); test('rename columns', () async { diff --git a/examples/migrations_example/lib/database.dart b/examples/migrations_example/lib/database.dart index bad8d6fd..da611213 100644 --- a/examples/migrations_example/lib/database.dart +++ b/examples/migrations_example/lib/database.dart @@ -63,18 +63,11 @@ class Database extends _$Database { break; case 8: // Added a unique key to the users table - - // TODO: Figure out why dropping the view is necessary (https://sqlite.org/forum/forumpost/de614349cb) - await m.drop(groupCount); await m.alterTable(TableMigration(v8.Users(this))); - await m.recreateAllViews(); break; case 9: // Added a check to the users table - await m.drop(groupCount); await m.alterTable(TableMigration(users)); - await m.recreateAllViews(); - break; } }