Better errors on QueryExecutor misuse, fix beforeOpen

This commit is contained in:
Simon Binder 2019-11-02 11:48:11 +01:00
parent ed4d69a792
commit b189a2bcb2
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
4 changed files with 63 additions and 12 deletions

View File

@ -101,8 +101,11 @@ class Database extends _$Database {
}, },
beforeOpen: (details) async { beforeOpen: (details) async {
if (details.wasCreated) { if (details.wasCreated) {
await into(users) // make sure that transactions can be used in the beforeOpen callback.
await transaction(() {
return into(users)
.insertAll([People.dash, People.duke, People.gopher]); .insertAll([People.dash, People.duke, People.gopher]);
});
} }
}, },
); );

View File

@ -135,7 +135,7 @@ mixin QueryEngine on DatabaseConnectionUser {
/// Whether this connection user is "top level", e.g. there is no parent /// Whether this connection user is "top level", e.g. there is no parent
/// connection user. We consider a [GeneratedDatabase] and a /// connection user. We consider a [GeneratedDatabase] and a
/// [DatabaseAccessor] to be top-level, while a [Transaction] or a /// [DatabaseAccessor] to be top-level, while a [Transaction] or a
/// [BeforeOpenEngine] aren't. /// [BeforeOpenRunner] aren't.
/// ///
/// If any query method is called on a [topLevel] database user, we check if /// If any query method is called on a [topLevel] database user, we check if
/// it could instead be delegated to a child executor. For instance, consider /// it could instead be delegated to a child executor. For instance, consider
@ -478,7 +478,10 @@ abstract class GeneratedDatabase extends DatabaseConnectionUser
final migration = _resolvedMigration; final migration = _resolvedMigration;
if (migration.beforeOpen != null) { if (migration.beforeOpen != null) {
return migration.beforeOpen(details); return _runEngineZoned(
BeforeOpenRunner(this, executor),
() => migration.beforeOpen(details),
);
} }
return Future.value(); return Future.value();
} }
@ -487,10 +490,4 @@ abstract class GeneratedDatabase extends DatabaseConnectionUser
Future<void> close() async { Future<void> close() async {
await executor.close(); await executor.close();
} }
/// Creates another instance of this [GeneratedDatabase] that uses the
/// [connection] instead of the current connection.
GeneratedDatabase cloneWith(DatabaseConnection connection) {
throw UnimplementedError();
}
} }

View File

