mirror of https://github.com/AMT-Cheif/drift.git
Better errors on QueryExecutor misuse, fix beforeOpen
This commit is contained in:
parent
ed4d69a792
commit
b189a2bcb2
|
@ -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]);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue