diff --git a/drift/lib/isolate.dart b/drift/lib/isolate.dart index fcbf0881..42b52fe9 100644 --- a/drift/lib/isolate.dart +++ b/drift/lib/isolate.dart @@ -98,12 +98,11 @@ class DriftIsolate { /// /// Setting the [isolateDebugLog] is only helpful when debugging drift itself. /// It will print messages exchanged between the two isolates. - // todo: breaking: Make synchronous in drift 2 Future connect({ bool isolateDebugLog = false, bool singleClientMode = false, }) async { - return remote( + return connectToRemoteAndInitialize( _open(), debugLog: isolateDebugLog, serialize: serialize, diff --git a/drift/lib/remote.dart b/drift/lib/remote.dart index ce7a9f2d..7c2d5e12 100644 --- a/drift/lib/remote.dart +++ b/drift/lib/remote.dart @@ -97,7 +97,7 @@ abstract class DriftServer { /// the message may be any Dart object. /// /// After calling [serve], you can obtain a [DatabaseConnection] on the other - /// end of the [channel] by calling [remote]. + /// end of the [channel] by calling [connectToRemoteAndInitialize]. /// /// __Warning__: As long as this library is marked experimental, the protocol /// might change with every drift version. For this reason, make sure that @@ -134,6 +134,17 @@ abstract class DriftServer { /// /// The optional [debugLog] can be enabled to print incoming and outgoing /// messages. +/// +/// __NOTE__: This synchronous method has a flaw, as its [QueryExecutor.dialect] +/// is always going to be [SqlDialect.sqlite]. While this not a problem in most +/// scenarios where that is the actual database, it makes it harder to use with +/// other database clients. The [connectToRemoteAndInitialize] method does not +/// have this issue. +/// +/// Due to this problem, it is recommended to avoid [remote] altogether. If you +/// know the dialect beforehand, you can wrap [connectToRemoteAndInitialize] in +/// a [DatabaseConnection.delayed] to get a connection sychronously. +@Deprecated('Use the asynchronous `connectToRemoteAndInitialize` instead') DatabaseConnection remote( StreamChannel channel, { bool debugLog = false, @@ -144,6 +155,37 @@ DatabaseConnection remote( return client.connection; } +/// Connects to a remote server over a two-way communication channel. +/// +/// The other end of the [channel] must be attached to a drift server with +/// [DriftServer.serve] for this setup to work. +/// +/// If it is known that only a single client will connect to this database +/// server, [singleClientMode] can be enabled. +/// When enabled, [shutdown] is implicitly called when the database connection +/// is closed. This may make it easier to dispose the remote isolate or server. +/// Also, update notifications for table updates don't have to be sent which +/// reduces load on the connection. +/// +/// If [serialize] is true, drift will only send [bool], [int], [double], +/// [Uint8List], [String] or [List]'s thereof over the channel. Otherwise, +/// the message may be any Dart object. +/// The value of [serialize] for [connectToRemoteAndInitialize] must be the same +/// value passed to [DriftServer.serve]. +/// +/// The optional [debugLog] can be enabled to print incoming and outgoing +/// messages. +Future connectToRemoteAndInitialize( + StreamChannel channel, { + bool debugLog = false, + bool serialize = true, + bool singleClientMode = false, +}) async { + final client = DriftClient(channel, debugLog, serialize, singleClientMode); + await client.serverInfo; + return client.connection; +} + /// Sends a shutdown request over a channel. /// /// On the remote side, the corresponding channel must have been passed to diff --git a/drift/lib/src/remote/client_impl.dart b/drift/lib/src/remote/client_impl.dart index b698aa3a..7ced7fa3 100644 --- a/drift/lib/src/remote/client_impl.dart +++ b/drift/lib/src/remote/client_impl.dart @@ -14,6 +14,12 @@ import 'protocol.dart'; class DriftClient { final DriftCommunication _channel; + SqlDialect _serverDialect = SqlDialect.sqlite; + final Completer _serverInfo = Completer(); + + /// Waits for the first [ServerInfo] message to this client. + Future get serverInfo => _serverInfo.future; + /// Whether we know that only a single client will use the database server. /// /// In this case, we shutdown the server after the client disconnects and @@ -51,6 +57,9 @@ class DriftClient { return _connectedDb.beforeOpen(executor, payload.details); } else if (payload is NotifyTablesUpdated) { _streamStore.handleTableUpdates(payload.updates.toSet(), true); + } else if (payload is ServerInfo) { + _serverDialect = payload.dialect; + _serverInfo.complete(payload); } } } @@ -62,6 +71,9 @@ abstract class _BaseExecutor extends QueryExecutor { // ignore: unused_element, https://github.com/dart-lang/sdk/issues/49007 _BaseExecutor(this.client, [this._executorId]); + @override + SqlDialect get dialect => client._serverDialect; + @override Future runBatched(BatchedStatements statements) { return client._channel @@ -129,9 +141,6 @@ class _RemoteQueryExecutor extends _BaseExecutor { Completer? _setSchemaVersion; Future? _serverIsOpen; - @override - SqlDialect get dialect => SqlDialect.sqlite; - @override TransactionExecutor beginTransaction() { return _RemoteTransactionExecutor(client, _executorId); diff --git a/drift/lib/src/remote/protocol.dart b/drift/lib/src/remote/protocol.dart index b950207b..d30fa43b 100644 --- a/drift/lib/src/remote/protocol.dart +++ b/drift/lib/src/remote/protocol.dart @@ -22,6 +22,8 @@ class DriftProtocol { static const _tag_DirectValue = 10; static const _tag_SelectResult = 11; static const _tag_RequestCancellation = 12; + static const _tag_ServerInfo = 13; + static const _tag_BigInt = 'bigint'; Object? serialize(Message message) { @@ -103,6 +105,11 @@ class DriftProtocol { ]; } else if (payload is EnsureOpen) { return [_tag_EnsureOpen, payload.schemaVersion, payload.executorId]; + } else if (payload is ServerInfo) { + return [ + _tag_ServerInfo, + payload.dialect.name, + ]; } else if (payload is RunBeforeOpen) { return [ _tag_RunBeforeOpen, @@ -189,6 +196,8 @@ class DriftProtocol { return RunTransactionAction(control, readNullableInt(2)); case _tag_EnsureOpen: return EnsureOpen(readInt(1), readNullableInt(2)); + case _tag_ServerInfo: + return ServerInfo(SqlDialect.values.byName(fullMessage![1] as String)); case _tag_RunBeforeOpen: return RunBeforeOpen( OpeningDetails(readNullableInt(1), readInt(2)), @@ -408,6 +417,17 @@ class EnsureOpen { } } +class ServerInfo { + final SqlDialect dialect; + + ServerInfo(this.dialect); + + @override + String toString() { + return 'ServerInfo($dialect)'; + } +} + /// Sent from the server to the client when it should run the before open /// callback. class RunBeforeOpen { diff --git a/drift/lib/src/remote/server_impl.dart b/drift/lib/src/remote/server_impl.dart index eaf96094..39aca60a 100644 --- a/drift/lib/src/remote/server_impl.dart +++ b/drift/lib/src/remote/server_impl.dart @@ -54,6 +54,7 @@ class ServerImplementation implements DriftServer { final comm = DriftCommunication(channel, serialize: serialize); comm.setRequestHandler((request) => _handleRequest(comm, request)); + comm.request(ServerInfo(connection.executor.dialect)); _activeChannels.add(comm); comm.closed.then((_) => _activeChannels.remove(comm)); diff --git a/drift/lib/src/runtime/api/connection.dart b/drift/lib/src/runtime/api/connection.dart index 371cd4eb..908f02e1 100644 --- a/drift/lib/src/runtime/api/connection.dart +++ b/drift/lib/src/runtime/api/connection.dart @@ -44,15 +44,17 @@ class DatabaseConnection { /// } /// } /// ``` - factory DatabaseConnection.delayed(FutureOr connection) { + factory DatabaseConnection.delayed(FutureOr connection, + {SqlDialect dialect = SqlDialect.sqlite}) { if (connection is DatabaseConnection) { return connection; } return DatabaseConnection( - LazyDatabase(() async => (await connection).executor), + LazyDatabase(() async => (await connection).executor, dialect: dialect), streamQueries: DelayedStreamQueryStore( - connection.then((conn) => conn.streamQueries)), + connection.then((conn) => conn.streamQueries), + ), ); } diff --git a/drift/test/remote_test.dart b/drift/test/remote_test.dart index a82a4632..bb6b8f51 100644 --- a/drift/test/remote_test.dart +++ b/drift/test/remote_test.dart @@ -29,8 +29,9 @@ void main() { DriftServer(testInMemoryDatabase(), allowRemoteShutdown: true); server.serve(controller.foreign); - final client = - remote(controller.local.expectedToClose, singleClientMode: true); + final client = await connectToRemoteAndInitialize( + controller.local.expectedToClose, + singleClientMode: true); final db = TodoDb.connect(client); await db.todosTable.select().get(); @@ -47,7 +48,7 @@ void main() { final controller = StreamChannelController(); server.serve(controller.foreign, serialize: false); - final client = remote( + final client = await connectToRemoteAndInitialize( controller.local.transformSink(StreamSinkTransformer.fromHandlers( handleData: (data, out) { expect(data, isNot(isA())); @@ -117,7 +118,7 @@ void main() { server.serve(channelController.foreign.changeStream(_checkStreamOfSimple), serialize: true); - final connection = remote( + final connection = await connectToRemoteAndInitialize( channelController.local .changeStream(_checkStreamOfSimple) .expectedToClose, @@ -162,7 +163,8 @@ void main() { server.serve(controller.foreign); addTearDown(server.shutdown); - final db = TodoDb.connect(remote(controller.local)); + final db = + TodoDb.connect(await connectToRemoteAndInitialize(controller.local)); addTearDown(db.close); await db.transaction(() async { @@ -190,6 +192,19 @@ void main() { verify(innerTransactions[1].send()); verify(outerTransaction.send()); }); + + test('reports correct dialect of remote', () async { + final executor = MockExecutor(); + when(executor.dialect).thenReturn(SqlDialect.postgres); + + final controller = StreamChannelController(); + final server = DriftServer(DatabaseConnection(executor)) + ..serve(controller.foreign); + + final client = await connectToRemoteAndInitialize(controller.local); + await server.shutdown(); + expect(client.executor.dialect, SqlDialect.postgres); + }); } Stream _checkStreamOfSimple(Stream source) { diff --git a/examples/app/lib/database/connection/web.dart b/examples/app/lib/database/connection/web.dart index aadea95e..08bada10 100644 --- a/examples/app/lib/database/connection/web.dart +++ b/examples/app/lib/database/connection/web.dart @@ -15,7 +15,8 @@ const _useWorker = true; DatabaseConnection connect({bool isInWebWorker = false}) { if (_useWorker && !isInWebWorker) { final worker = SharedWorker('shared_worker.dart.js'); - return remote(worker.port!.channel()); + return DatabaseConnection.delayed( + connectToRemoteAndInitialize(worker.port!.channel())); } else { return DatabaseConnection.delayed(Future.sync(() async { // We're using the experimental wasm support in Drift because this gives diff --git a/examples/flutter_web_worker_example/lib/src/platform/platform_web.dart b/examples/flutter_web_worker_example/lib/src/platform/platform_web.dart index 1bc2ec33..4c907a99 100644 --- a/examples/flutter_web_worker_example/lib/src/platform/platform_web.dart +++ b/examples/flutter_web_worker_example/lib/src/platform/platform_web.dart @@ -16,6 +16,7 @@ class PlatformInterface { static DatabaseConnection _connectToWorker(String databaseName) { final worker = SharedWorker( kReleaseMode ? 'worker.dart.min.js' : 'worker.dart.js', databaseName); - return remote(worker.port!.channel()); + return DatabaseConnection.delayed( + connectToRemoteAndInitialize(worker.port!.channel())); } } diff --git a/examples/web_worker_example/web/main.dart b/examples/web_worker_example/web/main.dart index 4867c811..ac28749f 100644 --- a/examples/web_worker_example/web/main.dart +++ b/examples/web_worker_example/web/main.dart @@ -4,9 +4,9 @@ import 'package:drift/remote.dart'; import 'package:drift/web.dart'; import 'package:web_worker_example/database.dart'; -void main() { +void main() async { final worker = SharedWorker('worker.dart.js'); - final connection = remote(worker.port!.channel()); + final connection = await connectToRemoteAndInitialize(worker.port!.channel()); final db = MyDatabase(connection); final output = document.getElementById('output')!;