@ -13,6 +13,10 @@ mixin _ExecutorWithQueryDelegate on QueryExecutor {
bool get logStatements => false; bool get logStatements => false;
final Lock _lock = Lock(); final Lock _lock = Lock();
/// Used to provide better error messages when calling operations without
/// calling [ensureOpen] before.
bool _ensureOpenCalled = false;
Future<T> _synchronized<T>(FutureOr<T> Function() action) async { Future<T> _synchronized<T>(FutureOr<T> Function() action) async {
if (isSequential) { if (isSequential) {
return await _lock.synchronized(action); return await _lock.synchronized(action);
@ -31,6 +35,7 @@ mixin _ExecutorWithQueryDelegate on QueryExecutor {
@override @override
Future<List<Map<String, dynamic>>> runSelect( Future<List<Map<String, dynamic>>> runSelect(
String statement, List args) async { String statement, List args) async {
assert(_ensureOpenCalled);
final result = await _synchronized(() { final result = await _synchronized(() {
_log(statement, args); _log(statement, args);
return impl.runSelect(statement, args); return impl.runSelect(statement, args);
@ -40,6 +45,7 @@ mixin _ExecutorWithQueryDelegate on QueryExecutor {
@override @override
Future<int> runUpdate(String statement, List args) { Future<int> runUpdate(String statement, List args) {
assert(_ensureOpenCalled);
return _synchronized(() { return _synchronized(() {
_log(statement, args); _log(statement, args);
return impl.runUpdate(statement, args); return impl.runUpdate(statement, args);
@ -48,6 +54,7 @@ mixin _ExecutorWithQueryDelegate on QueryExecutor {
@override @override
Future<int> runDelete(String statement, List args) { Future<int> runDelete(String statement, List args) {
assert(_ensureOpenCalled);
return _synchronized(() { return _synchronized(() {
_log(statement, args); _log(statement, args);
return impl.runUpdate(statement, args); return impl.runUpdate(statement, args);
@ -56,6 +63,7 @@ mixin _ExecutorWithQueryDelegate on QueryExecutor {
@override @override
Future<int> runInsert(String statement, List args) { Future<int> runInsert(String statement, List args) {
assert(_ensureOpenCalled);
return _synchronized(() { return _synchronized(() {
_log(statement, args); _log(statement, args);
return impl.runInsert(statement, args); return impl.runInsert(statement, args);
@ -64,6 +72,7 @@ mixin _ExecutorWithQueryDelegate on QueryExecutor {
@override @override
Future<void> runCustom(String statement, [List<dynamic> args]) { Future<void> runCustom(String statement, [List<dynamic> args]) {
assert(_ensureOpenCalled);
return _synchronized(() { return _synchronized(() {
final resolvedArgs = args ?? const []; final resolvedArgs = args ?? const [];
_log(statement, resolvedArgs); _log(statement, resolvedArgs);
@ -73,6 +82,7 @@ mixin _ExecutorWithQueryDelegate on QueryExecutor {
@override @override
Future<void> runBatched(List<BatchedStatement> statements) { Future<void> runBatched(List<BatchedStatement> statements) {
assert(_ensureOpenCalled);
return _synchronized(() { return _synchronized(() {
if (logStatements) { if (logStatements) {
print('Moor: Executing $statements in a batch'); print('Moor: Executing $statements in a batch');
@ -113,6 +123,7 @@ class _TransactionExecutor extends TransactionExecutor
@override @override
Future<bool> ensureOpen() async { Future<bool> ensureOpen() async {
_ensureOpenCalled = true;
if (_openingCompleter != null) { if (_openingCompleter != null) {
return await _openingCompleter.future; return await _openingCompleter.future;
} }
@ -189,7 +200,7 @@ class _TransactionExecutor extends TransactionExecutor
} }
} }
/// A database engine (implements [QueryExecutor]) that delegated the relevant /// A database engine (implements [QueryExecutor]) that delegates the relevant
/// work to a [DatabaseDelegate]. /// work to a [DatabaseDelegate].
class DelegatedDatabase extends QueryExecutor with _ExecutorWithQueryDelegate { class DelegatedDatabase extends QueryExecutor with _ExecutorWithQueryDelegate {
/// The [DatabaseDelegate] to send queries to. /// The [DatabaseDelegate] to send queries to.
@ -217,12 +228,15 @@ class DelegatedDatabase extends QueryExecutor with _ExecutorWithQueryDelegate {
@override @override
Future<bool> ensureOpen() { Future<bool> ensureOpen() {
_ensureOpenCalled = true;
return _openingLock.synchronized(() async { return _openingLock.synchronized(() async {
final alreadyOpen = await delegate.isOpen; final alreadyOpen = await delegate.isOpen;
if (alreadyOpen) { if (alreadyOpen) {
return true; return true;
} }
assert(databaseInfo != null,
'A databaseInfo needs to be set to use a QueryExeuctor');
await delegate.open(databaseInfo); await delegate.open(databaseInfo);
await _runMigrations(); await _runMigrations();
return true; return true;
@ -274,6 +288,30 @@ class DelegatedDatabase extends QueryExecutor with _ExecutorWithQueryDelegate {
} }
Future<void> _runBeforeOpen(OpeningDetails d) { Future<void> _runBeforeOpen(OpeningDetails d) {
return databaseInfo.beforeOpenCallback(this, d); return databaseInfo.beforeOpenCallback(_BeforeOpeningExecutor(this), d);
} }
} }
/// Inside a `beforeOpen` callback, all moor apis must be available. At the same
/// time, the `beforeOpen` callback must complete before any query sent outside
/// of a `beforeOpen` callback can run. We do this by introducing a special
/// executor that delegates all work to the original executor, but without
/// blocking on `ensureOpen`
class _BeforeOpeningExecutor extends QueryExecutor
with _ExecutorWithQueryDelegate {
final DelegatedDatabase _base;
_BeforeOpeningExecutor(this._base);
@override
TransactionExecutor beginTransaction() => _base.beginTransaction();
@override
Future<bool> ensureOpen() {
_ensureOpenCalled = true;
return Future.value(true);
}
@override
QueryDelegate get impl => _base.impl;
}

View File

@ -2,6 +2,8 @@ import 'package:moor/moor.dart';
import 'package:moor/src/runtime/executor/stream_queries.dart'; import 'package:moor/src/runtime/executor/stream_queries.dart';
/// Runs multiple statements transactionally. /// Runs multiple statements transactionally.
///
/// Moor users should use [QueryEngine.transaction] to use this api.
class Transaction extends DatabaseConnectionUser with QueryEngine { class Transaction extends DatabaseConnectionUser with QueryEngine {
/// Constructs a transaction executor from the [other] user and the underlying /// Constructs a transaction executor from the [other] user and the underlying
/// [executor]. /// [executor].
@ -46,3 +48,14 @@ class _TransactionStreamStore extends StreamQueryStore {
return parent.handleTableUpdates(affectedTables); return parent.handleTableUpdates(affectedTables);
} }
} }
/// Special query engine to run the [MigrationStrategy.beforeOpen] callback.
///
/// To use this api, moor users should use the [MigrationStrategy.beforeOpen]
/// parameter inside the [GeneratedDatabase.migration] getter.
class BeforeOpenRunner extends DatabaseConnectionUser with QueryEngine {
/// Creates a [BeforeOpenRunner] from the [database] and the special
/// [executor] running the queries.
BeforeOpenRunner(DatabaseConnectionUser database, QueryExecutor executor)
: super.delegate(database, executor: executor);
}