Skip to content

Commit

Permalink
Async APIs: add Query.findAsync #50
Browse files Browse the repository at this point in the history
  • Loading branch information
greenrobot-team committed Mar 7, 2023
1 parent f2dc058 commit 44c7f3e
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 21 deletions.
34 changes: 23 additions & 11 deletions benchmark/bin/query.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,17 @@ const count = 10000;

void main() async {
await QueryFind().report();
await QueryFindAsync().report();
await QueryFindIds().report();
await QueryFindIdsAsync().report();
await QueryStream().report();
}

class QueryBenchmark extends DbBenchmark {
static const expectedCount = count / 5;
late final Query<TestEntity> query;

QueryBenchmark(String name)
: super(name, iterations: 1, coefficient: 1 / expectedCount);
QueryBenchmark(String name) : super(name, coefficient: 1 / expectedCount);

@override
void setup() {
Expand All @@ -40,26 +41,37 @@ class QueryBenchmark extends DbBenchmark {
}

class QueryFind extends QueryBenchmark {
QueryFind() : super('${QueryFind}');
QueryFind() : super('$QueryFind');

@override
Future<void> run() async {
query.find();
return Future.value();
}
void runIteration(int iteration) => query.find();
}

class QueryFindAsync extends QueryBenchmark {
QueryFindAsync() : super('$QueryFindAsync');

@override
Future<void> 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<void> run() async => query.findIds();
Future<void> 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<void> run() async => await query.stream().toList();
Future<void> runIteration(int iteration) => query.stream().toList();
}
1 change: 1 addition & 0 deletions objectbox/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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!
Expand Down
76 changes: 76 additions & 0 deletions objectbox/lib/src/native/query/query.dart
Original file line number Diff line number Diff line change
Expand Up @@ -671,7 +671,17 @@ class Query<T> {
Query._(this._store, Pointer<OBX_query_builder> cBuilder, this._entity)
: _cQuery = checkObxPtr(C.query(cBuilder), 'create query') {
initializeDartAPI();
_attachFinalizer();
}

Query._fromConfiguration(this._store, _QueryConfiguration<T> 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);
Expand Down Expand Up @@ -781,6 +791,16 @@ class Query<T> {
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<T>(
Store store, _QueryConfiguration<T> configuration) =>
_asyncCallbackImpl<T, T?>(
store, configuration, (query) => query.findFirst());

/// Like [findFirst], but runs the query operation asynchronously in a worker
/// isolate.
Future<T?> findFirstAsync() => _findAsyncImpl(_findFirstAsyncCallback<T>);

/// Finds the only object matching the query. Returns null if there are no
/// results or throws [NonUniqueResultException] if there are multiple objects
/// matching.
Expand Down Expand Up @@ -813,6 +833,16 @@ class Query<T> {
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<T>(
Store store, _QueryConfiguration<T> configuration) =>
_asyncCallbackImpl<T, T?>(
store, configuration, (query) => query.findUnique());

/// Like [findUnique], but runs the query operation asynchronously in a worker
/// isolate.
Future<T?> findUniqueAsync() => _findAsyncImpl(_findUniqueAsyncCallback<T>);

/// Finds Objects matching the query and returns their IDs.
List<int> findIds() {
final idArrayPtr = checkObxPtr(C.query_find_ids(_ptr), 'find ids');
Expand All @@ -826,6 +856,16 @@ class Query<T> {
}
}

// Static callback to avoid over-capturing due to [dart-lang/sdk#36983](https://github.com/dart-lang/sdk/issues/36983).
static List<int> _findIdsAsyncCallback<T>(
Store store, _QueryConfiguration<T> configuration) =>
_asyncCallbackImpl<T, List<int>>(
store, configuration, (query) => query.findIds());

/// Like [findIds], but runs the query operation asynchronously in a worker
/// isolate.
Future<List<int>> findIdsAsync() => _findAsyncImpl(_findIdsAsyncCallback<T>);

/// Finds Objects matching the query.
List<T> find() {
final result = <T>[];
Expand All @@ -837,6 +877,32 @@ class Query<T> {
return result;
}

// Static callback to avoid over-capturing due to [dart-lang/sdk#36983](https://github.com/dart-lang/sdk/issues/36983).
static List<T> _findAsyncCallback<T>(
Store store, _QueryConfiguration<T> configuration) =>
_asyncCallbackImpl<T, List<T>>(
store, configuration, (query) => query.find());

/// Like [find], but runs the query operation asynchronously in a worker
/// isolate.
Future<List<T>> findAsync() => _findAsyncImpl(_findAsyncCallback<T>);

// Static callback to avoid over-capturing due to [dart-lang/sdk#36983](https://github.com/dart-lang/sdk/issues/36983).
static R _asyncCallbackImpl<T, R>(Store store,
_QueryConfiguration<T> configuration, R Function(Query<T>) 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<R> _findAsyncImpl<R>(
R Function(Store, _QueryConfiguration<T>) 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
Expand Down Expand Up @@ -1157,3 +1223,13 @@ class _StreamIsolateMessage {

const _StreamIsolateMessage(this.dataPtrAddresses, this.sizes);
}

class _QueryConfiguration<T> {
final int queryAddress;
final EntityDefinition<T> entity;

/// Creates a configuration to send to an isolate by cloning the native query.
_QueryConfiguration(Query<T> query)
: queryAddress = query._clone().address,
entity = query._entity;
}
29 changes: 19 additions & 10 deletions objectbox/test/query_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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>[
TestEntity(tInt: 0),
TestEntity(tInt: 10),
Expand All @@ -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', () {
Expand Down Expand Up @@ -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'));
Expand All @@ -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'));
Expand All @@ -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', () {
Expand Down

0 comments on commit 44c7f3e

Please sign in to comment.