Skip to content

Commit cb2b0fc

Browse files
authored
forward exceptions from user-provided property converters (#298)
1 parent e587529 commit cb2b0fc

File tree

6 files changed

+132
-13
lines changed

6 files changed

+132
-13
lines changed

objectbox/lib/src/native/bindings/data_visitor.dart

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,25 @@ Pointer<NativeFunction<obx_data_visitor>> dataVisitor(
3434

3535
@pragma('vm:prefer-inline')
3636
Pointer<NativeFunction<obx_data_visitor>> objectCollector<T>(
37-
List<T> list, Store store, EntityDefinition<T> entity) =>
37+
List<T> list,
38+
Store store,
39+
EntityDefinition<T> entity,
40+
ObjectCollectorError outError) =>
3841
dataVisitor((Pointer<Uint8> data, int size) {
39-
list.add(entity.objectFromFB(
40-
store, InternalStoreAccess.reader(store).access(data, size)));
41-
return true;
42+
try {
43+
list.add(entity.objectFromFB(
44+
store, InternalStoreAccess.reader(store).access(data, size)));
45+
return true;
46+
} catch (e) {
47+
outError.error = e;
48+
return false;
49+
}
4250
});
51+
52+
class ObjectCollectorError {
53+
Object? error;
54+
55+
void throwIfError() {
56+
if (error != null) throw error!;
57+
}
58+
}

objectbox/lib/src/native/query/query.dart

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -738,12 +738,18 @@ class Query<T> {
738738
/// results. Note: [offset] and [limit] are respected, if set.
739739
T? findFirst() {
740740
T? result;
741+
Object? error;
741742
final visitor = dataVisitor((Pointer<Uint8> data, int size) {
742-
result = _entity.objectFromFB(
743-
_store, InternalStoreAccess.reader(_store).access(data, size));
743+
try {
744+
result = _entity.objectFromFB(
745+
_store, InternalStoreAccess.reader(_store).access(data, size));
746+
} catch (e) {
747+
error = e;
748+
}
744749
return false; // we only want to visit the first element
745750
});
746751
checkObx(C.query_visit(_ptr, visitor, nullptr));
752+
if (error != null) throw error!;
747753
reachabilityFence(this);
748754
return result;
749755
}
@@ -756,17 +762,22 @@ class Query<T> {
756762
/// higher than one, otherwise the check for non-unique result won't work.
757763
T? findUnique() {
758764
T? result;
759-
Exception? error;
765+
Object? error;
760766
final visitor = dataVisitor((Pointer<Uint8> data, int size) {
761767
if (result == null) {
762-
result = _entity.objectFromFB(
763-
_store, InternalStoreAccess.reader(_store).access(data, size));
768+
try {
769+
result = _entity.objectFromFB(
770+
_store, InternalStoreAccess.reader(_store).access(data, size));
771+
return true;
772+
} catch (e) {
773+
error = e;
774+
return false;
775+
}
764776
} else {
765777
error = UniqueViolationException(
766778
'Query findUnique() matched more than one object');
767779
return false;
768780
}
769-
return true;
770781
});
771782
checkObx(C.query_visit(_ptr, visitor, nullptr));
772783
reachabilityFence(this);
@@ -790,8 +801,10 @@ class Query<T> {
790801
/// Finds Objects matching the query.
791802
List<T> find() {
792803
final result = <T>[];
793-
final collector = objectCollector(result, _store, _entity);
804+
final errorWrapper = ObjectCollectorError();
805+
final collector = objectCollector(result, _store, _entity, errorWrapper);
794806
checkObx(C.query_visit(_ptr, collector, nullptr));
807+
errorWrapper.throwIfError();
795808
reachabilityFence(this);
796809
return result;
797810
}

objectbox/test/box_test.dart

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import 'dart:io';
22
import 'dart:typed_data';
33

4-
import 'package:test/test.dart';
54
import 'package:objectbox/objectbox.dart';
5+
import 'package:test/test.dart';
6+
67
import 'entity.dart';
78
import 'entity2.dart';
89
import 'test_env.dart';
@@ -631,4 +632,27 @@ void main() {
631632
box.get(1);
632633
box2.get(1);
633634
}));
635+
636+
test('throwing in converters', () {
637+
late Box<ThrowingInConverters> box = store.box();
638+
639+
box.put(ThrowingInConverters());
640+
box.put(ThrowingInConverters(throwOnGet: true));
641+
expect(() => box.put(ThrowingInConverters(throwOnPut: true)),
642+
ThrowingInConverters.throwsIn('Getter'));
643+
644+
expect(
645+
() => box.putMany([
646+
ThrowingInConverters(),
647+
ThrowingInConverters(),
648+
ThrowingInConverters(throwOnPut: true)
649+
]),
650+
ThrowingInConverters.throwsIn('Getter'));
651+
652+
expect(box.count(), 2);
653+
654+
box.get(1);
655+
expect(() => box.get(2), ThrowingInConverters.throwsIn('Setter'));
656+
expect(() => box.getAll(), ThrowingInConverters.throwsIn('Setter'));
657+
});
634658
}

objectbox/test/entity2.dart

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'package:objectbox/objectbox.dart';
2+
import 'package:test/test.dart';
23

34
// Testing a model for entities in multiple files is generated properly
45
@Entity()
@@ -35,3 +36,24 @@ class TreeNode {
3536

3637
TreeNode(this.path);
3738
}
39+
40+
/// Test how DB operations behave if property converters throw.
41+
@Entity()
42+
class ThrowingInConverters {
43+
int id = 0;
44+
45+
final bool throwOnGet;
46+
final bool throwOnPut;
47+
48+
ThrowingInConverters({this.throwOnGet = false, this.throwOnPut = false});
49+
50+
int get value =>
51+
throwOnPut ? throw Exception('Getter invoked, e.g. box.put()') : 1;
52+
53+
set value(int val) {
54+
if (throwOnGet) throw Exception('Setter invoked, e.g. box.get())');
55+
}
56+
57+
static Matcher throwsIn(String op) =>
58+
throwsA(predicate((Exception e) => e.toString().contains('$op invoked')));
59+
}

objectbox/test/objectbox-model.json

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -491,9 +491,38 @@
491491
}
492492
],
493493
"relations": []
494+
},
495+
{
496+
"id": "10:8814538095619551454",
497+
"lastPropertyId": "4:1497692645133575154",
498+
"name": "ThrowingInConverters",
499+
"properties": [
500+
{
501+
"id": "1:4741681947876978320",
502+
"name": "id",
503+
"type": 6,
504+
"flags": 1
505+
},
506+
{
507+
"id": "2:4138824249670147679",
508+
"name": "throwOnGet",
509+
"type": 1
510+
},
511+
{
512+
"id": "3:7057161000152955585",
513+
"name": "throwOnPut",
514+
"type": 1
515+
},
516+
{
517+
"id": "4:1497692645133575154",
518+
"name": "value",
519+
"type": 6
520+
}
521+
],
522+
"relations": []
494523
}
495524
],
496-
"lastEntityId": "9:5417142555323669520",
525+
"lastEntityId": "10:8814538095619551454",
497526
"lastIndexId": "19:3009172190024929732",
498527
"lastRelationId": "1:2155747579134420981",
499528
"lastSequenceId": "0:0",

objectbox/test/query_test.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import 'package:collection/collection.dart';
22
import 'package:test/test.dart';
33

44
import 'entity.dart';
5+
import 'entity2.dart';
56
import 'objectbox.g.dart';
67
import 'test_env.dart';
78

@@ -869,4 +870,18 @@ void main() {
869870
'| Link RelatedEntityA via standalone Relation 1 (from entity 1 to 4) with conditions: tInt == 11',
870871
].join('\n'));
871872
});
873+
874+
test('throwing in converters', () {
875+
late Box<ThrowingInConverters> box = env.store.box();
876+
877+
box.put(ThrowingInConverters(throwOnGet: true));
878+
box.put(ThrowingInConverters());
879+
880+
final query = box.query().build();
881+
expect(query.count(), 2);
882+
expect(query.findIds().length, 2);
883+
884+
expect(query.findFirst, ThrowingInConverters.throwsIn('Setter'));
885+
expect(query.find, ThrowingInConverters.throwsIn('Setter'));
886+
});
872887
}

0 commit comments

Comments
 (0)