diff --git a/benchmark/bin/query.dart b/benchmark/bin/query.dart index d66f2adb..d775add2 100644 --- a/benchmark/bin/query.dart +++ b/benchmark/bin/query.dart @@ -6,7 +6,9 @@ const count = 10000; void main() async { await QueryFind().report(); + await QueryFindAsync().report(); await QueryFindIds().report(); + await QueryFindIdsAsync().report(); await QueryStream().report(); } @@ -14,8 +16,7 @@ class QueryBenchmark extends DbBenchmark { static const expectedCount = count / 5; late final Query query; - QueryBenchmark(String name) - : super(name, iterations: 1, coefficient: 1 / expectedCount); + QueryBenchmark(String name) : super(name, coefficient: 1 / expectedCount); @override void setup() { @@ -40,26 +41,37 @@ class QueryBenchmark extends DbBenchmark { } class QueryFind extends QueryBenchmark { - QueryFind() : super('${QueryFind}'); + QueryFind() : super('$QueryFind'); @override - Future run() async { - query.find(); - return Future.value(); - } + void runIteration(int iteration) => query.find(); +} + +class QueryFindAsync extends QueryBenchmark { + QueryFindAsync() : super('$QueryFindAsync'); + + @override + Future runIteration(int iteration) => query.findAsync(); } class QueryFindIds extends QueryBenchmark { - QueryFindIds() : super('${QueryFindIds}'); + QueryFindIds() : super('$QueryFindIds'); + + @override + void runIteration(int iteration) => query.findIds(); +} + +class QueryFindIdsAsync extends QueryBenchmark { + QueryFindIdsAsync() : super('$QueryFindIdsAsync'); @override - Future run() async => query.findIds(); + Future runIteration(int iteration) => query.findIdsAsync(); } /// Stream where visitor is running in Dart isolate. class QueryStream extends QueryBenchmark { - QueryStream() : super('${QueryStream}'); + QueryStream() : super('$QueryStream'); @override - Future run() async => await query.stream().toList(); + Future runIteration(int iteration) => query.stream().toList(); } diff --git a/objectbox/CHANGELOG.md b/objectbox/CHANGELOG.md index e57f3242..e4514e67 100644 --- a/objectbox/CHANGELOG.md +++ b/objectbox/CHANGELOG.md @@ -1,6 +1,7 @@ ## latest * Add `getAsync`, `getManyAsync` and `getAllAsync` to `Box`. +* Add `findAsync`, `findFirstAsync`, `findUniqueAsync` and `findIdsAsync` shortcuts to `Query`. * Require at least Dart SDK 2.17 (shipped with Flutter 3.0.0). * Breaking changes to generated code: run `flutter pub run build_runner build` (or `dart run build_runner build` for Dart Native apps) after updating! diff --git a/objectbox/lib/src/native/query/query.dart b/objectbox/lib/src/native/query/query.dart index 18f963e4..52afa926 100644 --- a/objectbox/lib/src/native/query/query.dart +++ b/objectbox/lib/src/native/query/query.dart @@ -671,7 +671,17 @@ class Query { Query._(this._store, Pointer cBuilder, this._entity) : _cQuery = checkObxPtr(C.query(cBuilder), 'create query') { initializeDartAPI(); + _attachFinalizer(); + } + + Query._fromConfiguration(this._store, _QueryConfiguration configuration) + : _cQuery = Pointer.fromAddress(configuration.queryAddress), + _entity = configuration.entity { + initializeDartAPI(); + _attachFinalizer(); + } + void _attachFinalizer() { // Keep the finalizer so we can detach it when close() is called manually. _cFinalizer = C.dartc_attach_finalizer(this, native_query_close, _cQuery.cast(), 256); @@ -781,6 +791,16 @@ class Query { return result; } + // Static callback to avoid over-capturing due to [dart-lang/sdk#36983](https://github.com/dart-lang/sdk/issues/36983). + static T? _findFirstAsyncCallback( + Store store, _QueryConfiguration configuration) => + _asyncCallbackImpl( + store, configuration, (query) => query.findFirst()); + + /// Like [findFirst], but runs the query operation asynchronously in a worker + /// isolate. + Future findFirstAsync() => _findAsyncImpl(_findFirstAsyncCallback); + /// Finds the only object matching the query. Returns null if there are no /// results or throws [NonUniqueResultException] if there are multiple objects /// matching. @@ -813,6 +833,16 @@ class Query { return result; } + // Static callback to avoid over-capturing due to [dart-lang/sdk#36983](https://github.com/dart-lang/sdk/issues/36983). + static T? _findUniqueAsyncCallback( + Store store, _QueryConfiguration configuration) => + _asyncCallbackImpl( + store, configuration, (query) => query.findUnique()); + + /// Like [findUnique], but runs the query operation asynchronously in a worker + /// isolate. + Future findUniqueAsync() => _findAsyncImpl(_findUniqueAsyncCallback); + /// Finds Objects matching the query and returns their IDs. List findIds() { final idArrayPtr = checkObxPtr(C.query_find_ids(_ptr), 'find ids'); @@ -826,6 +856,16 @@ class Query { } } + // Static callback to avoid over-capturing due to [dart-lang/sdk#36983](https://github.com/dart-lang/sdk/issues/36983). + static List _findIdsAsyncCallback( + Store store, _QueryConfiguration configuration) => + _asyncCallbackImpl>( + store, configuration, (query) => query.findIds()); + + /// Like [findIds], but runs the query operation asynchronously in a worker + /// isolate. + Future> findIdsAsync() => _findAsyncImpl(_findIdsAsyncCallback); + /// Finds Objects matching the query. List find() { final result = []; @@ -837,6 +877,32 @@ class Query { return result; } + // Static callback to avoid over-capturing due to [dart-lang/sdk#36983](https://github.com/dart-lang/sdk/issues/36983). + static List _findAsyncCallback( + Store store, _QueryConfiguration configuration) => + _asyncCallbackImpl>( + store, configuration, (query) => query.find()); + + /// Like [find], but runs the query operation asynchronously in a worker + /// isolate. + Future> findAsync() => _findAsyncImpl(_findAsyncCallback); + + // Static callback to avoid over-capturing due to [dart-lang/sdk#36983](https://github.com/dart-lang/sdk/issues/36983). + static R _asyncCallbackImpl(Store store, + _QueryConfiguration configuration, R Function(Query) action) { + final query = Query._fromConfiguration(store, configuration); + try { + return action(query); + } finally { + query.close(); + } + } + + /// Runs the given query callback on a worker isolate and returns the result. + Future _findAsyncImpl( + R Function(Store, _QueryConfiguration) callback) => + _store.runAsync(callback, _QueryConfiguration(this)); + /// Finds Objects matching the query, streaming them while the query executes. /// /// Results are streamed from a worker isolate in batches (the stream still @@ -1157,3 +1223,13 @@ class _StreamIsolateMessage { const _StreamIsolateMessage(this.dataPtrAddresses, this.sizes); } + +class _QueryConfiguration { + final int queryAddress; + final EntityDefinition entity; + + /// Creates a configuration to send to an isolate by cloning the native query. + _QueryConfiguration(Query query) + : queryAddress = query._clone().address, + entity = query._entity; +} diff --git a/objectbox/test/query_test.dart b/objectbox/test/query_test.dart index b0c76dc5..6e8212c7 100644 --- a/objectbox/test/query_test.dart +++ b/objectbox/test/query_test.dart @@ -27,7 +27,7 @@ void main() { box.query().build().find(); }); - test('Query with no conditions, and order as desc ints', () { + test('Query with no conditions, and order as desc ints', () async { box.putMany([ TestEntity(tInt: 0), TestEntity(tInt: 10), @@ -38,10 +38,15 @@ void main() { var query = box.query().order(TestEntity_.tInt, flags: Order.descending).build(); - final listDesc = query.find(); - query.close(); + try { + final listDesc = query.find(); + expect(listDesc.map((t) => t.tInt).toList(), [100, 10, 10, 0, 0]); - expect(listDesc.map((t) => t.tInt).toList(), [100, 10, 10, 0, 0]); + final listDescAsync = await query.findAsync(); + expect(listDescAsync.map((t) => t.tInt).toList(), [100, 10, 10, 0, 0]); + } finally { + query.close(); + } }); test('ignore transient field', () { @@ -333,7 +338,7 @@ void main() { q.close(); }); - test('.findFirst returns TestEntity', () { + test('.findFirst returns TestEntity', () async { box.put(TestEntity(tLong: 0)); box.put(TestEntity(tString: 'test1t')); box.put(TestEntity(tString: 'test')); @@ -346,14 +351,16 @@ void main() { var q = box.query(c).build(); expect(q.findFirst()!.tString, 'test1t'); + expect((await q.findFirstAsync())!.tString, 'test1t'); q.close(); q = box.query(number.notNull()).build(); expect(q.findFirst()!.tLong, 0); + expect((await q.findFirstAsync())!.tLong, 0); q.close(); }); - test('.findUnique', () { + test('.findUnique', () async { box.put(TestEntity(tLong: 0)); box.put(TestEntity(tString: 't1')); box.put(TestEntity(tString: 't2')); @@ -363,16 +370,18 @@ void main() { .order(TestEntity_.iInt) .build(); - expect( - () => query.findUnique(), - throwsA(predicate((NonUniqueResultException e) => - e.message == 'Query findUnique() matched more than one object'))); + final throwsNonUniqueEx = throwsA(predicate((NonUniqueResultException e) => + e.message == 'Query findUnique() matched more than one object')); + expect(() => query.findUnique(), throwsNonUniqueEx); + expect(() async => await query.findUniqueAsync(), throwsNonUniqueEx); query.param(TestEntity_.tString).value = 't2'; expect(query.findUnique()!.tString, 't2'); + expect((await query.findUniqueAsync())!.tString, 't2'); query.param(TestEntity_.tString).value = 'xyz'; expect(query.findUnique(), isNull); + expect(await query.findUniqueAsync(), isNull); }); test('.find works on large arrays', () {