diff --git a/extras/integration_tests/tests/lib/suite/suite.dart b/extras/integration_tests/tests/lib/suite/suite.dart index 9c25ec1f..8c58f06a 100644 --- a/extras/integration_tests/tests/lib/suite/suite.dart +++ b/extras/integration_tests/tests/lib/suite/suite.dart @@ -14,6 +14,8 @@ abstract class TestExecutor { } void runAllTests(TestExecutor executor) { + moorRuntimeOptions.dontWarnAboutMultipleDatabases = true; + tearDown(() async { await executor.deleteData(); }); diff --git a/moor/lib/src/runtime/api/db_base.dart b/moor/lib/src/runtime/api/db_base.dart index 9096889f..9afc436f 100644 --- a/moor/lib/src/runtime/api/db_base.dart +++ b/moor/lib/src/runtime/api/db_base.dart @@ -1,5 +1,14 @@ part of 'runtime_api.dart'; +/// Keep track of how many databases have been opened for a given database +/// type. +/// We get a number of error reports of "moor not generating tables" that have +/// their origin in users opening multiple instances of their database. This +/// can cause a race conditions when the second [GeneratedDatabase] is opening a +/// underlying [DatabaseConnection] that is already opened but doesn't have the +/// tables created. +Map _openedDbCount = {}; + /// A base class for all generated databases. abstract class GeneratedDatabase extends DatabaseConnectionUser with QueryEngine { @@ -33,12 +42,38 @@ abstract class GeneratedDatabase extends DatabaseConnectionUser {StreamQueryStore streamStore}) : super(types, executor, streamQueries: streamStore) { executor?.databaseInfo = this; + assert(_handleInstantiated()); } /// Used by generated code to connect to a database that is already open. GeneratedDatabase.connect(DatabaseConnection connection) : super.fromConnection(connection) { connection?.executor?.databaseInfo = this; + assert(_handleInstantiated()); + } + + bool _handleInstantiated() { + if (!_openedDbCount.containsKey(runtimeType) || + moorRuntimeOptions.dontWarnAboutMultipleDatabases) { + _openedDbCount[runtimeType] = 1; + return true; + } + final count = ++_openedDbCount[runtimeType]; + if (count > 1) { + print( + 'WARNING (moor): It looks like you\'ve created the database ' + '$runtimeType multiple times. When these two databases use the same ' + 'QueryExecutor, race conditions will ocur and might corrupt the ' + 'database. \n' + 'Try to follow the advice at https://moor.simonbinder.eu/faq/#using-the-database ' + 'or, if you know what you\'re doing, set moorRuntimeOptions.dontWarnAboutMultipleDatabases = true\n' + 'Here is the stacktrace from when the database was opened a second ' + 'time:\n${StackTrace.current}\n' + 'This warning will only appear on debug builds.', + ); + } + + return true; } /// Creates a [Migrator] with the provided query executor. Migrators generate @@ -89,5 +124,6 @@ abstract class GeneratedDatabase extends DatabaseConnectionUser /// Closes this database and releases associated resources. Future close() async { await executor.close(); + _openedDbCount[runtimeType]--; } } diff --git a/moor/lib/src/runtime/api/runtime_api.dart b/moor/lib/src/runtime/api/runtime_api.dart index 9641cba8..6920bd86 100644 --- a/moor/lib/src/runtime/api/runtime_api.dart +++ b/moor/lib/src/runtime/api/runtime_api.dart @@ -9,3 +9,16 @@ part 'connection.dart'; part 'db_base.dart'; part 'dao_base.dart'; part 'query_engine.dart'; + +/// Defines additional runtime behavior for moor. Changing the fields of this +/// class is rarely necessary. +class MoorRuntimeOptions { + /// Don't warn when a database class isn't used as singleton. + bool dontWarnAboutMultipleDatabases = false; +} + +/// Stores the [MoorRuntimeOptions] describing global moor behavior across +/// databases. +/// +/// Note that is is adapting this behavior is rarely needed. +MoorRuntimeOptions moorRuntimeOptions = MoorRuntimeOptions(); diff --git a/moor/test/data/tables/custom_tables.dart b/moor/test/data/tables/custom_tables.dart index 71e9feb5..75bf5df5 100644 --- a/moor/test/data/tables/custom_tables.dart +++ b/moor/test/data/tables/custom_tables.dart @@ -7,7 +7,9 @@ part 'custom_tables.g.dart'; queries: {'writeConfig': 'REPLACE INTO config VALUES (:key, :value)'}, ) class CustomTablesDb extends _$CustomTablesDb { - CustomTablesDb(QueryExecutor e) : super(e); + CustomTablesDb(QueryExecutor e) : super(e) { + moorRuntimeOptions.dontWarnAboutMultipleDatabases = true; + } @override int get schemaVersion => 1; diff --git a/moor/test/data/tables/todos.dart b/moor/test/data/tables/todos.dart index a7107f8a..7ea681b1 100644 --- a/moor/test/data/tables/todos.dart +++ b/moor/test/data/tables/todos.dart @@ -99,8 +99,12 @@ class CustomConverter extends TypeConverter { }, ) class TodoDb extends _$TodoDb { - TodoDb(QueryExecutor e) : super(e); - TodoDb.connect(DatabaseConnection connection) : super.connect(connection); + TodoDb(QueryExecutor e) : super(e) { + moorRuntimeOptions.dontWarnAboutMultipleDatabases = true; + } + TodoDb.connect(DatabaseConnection connection) : super.connect(connection) { + moorRuntimeOptions.dontWarnAboutMultipleDatabases = true; + } @override MigrationStrategy get migration => MigrationStrategy(); diff --git a/moor/test/database_test.dart b/moor/test/database_test.dart index b74f46bc..eec01050 100644 --- a/moor/test/database_test.dart +++ b/moor/test/database_test.dart @@ -32,6 +32,8 @@ class _FakeDb extends GeneratedDatabase { } void main() { + moorRuntimeOptions.dontWarnAboutMultipleDatabases = true; + test('status of OpeningDetails', () { expect(const OpeningDetails(null, 1).wasCreated, true); expect(const OpeningDetails(2, 4).wasCreated, false);