mirror of https://github.com/AMT-Cheif/drift.git
Add helper method to setup isolates more easily
This commit is contained in:
parent
0231225733
commit
cd00c6899e
|
@ -27,6 +27,7 @@ LazyDatabase _openConnection() {
|
|||
// for your app.
|
||||
final dbFolder = await getApplicationDocumentsDirectory();
|
||||
final file = File(p.join(dbFolder.path, 'db.sqlite'));
|
||||
return NativeDatabase(file);
|
||||
|
||||
return NativeDatabase.createInBackground(file);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -127,3 +127,16 @@ DatabaseConnection createDriftIsolateAndConnect() {
|
|||
}));
|
||||
}
|
||||
// #enddocregion init_connect
|
||||
|
||||
// #docregion simple
|
||||
QueryExecutor createSimple() {
|
||||
return LazyDatabase(() async {
|
||||
final dir = await getApplicationDocumentsDirectory();
|
||||
final file = File(p.join(dir.path, 'db.sqlite'));
|
||||
|
||||
// Using createInBackground creates a drift isolate with the recommended
|
||||
// options behind the scenes.
|
||||
return NativeDatabase.createInBackground(file);
|
||||
});
|
||||
}
|
||||
// #enddocregion simple
|
|
@ -60,7 +60,7 @@ LazyDatabase _openConnection() {
|
|||
// for your app.
|
||||
final dbFolder = await getApplicationDocumentsDirectory();
|
||||
final file = File(p.join(dbFolder.path, 'db.sqlite'));
|
||||
return NativeDatabase(file);
|
||||
return NativeDatabase.createInBackground(file);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -36,6 +36,28 @@ a background isolate. Zero code changes are needed for queries!
|
|||
|
||||
{% assign snippets = 'package:drift_docs/snippets/isolates.dart.excerpt.json' | readString | json_decode %}
|
||||
|
||||
## Simple setup
|
||||
|
||||
Starting from Drift version 2.3.0, using drift isolates has been greatly
|
||||
simplified. Simply use `NativeDatabase.createInBackground` as a drop-in
|
||||
replacement for the `NativeDatabase` you've been using before:
|
||||
|
||||
{% include "blocks/snippet" snippets = snippets name = 'simple' %}
|
||||
|
||||
In the common case where you only need a isolate for performance reasons, this
|
||||
is as simple as it gets.
|
||||
The rest of this article explains a more complex setup giving you full control
|
||||
over the internal components making up a drift isolate. This is useful for
|
||||
advanced use cases, including:
|
||||
|
||||
- Having two databases on different isolates which need to stay in sync.
|
||||
- Sharing a drift database connection across different Dart or Flutter engines,
|
||||
like for a background service on Android.
|
||||
|
||||
In most other cases, simply using `NativeDatabase.createInbackground` works
|
||||
great! It implements the same approach shared in this article, except that all
|
||||
the complicated bits are hidden behind a simple method.
|
||||
|
||||
## Preparations
|
||||
|
||||
To use the isolate api, first enable the appropriate [build option]({{ "builder_options.md" | pageUrl }}) by
|
||||
|
|
|
@ -60,7 +60,7 @@ LazyDatabase _openConnection() {
|
|||
await file.writeAsBytes(buffer.asUint8List(blob.offsetInBytes, blob.lengthInBytes));
|
||||
}
|
||||
|
||||
return NativeDatabase(file);
|
||||
return NativeDatabase.createInBackground(file);;
|
||||
});
|
||||
}
|
||||
```
|
||||
|
|
|
@ -9,6 +9,9 @@
|
|||
- Add `singleClientMode` to `remote()` and `DriftIsolate` connections to make
|
||||
the common case with one client more efficient.
|
||||
- Fix a concurrency issues around transactions.
|
||||
- Add `NativeDatabase.createInBackground` as a drop-in replacement for
|
||||
`NativeDatabase`. It creates a drift isolate behind the scenes, avoiding all
|
||||
of the boilerplate usually involved with drift isolates.
|
||||
- __Experimental__: Add a [modular generation mode](https://drift.simonbinder.eu/docs/advanced-features/builder_options/#enabling-modular-code-generation)
|
||||
in which drift will generate multiple smaller files instead of one very large
|
||||
one with all tables and generated queries.
|
||||
|
|
|
@ -10,7 +10,10 @@
|
|||
library drift.ffi;
|
||||
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:drift/isolate.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:sqlite3/common.dart';
|
||||
import 'package:sqlite3/sqlite3.dart';
|
||||
|
@ -50,6 +53,53 @@ class NativeDatabase extends DelegatedDatabase {
|
|||
return NativeDatabase._(_NativeDelegate(file, setup), logStatements);
|
||||
}
|
||||
|
||||
/// Creates a database storing its result in [file].
|
||||
///
|
||||
/// This method will create the same database as the default constructor of
|
||||
/// the [NativeDatabase] class. It also behaves the same otherwise: The [file]
|
||||
/// is created if it doesn't exist, [logStatements] can be used to print
|
||||
/// statements and [setup] can be used to perform a one-time setup work when
|
||||
/// the database is created.
|
||||
///
|
||||
/// The big distinction of this method is that the database is implicitly
|
||||
/// created on a background isolate, freeing up your main thread accessing the
|
||||
/// database from I/O work needed to run statements.
|
||||
/// When the database returned by this method is closed, the background
|
||||
/// isolate will shut down as well.
|
||||
///
|
||||
/// __Important limitations__: If the [setup] parameter is given, it must be
|
||||
/// a static or top-level function. The reason is that it is executed on
|
||||
/// another isolate.
|
||||
static QueryExecutor createInBackground(File file,
|
||||
{bool logStatements = false, DatabaseSetup? setup}) {
|
||||
return createBackgroundConnection(file,
|
||||
logStatements: logStatements, setup: setup)
|
||||
.executor;
|
||||
}
|
||||
|
||||
/// Like [createInBackground], except that it returns the whole
|
||||
/// [DatabaseConnection] instead of just the executor.
|
||||
///
|
||||
/// This creates a database writing data to the given [file]. The database
|
||||
/// runs in a background isolate and is stopped when closed.
|
||||
static DatabaseConnection createBackgroundConnection(File file,
|
||||
{bool logStatements = false, DatabaseSetup? setup}) {
|
||||
return DatabaseConnection.delayed(Future.sync(() async {
|
||||
final receiveIsolate = ReceivePort();
|
||||
await Isolate.spawn(
|
||||
_NativeIsolateStartup.start,
|
||||
_NativeIsolateStartup(
|
||||
file.absolute.path, logStatements, setup, receiveIsolate.sendPort),
|
||||
debugName: 'Drift isolate worker for ${file.path}',
|
||||
);
|
||||
|
||||
final driftIsolate = await receiveIsolate.first as DriftIsolate;
|
||||
receiveIsolate.close();
|
||||
|
||||
return driftIsolate.connect(singleClientMode: true);
|
||||
}));
|
||||
}
|
||||
|
||||
/// Creates an in-memory database won't persist its changes on disk.
|
||||
///
|
||||
/// {@macro drift_vm_database_factory}
|
||||
|
@ -204,3 +254,25 @@ class _NativeDelegate extends Sqlite3Delegate<Database> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _NativeIsolateStartup {
|
||||
final String path;
|
||||
final bool enableLogs;
|
||||
final DatabaseSetup? setup;
|
||||
final SendPort sendServer;
|
||||
|
||||
_NativeIsolateStartup(
|
||||
this.path, this.enableLogs, this.setup, this.sendServer);
|
||||
|
||||
static void start(_NativeIsolateStartup startup) {
|
||||
final isolate = DriftIsolate.inCurrent(() {
|
||||
return DatabaseConnection(NativeDatabase(
|
||||
File(startup.path),
|
||||
logStatements: startup.enableLogs,
|
||||
setup: startup.setup,
|
||||
));
|
||||
});
|
||||
|
||||
startup.sendServer.send(isolate);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,3 +35,4 @@ dev_dependencies:
|
|||
rxdart: ^0.27.0
|
||||
shelf: ^1.3.0
|
||||
stack_trace: ^1.10.0
|
||||
test_descriptor: ^2.0.1
|
||||
|
|
|
@ -1,14 +1,41 @@
|
|||
@TestOn('vm')
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:drift/native.dart';
|
||||
import 'package:sqlite3/sqlite3.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:test_descriptor/test_descriptor.dart' as d;
|
||||
|
||||
import '../../generated/todos.dart';
|
||||
import '../../test_utils/database_vm.dart';
|
||||
|
||||
void main() {
|
||||
preferLocalSqlite3();
|
||||
|
||||
group('implicit isolates', () {
|
||||
test('work with direct executors', () async {
|
||||
final file = File(d.path('test.db'));
|
||||
|
||||
final db = TodoDb(NativeDatabase.createInBackground(file));
|
||||
await db.todosTable.select().get(); // Open the database
|
||||
await db.close();
|
||||
|
||||
await d.file('test.db', anything).validate();
|
||||
});
|
||||
|
||||
test('work with connections', () async {
|
||||
final file = File(d.path('test.db'));
|
||||
|
||||
final db =
|
||||
TodoDb.connect(NativeDatabase.createBackgroundConnection(file));
|
||||
await db.todosTable.select().get(); // Open the database
|
||||
await db.close();
|
||||
|
||||
await d.file('test.db', anything).validate();
|
||||
});
|
||||
});
|
||||
|
||||
group('NativeDatabase.opened', () {
|
||||
test('disposes the underlying database by default', () async {
|
||||
final underlying = sqlite3.openInMemory();
|
||||
|
|
|
@ -1,63 +1,21 @@
|
|||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:drift/isolate.dart';
|
||||
import 'package:drift/native.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
/// Obtains a database connection for running drift in a Dart VM.
|
||||
///
|
||||
/// The [NativeDatabase] from drift will synchronously use sqlite3's C APIs.
|
||||
/// To move synchronous database work off the main thread, we use a
|
||||
/// [DriftIsolate], which can run queries in a background isolate under the
|
||||
/// hood.
|
||||
DatabaseConnection connect() {
|
||||
return DatabaseConnection.delayed(Future.sync(() async {
|
||||
return DatabaseConnection.delayed(Future(() async {
|
||||
// Background isolates can't use platform channels, so let's use
|
||||
// `path_provider` in the main isolate and just send the result containing
|
||||
// the path over to the background isolate.
|
||||
|
||||
// We use `path_provider` to find a suitable path to store our data in.
|
||||
final appDir = await getApplicationDocumentsDirectory();
|
||||
final dbPath = p.join(appDir.path, 'todos.db');
|
||||
|
||||
final receiveDriftIsolate = ReceivePort();
|
||||
await Isolate.spawn(_entrypointForDriftIsolate,
|
||||
_IsolateStartRequest(receiveDriftIsolate.sendPort, dbPath));
|
||||
|
||||
final driftIsolate = await receiveDriftIsolate.first as DriftIsolate;
|
||||
|
||||
// Each connect() spawns a new isolate which is only used for one
|
||||
// connection, so we shutdown the isolate when the database is closed.
|
||||
return driftIsolate.connect(singleClientMode: true);
|
||||
return NativeDatabase.createBackgroundConnection(File(dbPath));
|
||||
}));
|
||||
}
|
||||
|
||||
/// The entrypoint of isolates can only take a single message, but we need two
|
||||
/// (a send port to reach the originating isolate and the database's path that
|
||||
/// should be opened on the background isolate). So, we bundle this information
|
||||
/// in a single class.
|
||||
class _IsolateStartRequest {
|
||||
final SendPort talkToMain;
|
||||
final String databasePath;
|
||||
|
||||
_IsolateStartRequest(this.talkToMain, this.databasePath);
|
||||
}
|
||||
|
||||
/// The entrypoint for a background isolate launching a drift server.
|
||||
///
|
||||
/// The main isolate can then connect to that isolate server to transparently
|
||||
/// run queries in the background.
|
||||
void _entrypointForDriftIsolate(_IsolateStartRequest request) {
|
||||
// The native database synchronously uses sqlite3's C API with `dart:ffi` for
|
||||
// a fast database implementation that doesn't require platform channels.
|
||||
final databaseImpl = NativeDatabase(File(request.databasePath));
|
||||
|
||||
// We can use DriftIsolate.inCurrent because this function is the entrypoint
|
||||
// of a background isolate itself.
|
||||
final driftServer =
|
||||
DriftIsolate.inCurrent(() => DatabaseConnection(databaseImpl));
|
||||
|
||||
// Inform the main isolate about the server we just created.
|
||||
request.talkToMain.send(driftServer);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue