run method
Runs all pending migrations that haven't been applied yet.
Each migration is executed within its own transaction. If a migration fails, the transaction is rolled back and no further migrations are run.
Implementation
Future<void> run() async {
await _ensureMigrationsTable();
// Acquire advisory lock to prevent concurrent migration runs
final locked = await _acquireLock();
if (!locked) {
Logger.staticWarning('⚠️ Another migration process is running. Skipping.');
return;
}
try {
final applied = await _getAppliedMigrations();
final pending = _registry.where((e) => !applied.contains(e.name)).toList();
if (pending.isEmpty) {
Logger.staticInfo('✅ Nothing to migrate.');
return;
}
// Calculate the next batch number ONCE for the entire run
final batch = await _getNextBatchNumber();
var successCount = 0;
for (final entry in pending) {
Logger.staticInfo('🚀 Migrating: ${entry.name}');
try {
await _db.transaction((tx) async {
await entry.migration.up(tx);
await tx.query(
'INSERT INTO migrations (name, batch) VALUES (@name, @batch)',
{'name': entry.name, 'batch': batch},
);
});
successCount++;
} catch (e) {
Logger.staticWarning(
'❌ Migration "${entry.name}" failed: $e\n'
' Transaction rolled back. $successCount migration(s) were applied before this failure.'
);
return; // Stop running further migrations
}
}
Logger.staticInfo('✨ Successfully applied $successCount migration(s) in batch $batch.');
} finally {
await _releaseLock();
}
}