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:
co-authored by
shenlong-tanwen
parent
20862f1d4a
commit
da5c2fedbe
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user