serializableConnection method

  1. @experimental
Future<DriftIsolate> serializableConnection()

Creates a DriftIsolate that, when connected to, will run queries on the database already opened by this.

This can be used to share existing database across isolates, as instances of generated database classes can't be sent across isolates by default. A DriftIsolate can be sent over ports though, which enables a concise way to open a temporary isolate that is using an existing database:

Future<void> main() async {
  final database = MyDatabase(...);

  // This is illegal - MyDatabase is not serializable
  await Isolate.run(() async {
    await database.batch(...);
  });

  // This will work. Only the `connection` is sent to the new isolate. By
  // creating a new database instance based on the connection, the same
  // logical database can be shared across isolates.
  final connection = await database.serializableConnection();
  await Isolate.run(() async {
     final database = MyDatabase(await connection.connect());
     await database.batch(...);
  });
}

The example of running a short-lived database for a single task unit requiring a database is also available through computeWithDatabase.

Implementation

@experimental
Future<DriftIsolate> serializableConnection() async {
  final currentlyInRootConnection = resolvedEngine is GeneratedDatabase;
  // ignore: invalid_use_of_protected_member
  final localConnection = resolvedEngine.connection;
  final data = await localConnection.connectionData;

  // If we're connected to an isolate already, we can use that one directly
  // instead of starting a short-lived drift server.
  // However, this does not work if [serializableConnection] is called in a
  // transaction zone, since the top-level connection could be blocked waiting
  // for the transaction (as transactions can't be concurrent in sqlite3).
  if (data is DriftIsolate && currentlyInRootConnection) {
    return data;
  } else {
    // Set up a drift server acting as a proxy to the existing database
    // connection.
    final server = RunningDriftServer(
      Isolate.current,
      localConnection,
      onlyAcceptSingleConnection: true,
      closeConnectionAfterShutdown: false,
      killIsolateWhenDone: false,
    );

    // Since the existing database didn't use an isolate server, we need to
    // manually forward stream query updates.
    final forwardToServer = tableUpdates().listen((localUpdates) {
      server.server.dispatchTableUpdateNotification(
          NotifyTablesUpdated(localUpdates.toList()));
    });
    final forwardToLocal =
        server.server.tableUpdateNotifications.listen((remoteUpdates) {
      notifyUpdates(remoteUpdates.updates.toSet());
    });
    server.server.done.whenComplete(() {
      forwardToServer.cancel();
      forwardToLocal.cancel();
    });

    return DriftIsolate.fromConnectPort(
      server.portToOpenConnection,
      serialize: false,
    );
  }
}