mirror of https://github.com/AMT-Cheif/drift.git
Add DatabaseConnection.delayed constructor
This commit is contained in:
parent
a037de6621
commit
a2b28945d1
|
@ -63,6 +63,24 @@ void main() async {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If you need to construct the database outside of an `async` context, you can use the new
|
||||||
|
`DatabaseConnection.delayed` constructor introduced in moor 3.4. In the example above, you
|
||||||
|
could synchronously obtain a `TodoDb` by using:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
Future<DatabaseConnection> _connectAsync() async {
|
||||||
|
MoorIsolate isolate = await MoorIsolate.spawn(_backgroundConnection);
|
||||||
|
return isolate.connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
final db = TodoDb.connect(DatabaseConnection.delayed(_connectAsync()));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This can be helpful when using moor in DI frameworks, since you have the database available
|
||||||
|
immediately. Internally, moor will connect when the first query is sent to the database.
|
||||||
|
|
||||||
### Initialization on the main thread
|
### Initialization on the main thread
|
||||||
|
|
||||||
Platform channels are not available on background isolates, but sometimes you might want to use
|
Platform channels are not available on background isolates, but sometimes you might want to use
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
## 3.4.0 (unreleased)
|
||||||
|
|
||||||
|
- New `DatabaseConnection.delayed` constructor to synchronously obtain a database connection
|
||||||
|
that requires async setup. This can be useful when connecting to a `MoorIsolate`.
|
||||||
|
|
||||||
## 3.3.1
|
## 3.3.1
|
||||||
|
|
||||||
- Support changing `onData` handlers for query streams.
|
- Support changing `onData` handlers for query streams.
|
||||||
|
|
|
@ -26,6 +26,41 @@ class DatabaseConnection {
|
||||||
: typeSystem = SqlTypeSystem.defaultInstance,
|
: typeSystem = SqlTypeSystem.defaultInstance,
|
||||||
streamQueries = StreamQueryStore();
|
streamQueries = StreamQueryStore();
|
||||||
|
|
||||||
|
/// Database connection that is instantly available, but delegates work to a
|
||||||
|
/// connection only available through a `Future`.
|
||||||
|
///
|
||||||
|
/// This can be useful in scenarios where you need to obtain a database
|
||||||
|
/// instance synchronously, but need an async setup. A prime example here is
|
||||||
|
/// `MoorIsolate`:
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// @UseMoor(...)
|
||||||
|
/// class MyDatabase extends _$MyDatabase {
|
||||||
|
/// MyDatabase._connect(DatabaseConnection c): super.connect(c);
|
||||||
|
///
|
||||||
|
/// factory MyDatabase.fromIsolate(MoorIsolate isolate) {
|
||||||
|
/// return MyDatabase._connect(
|
||||||
|
/// // isolate.connect() returns a future, but we can still return a
|
||||||
|
/// // database synchronously thanks to DatabaseConnection.delayed!
|
||||||
|
/// DatabaseConnection.delayed(isolate.connect()),
|
||||||
|
/// );
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
factory DatabaseConnection.delayed(FutureOr<DatabaseConnection> connection) {
|
||||||
|
if (connection is DatabaseConnection) {
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
final future = connection as Future<DatabaseConnection>;
|
||||||
|
|
||||||
|
return DatabaseConnection(
|
||||||
|
SqlTypeSystem.defaultInstance,
|
||||||
|
LazyDatabase(() async => (await future).executor),
|
||||||
|
DelayedStreamQueryStore(future.then((conn) => conn.streamQueries)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a database connection that is identical to this one, except that
|
/// Returns a database connection that is identical to this one, except that
|
||||||
/// it uses the provided [executor].
|
/// it uses the provided [executor].
|
||||||
DatabaseConnection withExecutor(QueryExecutor executor) {
|
DatabaseConnection withExecutor(QueryExecutor executor) {
|
||||||
|
|
|
@ -2,6 +2,7 @@ import 'dart:async';
|
||||||
|
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
import 'package:moor/moor.dart';
|
import 'package:moor/moor.dart';
|
||||||
|
import 'package:moor/src/runtime/executor/delayed_stream_queries.dart';
|
||||||
import 'package:moor/src/runtime/executor/stream_queries.dart';
|
import 'package:moor/src/runtime/executor/stream_queries.dart';
|
||||||
|
|
||||||
part 'batch.dart';
|
part 'batch.dart';
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
import 'package:moor/src/runtime/api/runtime_api.dart';
|
||||||
|
|
||||||
|
import 'stream_queries.dart';
|
||||||
|
|
||||||
|
/// Version of [StreamQueryStore] that delegates work to an asynchronously-
|
||||||
|
/// available delegate.
|
||||||
|
/// This class is internal and should not be exposed to moor users. It's used
|
||||||
|
/// through a delayed database connection.
|
||||||
|
class DelayedStreamQueryStore implements StreamQueryStore {
|
||||||
|
Future<StreamQueryStore> _delegate;
|
||||||
|
StreamQueryStore _resolved;
|
||||||
|
|
||||||
|
/// Creates a [StreamQueryStore] that will work after [delegate] is
|
||||||
|
/// available.
|
||||||
|
DelayedStreamQueryStore(Future<StreamQueryStore> delegate) {
|
||||||
|
_delegate = delegate.then((value) => _resolved = value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() async => (await _delegate).close();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void handleTableUpdates(Set<TableUpdate> updates) {
|
||||||
|
_resolved?.handleTableUpdates(updates);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void markAsClosed(QueryStream stream, Function() whenRemoved) {
|
||||||
|
throw UnimplementedError('The stream will call this on the delegate');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void markAsOpened(QueryStream stream) {
|
||||||
|
throw UnimplementedError('The stream will call this on the delegate');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<T> registerStream<T>(QueryStreamFetcher<T> fetcher) {
|
||||||
|
return Stream.fromFuture(_delegate)
|
||||||
|
.asyncExpand((resolved) => resolved.registerStream(fetcher))
|
||||||
|
.asBroadcastStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<Null> updatesForSync(TableUpdateQuery query) {
|
||||||
|
return Stream.fromFuture(_delegate)
|
||||||
|
.asyncExpand((resolved) => resolved.updatesForSync(query))
|
||||||
|
.asBroadcastStream();
|
||||||
|
}
|
||||||
|
}
|
|
@ -66,4 +66,13 @@ class LazyDatabase extends QueryExecutor {
|
||||||
@override
|
@override
|
||||||
Future<int> runUpdate(String statement, List args) =>
|
Future<int> runUpdate(String statement, List args) =>
|
||||||
_delegate.runUpdate(statement, args);
|
_delegate.runUpdate(statement, args);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() {
|
||||||
|
if (_delegate != null) {
|
||||||
|
return _delegate.close();
|
||||||
|
} else {
|
||||||
|
return Future.value();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
name: moor
|
name: moor
|
||||||
description: Moor is a safe and reactive persistence library for Dart applications
|
description: Moor is a safe and reactive persistence library for Dart applications
|
||||||
version: 3.3.1
|
version: 3.4.0-dev
|
||||||
repository: https://github.com/simolus3/moor
|
repository: https://github.com/simolus3/moor
|
||||||
homepage: https://moor.simonbinder.eu/
|
homepage: https://moor.simonbinder.eu/
|
||||||
issue_tracker: https://github.com/simolus3/moor/issues
|
issue_tracker: https://github.com/simolus3/moor/issues
|
||||||
|
|
|
@ -83,26 +83,25 @@ void main() {
|
||||||
void _runTests(
|
void _runTests(
|
||||||
FutureOr<MoorIsolate> Function() spawner, bool terminateIsolate) {
|
FutureOr<MoorIsolate> Function() spawner, bool terminateIsolate) {
|
||||||
MoorIsolate isolate;
|
MoorIsolate isolate;
|
||||||
DatabaseConnection isolateConnection;
|
TodoDb database;
|
||||||
|
|
||||||
setUp(() async {
|
setUp(() async {
|
||||||
isolate = await spawner();
|
isolate = await spawner();
|
||||||
isolateConnection = await isolate.connect(isolateDebugLog: false);
|
|
||||||
|
database = TodoDb.connect(
|
||||||
|
DatabaseConnection.delayed(isolate.connect(isolateDebugLog: false)),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
tearDown(() {
|
tearDown(() async {
|
||||||
isolateConnection.executor.close();
|
await database.close();
|
||||||
|
|
||||||
if (terminateIsolate) {
|
if (terminateIsolate) {
|
||||||
return isolate.shutdownAll();
|
await isolate.shutdownAll();
|
||||||
} else {
|
|
||||||
return Future.value();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('can open database and send requests', () async {
|
test('can open database and send requests', () async {
|
||||||
final database = TodoDb.connect(isolateConnection);
|
|
||||||
|
|
||||||
final result = await database.select(database.todosTable).get();
|
final result = await database.select(database.todosTable).get();
|
||||||
expect(result, isEmpty);
|
expect(result, isEmpty);
|
||||||
});
|
});
|
||||||
|
@ -110,7 +109,6 @@ void _runTests(
|
||||||
test('can run beforeOpen', () async {
|
test('can run beforeOpen', () async {
|
||||||
var beforeOpenCalled = false;
|
var beforeOpenCalled = false;
|
||||||
|
|
||||||
final database = TodoDb.connect(isolateConnection);
|
|
||||||
database.migration = MigrationStrategy(beforeOpen: (details) async {
|
database.migration = MigrationStrategy(beforeOpen: (details) async {
|
||||||
await database.customStatement('PRAGMA foreign_keys = ON');
|
await database.customStatement('PRAGMA foreign_keys = ON');
|
||||||
beforeOpenCalled = true;
|
beforeOpenCalled = true;
|
||||||
|
@ -118,26 +116,20 @@ void _runTests(
|
||||||
|
|
||||||
// run a select statement to verify that the database is open
|
// run a select statement to verify that the database is open
|
||||||
await database.customSelect('SELECT 1').get();
|
await database.customSelect('SELECT 1').get();
|
||||||
await database.close();
|
|
||||||
expect(beforeOpenCalled, isTrue);
|
expect(beforeOpenCalled, isTrue);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('stream queries work as expected', () async {
|
test('stream queries work as expected', () async {
|
||||||
final database = TodoDb.connect(isolateConnection);
|
|
||||||
final initialCompanion = TodosTableCompanion.insert(content: 'my content');
|
final initialCompanion = TodosTableCompanion.insert(content: 'my content');
|
||||||
|
|
||||||
final stream = database.select(database.todosTable).watchSingle();
|
final stream = database.select(database.todosTable).watchSingle();
|
||||||
final expectation = expectLater(
|
|
||||||
stream,
|
|
||||||
emitsInOrder([null, TodoEntry(id: 1, content: 'my content')]),
|
|
||||||
);
|
|
||||||
|
|
||||||
|
await expectLater(stream, emits(null));
|
||||||
await database.into(database.todosTable).insert(initialCompanion);
|
await database.into(database.todosTable).insert(initialCompanion);
|
||||||
await expectation;
|
await expectLater(stream, emits(TodoEntry(id: 1, content: 'my content')));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('can start transactions', () async {
|
test('can start transactions', () async {
|
||||||
final database = TodoDb.connect(isolateConnection);
|
|
||||||
final initialCompanion = TodosTableCompanion.insert(content: 'my content');
|
final initialCompanion = TodosTableCompanion.insert(content: 'my content');
|
||||||
|
|
||||||
await database.transaction(() async {
|
await database.transaction(() async {
|
||||||
|
@ -149,15 +141,12 @@ void _runTests(
|
||||||
});
|
});
|
||||||
|
|
||||||
test('supports no-op transactions', () async {
|
test('supports no-op transactions', () async {
|
||||||
final database = TodoDb.connect(isolateConnection);
|
|
||||||
await database.transaction(() {
|
await database.transaction(() {
|
||||||
return Future.value(null);
|
return Future.value(null);
|
||||||
});
|
});
|
||||||
await database.close();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('supports transactions in migrations', () async {
|
test('supports transactions in migrations', () async {
|
||||||
final database = TodoDb.connect(isolateConnection);
|
|
||||||
database.migration = MigrationStrategy(beforeOpen: (details) async {
|
database.migration = MigrationStrategy(beforeOpen: (details) async {
|
||||||
await database.transaction(() async {
|
await database.transaction(() async {
|
||||||
return await database.customSelect('SELECT 1').get();
|
return await database.customSelect('SELECT 1').get();
|
||||||
|
@ -165,15 +154,11 @@ void _runTests(
|
||||||
});
|
});
|
||||||
|
|
||||||
await database.customSelect('SELECT 2').get();
|
await database.customSelect('SELECT 2').get();
|
||||||
|
|
||||||
await database.close();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('transactions have an isolated view on data', () async {
|
test('transactions have an isolated view on data', () async {
|
||||||
// regression test for https://github.com/simolus3/moor/issues/324
|
// regression test for https://github.com/simolus3/moor/issues/324
|
||||||
final db = TodoDb.connect(isolateConnection);
|
await database
|
||||||
|
|
||||||
await db
|
|
||||||
.customStatement('create table tbl (id integer primary key not null)');
|
.customStatement('create table tbl (id integer primary key not null)');
|
||||||
|
|
||||||
Future<void> expectRowCount(TodoDb db, int count) async {
|
Future<void> expectRowCount(TodoDb db, int count) async {
|
||||||
|
@ -182,74 +167,65 @@ void _runTests(
|
||||||
}
|
}
|
||||||
|
|
||||||
final rowInserted = Completer<void>();
|
final rowInserted = Completer<void>();
|
||||||
final runTransaction = db.transaction(() async {
|
final runTransaction = database.transaction(() async {
|
||||||
await db.customInsert('insert into tbl default values');
|
await database.customInsert('insert into tbl default values');
|
||||||
await expectRowCount(db, 1);
|
await expectRowCount(database, 1);
|
||||||
rowInserted.complete();
|
rowInserted.complete();
|
||||||
// Hold transaction open for expectRowCount() outside the transaction to
|
// Hold transaction open for expectRowCount() outside the transaction to
|
||||||
// finish
|
// finish
|
||||||
await Future.delayed(const Duration(seconds: 1));
|
await Future.delayed(const Duration(seconds: 1));
|
||||||
await db.customStatement('delete from tbl');
|
await database.customStatement('delete from tbl');
|
||||||
await expectRowCount(db, 0);
|
await expectRowCount(database, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
await rowInserted.future;
|
await rowInserted.future;
|
||||||
await expectRowCount(db, 0);
|
await expectRowCount(database, 0);
|
||||||
await runTransaction; // wait for the transaction to complete
|
await runTransaction; // wait for the transaction to complete
|
||||||
|
|
||||||
await db.close();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("can't run queries on a closed database", () async {
|
test("can't run queries on a closed database", () async {
|
||||||
final db = TodoDb.connect(isolateConnection);
|
await database.customSelect('SELECT 1;').getSingle();
|
||||||
await db.customSelect('SELECT 1;').getSingle();
|
|
||||||
|
|
||||||
await db.close();
|
await database.close();
|
||||||
|
|
||||||
await expectLater(
|
await expectLater(
|
||||||
() => db.customSelect('SELECT 1;').getSingle(), throwsStateError);
|
() => database.customSelect('SELECT 1;').getSingle(), throwsStateError);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('can run deletes, updates and batches', () async {
|
test('can run deletes, updates and batches', () async {
|
||||||
final db = TodoDb.connect(isolateConnection);
|
await database.into(database.users).insert(
|
||||||
|
|
||||||
await db.into(db.users).insert(
|
|
||||||
UsersCompanion.insert(name: 'simon.', profilePicture: Uint8List(0)));
|
UsersCompanion.insert(name: 'simon.', profilePicture: Uint8List(0)));
|
||||||
|
|
||||||
await db
|
await database
|
||||||
.update(db.users)
|
.update(database.users)
|
||||||
.write(const UsersCompanion(name: Value('changed name')));
|
.write(const UsersCompanion(name: Value('changed name')));
|
||||||
var result = await db.select(db.users).getSingle();
|
var result = await database.select(database.users).getSingle();
|
||||||
expect(result.name, 'changed name');
|
expect(result.name, 'changed name');
|
||||||
|
|
||||||
await db.delete(db.users).go();
|
await database.delete(database.users).go();
|
||||||
|
|
||||||
await db.batch((batch) {
|
await database.batch((batch) {
|
||||||
batch.insert(
|
batch.insert(
|
||||||
db.users,
|
database.users,
|
||||||
UsersCompanion.insert(name: 'not simon', profilePicture: Uint8List(0)),
|
UsersCompanion.insert(name: 'not simon', profilePicture: Uint8List(0)),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
result = await db.select(db.users).getSingle();
|
result = await database.select(database.users).getSingle();
|
||||||
expect(result.name, 'not simon');
|
expect(result.name, 'not simon');
|
||||||
|
|
||||||
await db.close();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('transactions can be rolled back', () async {
|
test('transactions can be rolled back', () async {
|
||||||
final db = TodoDb.connect(isolateConnection);
|
await expectLater(database.transaction(() async {
|
||||||
|
await database.into(database.categories).insert(
|
||||||
await expectLater(db.transaction(() async {
|
|
||||||
await db.into(db.categories).insert(
|
|
||||||
CategoriesCompanion.insert(description: 'my fancy description'));
|
CategoriesCompanion.insert(description: 'my fancy description'));
|
||||||
throw Exception('expected');
|
throw Exception('expected');
|
||||||
}), throwsException);
|
}), throwsException);
|
||||||
|
|
||||||
final result = await db.select(db.categories).get();
|
final result = await database.select(database.categories).get();
|
||||||
expect(result, isEmpty);
|
expect(result, isEmpty);
|
||||||
|
|
||||||
await db.close();
|
await database.close();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -79,4 +79,17 @@ void main() {
|
||||||
|
|
||||||
verify(inner.ensureOpen(user));
|
verify(inner.ensureOpen(user));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('can close inner executor', () async {
|
||||||
|
final inner = MockExecutor();
|
||||||
|
final lazy = LazyDatabase(() => inner);
|
||||||
|
final user = _LazyQueryUserForTest();
|
||||||
|
|
||||||
|
await lazy.close(); // Close before opening, expect no effect
|
||||||
|
|
||||||
|
await lazy.ensureOpen(user);
|
||||||
|
await lazy.close();
|
||||||
|
|
||||||
|
verify(inner.close());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue