fix: save user_version in migration transaction (#31039)

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
This commit is contained in:
shenlong
2026-09-23 19:14:55 +05:30
committed by GitHub
co-authored by shenlong-tanwen
parent 20862f1d4a
commit da5c2fedbe
2 changed files with 65 additions and 5 deletions
+16 -5
View File
@@ -168,9 +168,15 @@ class Drift extends $Drift {
await customStatement('PRAGMA foreign_keys = OFF');
try {
await transaction(
() => m.runMigrationSteps(
from: from,
await transaction(() async {
// Re-read the current version inside the transaction to avoid race between different connections.
final current = (await customSelect('PRAGMA user_version').getSingle()).read<int>('user_version');
if (current >= to) {
return;
}
await m.runMigrationSteps(
from: current,
to: to,
steps: migrationSteps(
from1To2: (m, v2) async {
@@ -368,8 +374,13 @@ class Drift extends $Drift {
await _healV33DateTimes(this);
},
),
),
);
);
// This prevents a drift between the current connection and other connections in the pool,
// which is waiting for the transaction to finish. Drift updates this outside of the transaction,
// so doing it inside the transaction ensures that all connections are in sync.
await customStatement('PRAGMA user_version = $to');
});
if (kDebugMode) {
// Fail if the migration broke foreign keys
@@ -0,0 +1,49 @@
import 'dart:io';
import 'package:drift/drift.dart';
import 'package:drift/native.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:immich_mobile/data/db/main/database.dart';
import 'generated/schema_v22.dart' as v22;
void main() {
driftRuntimeOptions.dontWarnAboutMultipleDatabases = true;
late Directory dir;
late File file;
setUp(() async {
dir = await Directory.systemTemp.createTemp('immich_migration_test');
file = File('${dir.path}/db.sqlite');
final old = v22.DatabaseAtV22(NativeDatabase(file));
await old.customStatement('PRAGMA journal_mode = WAL');
await old.localAssetEntity.insertOne(
v22.LocalAssetEntityCompanion.insert(id: 'asset', name: 'asset.mp4', type: 0, durationInSeconds: const .new(5)),
);
await old.close();
});
tearDown(() async => await dir.delete(recursive: true));
Drift open() =>
Drift(NativeDatabase.createInBackground(file, setup: (db) => db.execute('PRAGMA busy_timeout = 30000;')));
test('two isolates migrating the same database concurrently both succeed', () async {
final first = open();
final second = open();
addTearDown(() async {
await first.close();
await second.close();
});
// Migrations are executed lazily; The below forces both instances to run the migration concurrently
await Future.wait([first.customSelect('SELECT 1').get(), second.customSelect('SELECT 1').get()]);
for (final db in [first, second]) {
final version = await db.customSelect('PRAGMA user_version').getSingle();
expect(version.read<int>('user_version'), db.schemaVersion);
}
});
}