From a6834088976f12ea7e000be3b2d00e956b76c422 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Sat, 13 Jan 2024 12:14:29 +0100 Subject: [PATCH] Add option to disable migrations --- drift/CHANGELOG.md | 2 + drift/lib/native.dart | 35 +++++++++++++- drift/lib/src/sqlite3/database.dart | 9 +++- .../platforms/ffi/native_database_test.dart | 47 +++++++++++++++++++ 4 files changed, 90 insertions(+), 3 deletions(-) diff --git a/drift/CHANGELOG.md b/drift/CHANGELOG.md index d30af2fb0..b67d826c0 100644 --- a/drift/CHANGELOG.md +++ b/drift/CHANGELOG.md @@ -6,6 +6,8 @@ supported by drift (like UUIDs) on databases that support it while falling back to a text type on sqlite3. - Close wasm databases hosted in workers after the last client disconnects. +- Add `enableMigrations` parameter to `NativeDatabase` which can be used to + optionally disable database migrations when opening databases. ## 2.14.1 diff --git a/drift/lib/native.dart b/drift/lib/native.dart index 6df94b814..4707c79ad 100644 --- a/drift/lib/native.dart +++ b/drift/lib/native.dart @@ -67,17 +67,27 @@ class NativeDatabase extends DelegatedDatabase { /// the database is opened, before drift is fully ready. This can be used to /// add custom user-defined sql functions or to provide encryption keys in /// SQLCipher implementations. + /// + /// By default, drift runs migrations defined in your database class to create + /// tables when the database is first opened or to alter when when your schema + /// changes. This uses the `user_version` sqlite3 pragma, which is compared + /// against the `schemaVersion` getter of the database. + /// If you want to manage migrations independently or don't need them at all, + /// you can disable migrations in drift with the [enableMigrations] + /// parameter. /// {@endtemplate} factory NativeDatabase( File file, { bool logStatements = false, DatabaseSetup? setup, + bool enableMigrations = true, bool cachePreparedStatements = _cacheStatementsByDefault, }) { return NativeDatabase._( _NativeDelegate( file, setup, + enableMigrations, cachePreparedStatements, ), logStatements); @@ -105,6 +115,7 @@ class NativeDatabase extends DelegatedDatabase { bool logStatements = false, bool cachePreparedStatements = _cacheStatementsByDefault, DatabaseSetup? setup, + bool enableMigrations = true, IsolateSetup? isolateSetup, }) { return createBackgroundConnection( @@ -112,6 +123,7 @@ class NativeDatabase extends DelegatedDatabase { logStatements: logStatements, setup: setup, isolateSetup: isolateSetup, + enableMigrations: enableMigrations, cachePreparedStatements: cachePreparedStatements, ); } @@ -126,6 +138,7 @@ class NativeDatabase extends DelegatedDatabase { bool logStatements = false, DatabaseSetup? setup, IsolateSetup? isolateSetup, + bool enableMigrations = true, bool cachePreparedStatements = _cacheStatementsByDefault, }) { return DatabaseConnection.delayed(Future.sync(() async { @@ -136,6 +149,7 @@ class NativeDatabase extends DelegatedDatabase { file.absolute.path, logStatements, cachePreparedStatements, + enableMigrations, setup, isolateSetup, receiveIsolate.sendPort, @@ -159,7 +173,15 @@ class NativeDatabase extends DelegatedDatabase { bool cachePreparedStatements = _cacheStatementsByDefault, }) { return NativeDatabase._( - _NativeDelegate(null, setup, cachePreparedStatements), + _NativeDelegate( + null, + setup, + // Disabling migrations makes no sense for in-memory databases, which + // would always be empty otherwise. They will also not be read-only, so + // what's the point... + true, + cachePreparedStatements, + ), logStatements, ); } @@ -181,6 +203,7 @@ class NativeDatabase extends DelegatedDatabase { bool logStatements = false, DatabaseSetup? setup, bool closeUnderlyingOnClose = true, + bool enableMigrations = true, bool cachePreparedStatements = _cacheStatementsByDefault, }) { return NativeDatabase._( @@ -189,6 +212,7 @@ class NativeDatabase extends DelegatedDatabase { setup, closeUnderlyingOnClose, cachePreparedStatements, + enableMigrations, ), logStatements); } @@ -246,9 +270,11 @@ class NativeDatabase extends DelegatedDatabase { class _NativeDelegate extends Sqlite3Delegate { final File? file; - _NativeDelegate(this.file, DatabaseSetup? setup, bool cachePreparedStatements) + _NativeDelegate(this.file, DatabaseSetup? setup, bool enableMigrations, + bool cachePreparedStatements) : super( setup, + enableMigrations: enableMigrations, cachePreparedStatements: cachePreparedStatements, ); @@ -257,9 +283,11 @@ class _NativeDelegate extends Sqlite3Delegate { super.setup, super.closeUnderlyingWhenClosed, bool cachePreparedStatements, + bool enableMigrations, ) : file = null, super.opened( cachePreparedStatements: cachePreparedStatements, + enableMigrations: enableMigrations, ); @override @@ -334,6 +362,7 @@ class _NativeIsolateStartup { final String path; final bool enableLogs; final bool cachePreparedStatements; + final bool enableMigrations; final DatabaseSetup? setup; final IsolateSetup? isolateSetup; final SendPort sendServer; @@ -342,6 +371,7 @@ class _NativeIsolateStartup { this.path, this.enableLogs, this.cachePreparedStatements, + this.enableMigrations, this.setup, this.isolateSetup, this.sendServer, @@ -354,6 +384,7 @@ class _NativeIsolateStartup { File(startup.path), logStatements: startup.enableLogs, cachePreparedStatements: startup.cachePreparedStatements, + enableMigrations: startup.enableMigrations, setup: startup.setup, )); }); diff --git a/drift/lib/src/sqlite3/database.dart b/drift/lib/src/sqlite3/database.dart index 647d705f5..088b82520 100644 --- a/drift/lib/src/sqlite3/database.dart +++ b/drift/lib/src/sqlite3/database.dart @@ -28,6 +28,9 @@ abstract class Sqlite3Delegate /// Whether prepared statements should be cached. final bool cachePreparedStatements; + /// Whether migrations are enabled (they are by default). + final bool enableMigrations; + /// Whether the [database] should be closed when [close] is called on this /// instance. /// @@ -40,6 +43,7 @@ abstract class Sqlite3Delegate /// A delegate that will call [openDatabase] to open the database. Sqlite3Delegate( this._setup, { + this.enableMigrations = true, required this.cachePreparedStatements, }) : closeUnderlyingWhenClosed = true; @@ -49,6 +53,7 @@ abstract class Sqlite3Delegate this._database, this._setup, this.closeUnderlyingWhenClosed, { + this.enableMigrations = true, required this.cachePreparedStatements, }) { _initializeDatabase(); @@ -98,7 +103,9 @@ abstract class Sqlite3Delegate database.useNativeFunctions(); _setup?.call(database); - versionDelegate = _SqliteVersionDelegate(database); + versionDelegate = enableMigrations + ? _SqliteVersionDelegate(database) + : const NoVersionDelegate(); _hasInitializedDatabase = true; } diff --git a/drift/test/platforms/ffi/native_database_test.dart b/drift/test/platforms/ffi/native_database_test.dart index e073c491f..f77a9ff6b 100644 --- a/drift/test/platforms/ffi/native_database_test.dart +++ b/drift/test/platforms/ffi/native_database_test.dart @@ -184,6 +184,53 @@ void main() { await db.close(); expect(underlying.activeStatementCount, isZero); }); + + group('can disable migrations', () { + Future runTest(QueryExecutor executor) async { + final db = TodoDb(executor); + db.migration = MigrationStrategy( + onCreate: (_) => fail('should not call onCreate'), + onUpgrade: (_, __, ___) => fail('should not call onUpgrade'), + beforeOpen: expectAsync1((details) async {}), + ); + + addTearDown(db.close); + await db.customSelect('select 1').get(); // open database + } + + test( + 'with native database', + () => runTest(NativeDatabase( + File(d.path('test.db')), + enableMigrations: false, + )), + ); + + test( + 'createInBackground', + () => runTest(NativeDatabase.createInBackground( + File(d.path('test.db')), + enableMigrations: false, + )), + ); + + test( + 'createBackgroundConnection', + () => runTest(NativeDatabase.createBackgroundConnection( + File(d.path('test.db')), + enableMigrations: false, + )), + ); + + test( + 'opened', + () => runTest(NativeDatabase.opened( + sqlite3.openInMemory(), + enableMigrations: false, + closeUnderlyingOnClose: true, + )), + ); + }); } class _FakeExecutorUser extends QueryExecutorUser {