From 02342765b68923634a86ab600b29120762d0b04d Mon Sep 17 00:00:00 2001 From: Jasm Sison Date: Sun, 27 Sep 2020 00:03:00 +0200 Subject: [PATCH 01/15] Implementation of @Unique (that is already implicitly supported by @Property) The tests keep exploding with this though: ObjectBoxException: failed to create store: 10501 No index ID/UID assigned for indexed property: Property uByte (15, Byte) Even objectbox-model.json seems normal. --- generator/lib/src/entity_resolver.dart | 6 +++ lib/src/annotations.dart | 5 +++ test/box_test.dart | 9 +++++ test/entity.dart | 51 ++++++++++++++++++++--- test/objectbox-model.json | 56 +++++++++++++++++++++++++- 5 files changed, 121 insertions(+), 6 deletions(-) diff --git a/generator/lib/src/entity_resolver.dart b/generator/lib/src/entity_resolver.dart index 1234fe480..d61223b39 100644 --- a/generator/lib/src/entity_resolver.dart +++ b/generator/lib/src/entity_resolver.dart @@ -20,6 +20,7 @@ class EntityResolver extends Builder { final _propertyChecker = const TypeChecker.fromRuntime(obx.Property); final _idChecker = const TypeChecker.fromRuntime(obx.Id); final _transientChecker = const TypeChecker.fromRuntime(obx.Transient); + final _uniqueChecker = const TypeChecker.fromRuntime(obx.Unique); @override FutureOr build(BuildStep buildStep) async { @@ -87,6 +88,11 @@ class EntityResolver extends Builder { final _idAnnotation = _idChecker.firstAnnotationOfExact(f); propUid = _idAnnotation.getField('uid').toIntValue(); + } else if (_uniqueChecker.hasAnnotationOfExact(f)) { + final _uniqueAnnotation = _uniqueChecker.firstAnnotationOfExact(f); + propUid = _uniqueAnnotation.getField('uid').toIntValue(); + fieldType = _uniqueAnnotation.getField('type').toIntValue(); + flags = OBXPropertyFlag.UNIQUE; } else if (_propertyChecker.hasAnnotationOfExact(f)) { final _propertyAnnotation = _propertyChecker.firstAnnotationOfExact(f); propUid = _propertyAnnotation.getField('uid').toIntValue(); diff --git a/lib/src/annotations.dart b/lib/src/annotations.dart index aff1fd12b..2361c2b19 100644 --- a/lib/src/annotations.dart +++ b/lib/src/annotations.dart @@ -24,3 +24,8 @@ class Id { class Transient { const Transient(); } + +class Unique { + final int uid, type; + const Unique({this.type, this.uid}); +} diff --git a/test/box_test.dart b/test/box_test.dart index 77a214fec..6a72057d5 100644 --- a/test/box_test.dart +++ b/test/box_test.dart @@ -52,6 +52,15 @@ void main() { expect(item.tString, equals('Two')); }); + test('.put() cannot add duplicate values on a unique field', () { + final u1 = TestEntity.unique(uString: 'a', uLong: 1, uInt: 1, + uShort: 1, uBool: false, uByte: 1, uChar: 1, uDouble: 0.1, uFloat: 1); + final again = TestEntity.unique(uString: 'a', uLong: 1, uInt: 1, + uShort: 1, uBool: false, uByte: 1, uChar: 1, uDouble: 0.1, uFloat: 1); + + expect(box.putMany([u1, again]), throwsException); + }); + test('.getAll retrieves all items', () { final int id1 = box.put(TestEntity(tString: 'One')); final int id2 = box.put(TestEntity(tString: 'Two')); diff --git a/test/entity.dart b/test/entity.dart index aaaeef2d4..34197d87d 100644 --- a/test/entity.dart +++ b/test/entity.dart @@ -1,4 +1,5 @@ import 'package:objectbox/objectbox.dart'; +import 'package:objectbox/src/bindings/constants.dart'; /// A dummy annotation to verify the code is generated properly even with annotations unknown to ObjectBox generator. class TestingUnknownAnnotation { @@ -27,23 +28,23 @@ class TestEntity { // explicitly declared types, see OB-C, objectbox.h // OBXPropertyType.Byte | 1 byte - @Property(type: 2) + @Property(type: OBXPropertyType.Byte) int tByte; // OBXPropertyType.Short | 2 bytes - @Property(type: 3) + @Property(type: OBXPropertyType.Short) int tShort; // OBXPropertyType.Char | 1 byte - @Property(type: 4) + @Property(type: OBXPropertyType.Char) int tChar; // OBXPropertyType.Int | ob: 4 bytes, dart: 8 bytes - @Property(type: 5) + @Property(type: OBXPropertyType.Int) int tInt; // OBXPropertyType.Float | 4 bytes - @Property(type: 7) + @Property(type: OBXPropertyType.Float) double tFloat; TestEntity( @@ -63,4 +64,44 @@ class TestEntity { omit = -1; disregard = 1; } + + @Unique(type:OBXPropertyType.Byte) + int uByte; + + @Unique(type:OBXPropertyType.Short) + int uShort; + + @Unique(type:OBXPropertyType.Char) + int uChar; + + @Unique(type:OBXPropertyType.Int) + int uInt; + + @Unique(type:OBXPropertyType.Float) + double uFloat; + + // implicitly determined types + @Unique() + String uString; + + @Unique() + int uLong; + + @Unique() + double uDouble; + + @Unique() + bool uBool; + + TestEntity.unique({ + this.uString, + this.uLong, + this.uInt, + this.uShort, + this.uBool, + this.uByte, + this.uChar, + this.uDouble, + this.uFloat + }); } diff --git a/test/objectbox-model.json b/test/objectbox-model.json index 658cfbaa9..2864c0924 100644 --- a/test/objectbox-model.json +++ b/test/objectbox-model.json @@ -5,7 +5,7 @@ "entities": [ { "id": "1:4630700155272683157", - "lastPropertyId": "14:2723176855509462268", + "lastPropertyId": "23:8978425861124512379", "name": "TestEntity", "properties": [ { @@ -58,6 +58,60 @@ "id": "14:2723176855509462268", "name": "tFloat", "type": 7 + }, + { + "id": "15:767475693077394688", + "name": "uByte", + "type": 2, + "flags": 32 + }, + { + "id": "16:2923161683700519368", + "name": "uShort", + "type": 3, + "flags": 32 + }, + { + "id": "17:3741175584836339593", + "name": "uChar", + "type": 4, + "flags": 32 + }, + { + "id": "18:1170160278955796405", + "name": "uInt", + "type": 5, + "flags": 32 + }, + { + "id": "19:855671733053348098", + "name": "uFloat", + "type": 7, + "flags": 32 + }, + { + "id": "20:1217347386939197673", + "name": "uString", + "type": 9, + "flags": 32 + }, + { + "id": "21:2985664910783172912", + "name": "uLong", + "type": 6, + "flags": 32 + }, + { + "id": "22:7232617929596152876", + "name": "uDouble", + "type": 8, + "flags": 32 + }, + { + "id": "23:8978425861124512379", + "name": "uBool", + "type": 1, + "flags": 32 } ] }, From 2fcaae29a6dff9a81880234bee147f54efd9335a Mon Sep 17 00:00:00 2001 From: Jasm Sison Date: Tue, 29 Sep 2020 14:22:56 +0200 Subject: [PATCH 02/15] added @Index --- generator/lib/src/entity_resolver.dart | 6 +++ lib/src/annotations.dart | 5 +++ test/entity.dart | 40 ++++++++++++++++++ test/objectbox-model.json | 56 +++++++++++++++++++++++++- 4 files changed, 106 insertions(+), 1 deletion(-) diff --git a/generator/lib/src/entity_resolver.dart b/generator/lib/src/entity_resolver.dart index d61223b39..b0d4e6f55 100644 --- a/generator/lib/src/entity_resolver.dart +++ b/generator/lib/src/entity_resolver.dart @@ -21,6 +21,7 @@ class EntityResolver extends Builder { final _idChecker = const TypeChecker.fromRuntime(obx.Id); final _transientChecker = const TypeChecker.fromRuntime(obx.Transient); final _uniqueChecker = const TypeChecker.fromRuntime(obx.Unique); + final _indexChecker = const TypeChecker.fromRuntime(obx.Index); @override FutureOr build(BuildStep buildStep) async { @@ -93,6 +94,11 @@ class EntityResolver extends Builder { propUid = _uniqueAnnotation.getField('uid').toIntValue(); fieldType = _uniqueAnnotation.getField('type').toIntValue(); flags = OBXPropertyFlag.UNIQUE; + } else if (_indexChecker.hasAnnotationOfExact(f)) { + final _indexAnnotation = _indexChecker.firstAnnotationOfExact(f); + propUid = _indexAnnotation.getField('uid').toIntValue(); + fieldType = _indexAnnotation.getField('type').toIntValue(); + flags = OBXPropertyFlag.INDEXED; } else if (_propertyChecker.hasAnnotationOfExact(f)) { final _propertyAnnotation = _propertyChecker.firstAnnotationOfExact(f); propUid = _propertyAnnotation.getField('uid').toIntValue(); diff --git a/lib/src/annotations.dart b/lib/src/annotations.dart index 2361c2b19..0da13ee70 100644 --- a/lib/src/annotations.dart +++ b/lib/src/annotations.dart @@ -29,3 +29,8 @@ class Unique { final int uid, type; const Unique({this.type, this.uid}); } + +class Index { + final int uid, type; + const Index({this.type, this.uid}); +} diff --git a/test/entity.dart b/test/entity.dart index 34197d87d..50886dbef 100644 --- a/test/entity.dart +++ b/test/entity.dart @@ -104,4 +104,44 @@ class TestEntity { this.uDouble, this.uFloat }); + + @Index(type:OBXPropertyType.Byte) + int iByte; + + @Index(type:OBXPropertyType.Short) + int iShort; + + @Index(type:OBXPropertyType.Char) + int iChar; + + @Index(type:OBXPropertyType.Int) + int iInt; + + @Index(type:OBXPropertyType.Float) + double iFloat; + + // implicitly determined types + @Index() + String iString; + + @Index() + int iLong; + + @Index() + double iDouble; + + @Index() + bool iBool; + + TestEntity.index({ + this.iString, + this.iLong, + this.iInt, + this.iShort, + this.iBool, + this.iByte, + this.iChar, + this.iDouble, + this.iFloat + }); } diff --git a/test/objectbox-model.json b/test/objectbox-model.json index 2864c0924..05c89e963 100644 --- a/test/objectbox-model.json +++ b/test/objectbox-model.json @@ -5,7 +5,7 @@ "entities": [ { "id": "1:4630700155272683157", - "lastPropertyId": "23:8978425861124512379", + "lastPropertyId": "32:5544628040325554444", "name": "TestEntity", "properties": [ { @@ -112,6 +112,60 @@ "name": "uBool", "type": 1, "flags": 32 + }, + { + "id": "24:9122763804238561339", + "name": "iByte", + "type": 2, + "flags": 8 + }, + { + "id": "25:81930750446448558", + "name": "iShort", + "type": 3, + "flags": 8 + }, + { + "id": "26:5498485170602124507", + "name": "iChar", + "type": 4, + "flags": 8 + }, + { + "id": "27:7761170442415326977", + "name": "iInt", + "type": 5, + "flags": 8 + }, + { + "id": "28:5286929570655467121", + "name": "iFloat", + "type": 7, + "flags": 8 + }, + { + "id": "29:2621505300040664314", + "name": "iString", + "type": 9, + "flags": 8 + }, + { + "id": "30:8500915740288627912", + "name": "iLong", + "type": 6, + "flags": 8 + }, + { + "id": "31:4437647154686109657", + "name": "iDouble", + "type": 8, + "flags": 8 + }, + { + "id": "32:5544628040325554444", + "name": "iBool", + "type": 1, + "flags": 8 } ] }, From 0803a72a1be74ea84143b81f672d740a1116217a Mon Sep 17 00:00:00 2001 From: Jasm Sison Date: Tue, 29 Sep 2020 17:49:41 +0200 Subject: [PATCH 03/15] Got the indexUid to write on the json object, without everything falling apart @Unique and @Index are sharing the same logic. --- generator/lib/src/code_builder.dart | 1 + generator/lib/src/entity_resolver.dart | 25 ++++++++++++++++++------- lib/src/annotations.dart | 8 ++++---- lib/src/modelinfo/modelentity.dart | 19 +++++++++++++++++-- lib/src/modelinfo/modelproperty.dart | 8 +++++++- lib/src/util.dart | 9 +++++++++ 6 files changed, 56 insertions(+), 14 deletions(-) diff --git a/generator/lib/src/code_builder.dart b/generator/lib/src/code_builder.dart index 607c688a7..ff4c1878a 100644 --- a/generator/lib/src/code_builder.dart +++ b/generator/lib/src/code_builder.dart @@ -140,6 +140,7 @@ class CodeBuilder extends Builder { propInModel.name = prop.name; propInModel.type = prop.type; propInModel.flags = prop.flags; + propInModel.indexId = prop.indexId; } } diff --git a/generator/lib/src/entity_resolver.dart b/generator/lib/src/entity_resolver.dart index b0d4e6f55..635971804 100644 --- a/generator/lib/src/entity_resolver.dart +++ b/generator/lib/src/entity_resolver.dart @@ -6,6 +6,7 @@ import 'package:source_gen/source_gen.dart'; import 'package:objectbox/objectbox.dart' as obx; import 'package:objectbox/src/bindings/constants.dart'; import 'package:objectbox/src/modelinfo/index.dart'; +import 'package:objectbox/src/util.dart'; /// EntityResolver finds all classes with an @Entity annotation and generates '.objectbox.info' files in build cache. /// It's using some tools from source_gen but defining its custom builder because source_gen expects only dart code. @@ -70,7 +71,7 @@ class EntityResolver extends Builder { } int fieldType, flags = 0; - int propUid; + int propUid, indexUid; if (_idChecker.hasAnnotationOfExact(f)) { if (hasIdProperty) { @@ -92,11 +93,13 @@ class EntityResolver extends Builder { } else if (_uniqueChecker.hasAnnotationOfExact(f)) { final _uniqueAnnotation = _uniqueChecker.firstAnnotationOfExact(f); propUid = _uniqueAnnotation.getField('uid').toIntValue(); + indexUid = _uniqueAnnotation.getField('indexUid').toIntValue(); fieldType = _uniqueAnnotation.getField('type').toIntValue(); flags = OBXPropertyFlag.UNIQUE; } else if (_indexChecker.hasAnnotationOfExact(f)) { final _indexAnnotation = _indexChecker.firstAnnotationOfExact(f); propUid = _indexAnnotation.getField('uid').toIntValue(); + indexUid = _indexAnnotation.getField('indexUid').toIntValue(); fieldType = _indexAnnotation.getField('type').toIntValue(); flags = OBXPropertyFlag.INDEXED; } else if (_propertyChecker.hasAnnotationOfExact(f)) { @@ -133,17 +136,25 @@ class EntityResolver extends Builder { // create property (do not use readEntity.createProperty in order to avoid generating new ids) final prop = ModelProperty(IdUid.empty(), f.name, fieldType, flags, readEntity); + + // TODO test on @Property(...) that uses the proper flags for index + final isIndexer = flags.isIndexer; + + if (isIndexer) { + prop.indexId = indexUid == null ? IdUid.empty() : IdUid(0, indexUid); + } + if (propUid != null) prop.id.uid = propUid; readEntity.properties.add(prop); log.info( - ' property ${prop.name}(${prop.id}) type:${prop.type} flags:${prop.flags}'); - } + ' ${isIndexer ? "index " : ""}property ${prop.name}(${prop.id}) type:${prop.type} flags:${prop.flags}'); - // some checks on the entity's integrity - if (!hasIdProperty) { - throw InvalidGenerationSourceError( - 'in target ${elementBare.name}: has no properties annotated with @Id'); + // some checks on the entity's integrity + if (!hasIdProperty) { + throw InvalidGenerationSourceError( + 'in target ${elementBare.name}: has no properties annotated with @Id'); + } } return readEntity; diff --git a/lib/src/annotations.dart b/lib/src/annotations.dart index 0da13ee70..da833617e 100644 --- a/lib/src/annotations.dart +++ b/lib/src/annotations.dart @@ -26,11 +26,11 @@ class Transient { } class Unique { - final int uid, type; - const Unique({this.type, this.uid}); + final int uid, indexUid, type; + const Unique({this.type, this.uid, this.indexUid}); } class Index { - final int uid, type; - const Index({this.type, this.uid}); + final int uid, indexUid, type; + const Index({this.type, this.uid, this.indexUid}); } diff --git a/lib/src/modelinfo/modelentity.dart b/lib/src/modelinfo/modelentity.dart index c7240cd44..830609afd 100644 --- a/lib/src/modelinfo/modelentity.dart +++ b/lib/src/modelinfo/modelentity.dart @@ -110,7 +110,7 @@ class ModelEntity { return ret ??= findPropertyByName(other.name); } - ModelProperty createProperty(String name, [int uid = 0]) { + ModelProperty createProperty(String name, int uid, int indexUid) { var id = 1; if (properties.isNotEmpty) id = lastPropertyId.id + 1; if (uid != 0 && model.containsUid(uid)) { @@ -119,13 +119,28 @@ class ModelEntity { final uniqueUid = uid == 0 ? model.generateUid() : uid; var property = ModelProperty(IdUid(id, uniqueUid), name, 0, 0, this); + + var isIndexer = property.flags.isIndexer; + if (isIndexer) { + if (indexUid != 0 && model.containsUid(indexUid)) { + throw Exception('index uid already exists: $uid'); + } + property.indexId = IdUid(_model.lastIndexId.id + 1, + indexUid == 0 ? model.generateUid() : indexUid); + } + properties.add(property); lastPropertyId = property.id; + + if (isIndexer) { + _model.lastIndexId = property.indexId; + } + return property; } ModelProperty addProperty(ModelProperty prop) { - final modelProp = createProperty(prop.name, prop.id.uid); + final modelProp = createProperty(prop.name, prop.id.uid, prop.indexId.uid); modelProp.type = prop.type; modelProp.flags = prop.flags; return modelProp; diff --git a/lib/src/modelinfo/modelproperty.dart b/lib/src/modelinfo/modelproperty.dart index 717014750..0cce89cf6 100644 --- a/lib/src/modelinfo/modelproperty.dart +++ b/lib/src/modelinfo/modelproperty.dart @@ -1,9 +1,10 @@ import 'modelentity.dart'; import 'iduid.dart'; +import '../util.dart'; /// ModelProperty describes a single property of an entity, i.e. its id, name, type and flags. class ModelProperty { - IdUid id; + IdUid id, indexId; String name; int type, flags; ModelEntity entity; @@ -18,6 +19,8 @@ class ModelProperty { name = data['name']; type = data['type']; flags = data.containsKey('flags') ? data['flags'] : 0; + indexId = + data.containsKey('indexId') ? IdUid.fromString(data['indexId']) : null; if (check) validate(); } @@ -36,6 +39,9 @@ class ModelProperty { ret['name'] = name; ret['type'] = type; if (flags != 0) ret['flags'] = flags; + if (flags.isIndexer) { + ret['indexId'] = indexId.toString(); + } return ret; } diff --git a/lib/src/util.dart b/lib/src/util.dart index 24fd40d7f..e3a665278 100644 --- a/lib/src/util.dart +++ b/lib/src/util.dart @@ -1,2 +1,11 @@ +import 'package:objectbox/src/bindings/constants.dart'; + bool listContains(List list, T item) => list.indexWhere((x) => x == item) != -1; + +extension Indexer on int { + bool get isIndexer => + this & (OBXPropertyFlag.INDEXED + OBXPropertyFlag.UNIQUE) == 40 || + this & OBXPropertyFlag.INDEXED == 8 || + this & OBXPropertyFlag.UNIQUE == 32; +} From b2c056dcfe1775da92cd186c4f1f5c66ec25c57a Mon Sep 17 00:00:00 2001 From: Jasm Sison Date: Tue, 29 Sep 2020 19:54:41 +0200 Subject: [PATCH 04/15] accidentally changed the scope of the loop --- generator/lib/src/entity_resolver.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/generator/lib/src/entity_resolver.dart b/generator/lib/src/entity_resolver.dart index 635971804..9d5ec3781 100644 --- a/generator/lib/src/entity_resolver.dart +++ b/generator/lib/src/entity_resolver.dart @@ -149,12 +149,12 @@ class EntityResolver extends Builder { log.info( ' ${isIndexer ? "index " : ""}property ${prop.name}(${prop.id}) type:${prop.type} flags:${prop.flags}'); + } - // some checks on the entity's integrity - if (!hasIdProperty) { - throw InvalidGenerationSourceError( - 'in target ${elementBare.name}: has no properties annotated with @Id'); - } + // some checks on the entity's integrity + if (!hasIdProperty) { + throw InvalidGenerationSourceError( + 'in target ${elementBare.name}: has no properties annotated with @Id'); } return readEntity; From b33d534415aa24d2250f32c5f202d5ba9694cdab Mon Sep 17 00:00:00 2001 From: Jasm Sison Date: Tue, 29 Sep 2020 22:54:28 +0200 Subject: [PATCH 05/15] reset the json --- test/objectbox-model.json | 110 +------------------------------------- 1 file changed, 1 insertion(+), 109 deletions(-) diff --git a/test/objectbox-model.json b/test/objectbox-model.json index 05c89e963..658cfbaa9 100644 --- a/test/objectbox-model.json +++ b/test/objectbox-model.json @@ -5,7 +5,7 @@ "entities": [ { "id": "1:4630700155272683157", - "lastPropertyId": "32:5544628040325554444", + "lastPropertyId": "14:2723176855509462268", "name": "TestEntity", "properties": [ { @@ -58,114 +58,6 @@ "id": "14:2723176855509462268", "name": "tFloat", "type": 7 - }, - { - "id": "15:767475693077394688", - "name": "uByte", - "type": 2, - "flags": 32 - }, - { - "id": "16:2923161683700519368", - "name": "uShort", - "type": 3, - "flags": 32 - }, - { - "id": "17:3741175584836339593", - "name": "uChar", - "type": 4, - "flags": 32 - }, - { - "id": "18:1170160278955796405", - "name": "uInt", - "type": 5, - "flags": 32 - }, - { - "id": "19:855671733053348098", - "name": "uFloat", - "type": 7, - "flags": 32 - }, - { - "id": "20:1217347386939197673", - "name": "uString", - "type": 9, - "flags": 32 - }, - { - "id": "21:2985664910783172912", - "name": "uLong", - "type": 6, - "flags": 32 - }, - { - "id": "22:7232617929596152876", - "name": "uDouble", - "type": 8, - "flags": 32 - }, - { - "id": "23:8978425861124512379", - "name": "uBool", - "type": 1, - "flags": 32 - }, - { - "id": "24:9122763804238561339", - "name": "iByte", - "type": 2, - "flags": 8 - }, - { - "id": "25:81930750446448558", - "name": "iShort", - "type": 3, - "flags": 8 - }, - { - "id": "26:5498485170602124507", - "name": "iChar", - "type": 4, - "flags": 8 - }, - { - "id": "27:7761170442415326977", - "name": "iInt", - "type": 5, - "flags": 8 - }, - { - "id": "28:5286929570655467121", - "name": "iFloat", - "type": 7, - "flags": 8 - }, - { - "id": "29:2621505300040664314", - "name": "iString", - "type": 9, - "flags": 8 - }, - { - "id": "30:8500915740288627912", - "name": "iLong", - "type": 6, - "flags": 8 - }, - { - "id": "31:4437647154686109657", - "name": "iDouble", - "type": 8, - "flags": 8 - }, - { - "id": "32:5544628040325554444", - "name": "iBool", - "type": 1, - "flags": 8 } ] }, From b5939d8a814523ff7763fad147ac21fcdae6d5b0 Mon Sep 17 00:00:00 2001 From: Jasm Sison Date: Wed, 30 Sep 2020 00:06:31 +0200 Subject: [PATCH 06/15] why merge twice?! --- generator/lib/src/code_builder.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/generator/lib/src/code_builder.dart b/generator/lib/src/code_builder.dart index ff4c1878a..463b85927 100644 --- a/generator/lib/src/code_builder.dart +++ b/generator/lib/src/code_builder.dart @@ -127,8 +127,6 @@ class CodeBuilder extends Builder { 'Entity ${entity.name}(${entity.id.toString()}) not found in the code, removing from the model'); model.removeEntity(entity); }); - - entities.forEach((entity) => mergeEntity(model, entity)); } void mergeProperty(ModelEntity entity, ModelProperty prop) { From 25924e48f175da9cbee4ff04f44abc2736c6e78d Mon Sep 17 00:00:00 2001 From: Jasm Sison Date: Wed, 30 Sep 2020 00:14:38 +0200 Subject: [PATCH 07/15] solved the mystery of the disappearing IdUids --- generator/lib/src/code_builder.dart | 13 +++++++--- lib/src/modelinfo/modelentity.dart | 39 ++++++++++++++-------------- lib/src/modelinfo/modelproperty.dart | 5 ++-- lib/src/util.dart | 1 - 4 files changed, 31 insertions(+), 27 deletions(-) diff --git a/generator/lib/src/code_builder.dart b/generator/lib/src/code_builder.dart index 463b85927..5d5907f0e 100644 --- a/generator/lib/src/code_builder.dart +++ b/generator/lib/src/code_builder.dart @@ -135,10 +135,15 @@ class CodeBuilder extends Builder { log.info('Found new property ${entity.name}.${prop.name}'); entity.addProperty(prop); } else { - propInModel.name = prop.name; - propInModel.type = prop.type; - propInModel.flags = prop.flags; - propInModel.indexId = prop.indexId; + if (propInModel.name != prop.name) { + log.warning('The name of the property(${prop.name}) changed.'); + } + if (propInModel.flags != prop.flags) { + log.warning('The flags of the property(${prop.name}) changed.'); + } + if (propInModel.type != prop.type) { + log.warning('The type of the property(${prop.name}) changed.'); + } } } diff --git a/lib/src/modelinfo/modelentity.dart b/lib/src/modelinfo/modelentity.dart index 830609afd..e2a4048df 100644 --- a/lib/src/modelinfo/modelentity.dart +++ b/lib/src/modelinfo/modelentity.dart @@ -1,8 +1,8 @@ -import '../util.dart'; import 'iduid.dart'; import 'modelinfo.dart'; import 'modelproperty.dart'; -import 'package:objectbox/src/bindings/constants.dart'; +import '../bindings/constants.dart'; +import '../util.dart'; /// ModelEntity describes an entity of a model and consists of instances of `ModelProperty` as well as an other entity /// information: id, name and last property id. @@ -110,40 +110,38 @@ class ModelEntity { return ret ??= findPropertyByName(other.name); } - ModelProperty createProperty(String name, int uid, int indexUid) { - var id = 1; - if (properties.isNotEmpty) id = lastPropertyId.id + 1; + ModelProperty createProperty( + String name, int uid, int type, int flags, int indexUid) { + final id = properties.isNotEmpty ? lastPropertyId.id + 1 : 1; if (uid != 0 && model.containsUid(uid)) { throw Exception('uid already exists: $uid'); } final uniqueUid = uid == 0 ? model.generateUid() : uid; - var property = ModelProperty(IdUid(id, uniqueUid), name, 0, 0, this); + final property = ModelProperty(IdUid(id, uniqueUid), name, 0, 0, this); - var isIndexer = property.flags.isIndexer; - if (isIndexer) { + if (flags.isIndexer) { if (indexUid != 0 && model.containsUid(indexUid)) { throw Exception('index uid already exists: $uid'); } property.indexId = IdUid(_model.lastIndexId.id + 1, indexUid == 0 ? model.generateUid() : indexUid); + + _model.lastIndexId = property.indexId; } + property.type = type; + property.flags = flags; + properties.add(property); lastPropertyId = property.id; - if (isIndexer) { - _model.lastIndexId = property.indexId; - } - return property; } ModelProperty addProperty(ModelProperty prop) { - final modelProp = createProperty(prop.name, prop.id.uid, prop.indexId.uid); - modelProp.type = prop.type; - modelProp.flags = prop.flags; - return modelProp; + return createProperty( + prop.name, prop.id.uid, prop.type, prop.flags, prop.indexId.uid); } void removeProperty(ModelProperty prop) { @@ -155,14 +153,15 @@ class ModelEntity { } properties = properties.where((p) => p != foundProp).toList(); model.retiredPropertyUids.add(prop.id.uid); + + if (prop.flags.isIndexer) { + model.retiredIndexUids.add(prop.indexId.uid); + } } bool containsUid(int searched) { if (id.uid == searched) return true; if (lastPropertyId != null && lastPropertyId.uid == searched) return true; - if (properties.indexWhere((p) => p.containsUid(searched)) != -1) { - return true; - } - return false; + return properties.indexWhere((p) => p.containsUid(searched)) != -1; } } diff --git a/lib/src/modelinfo/modelproperty.dart b/lib/src/modelinfo/modelproperty.dart index 0cce89cf6..31866038a 100644 --- a/lib/src/modelinfo/modelproperty.dart +++ b/lib/src/modelinfo/modelproperty.dart @@ -19,8 +19,9 @@ class ModelProperty { name = data['name']; type = data['type']; flags = data.containsKey('flags') ? data['flags'] : 0; - indexId = - data.containsKey('indexId') ? IdUid.fromString(data['indexId']) : null; + if (flags.isIndexer) { + indexId = IdUid.fromString(data['indexId']); + } if (check) validate(); } diff --git a/lib/src/util.dart b/lib/src/util.dart index e3a665278..05b276311 100644 --- a/lib/src/util.dart +++ b/lib/src/util.dart @@ -5,7 +5,6 @@ bool listContains(List list, T item) => extension Indexer on int { bool get isIndexer => - this & (OBXPropertyFlag.INDEXED + OBXPropertyFlag.UNIQUE) == 40 || this & OBXPropertyFlag.INDEXED == 8 || this & OBXPropertyFlag.UNIQUE == 32; } From 2595dc6c4afe6e85e2b94756d3d2cf8dbd5d5baf Mon Sep 17 00:00:00 2001 From: Jasm Sison Date: Wed, 30 Sep 2020 01:19:03 +0200 Subject: [PATCH 08/15] ha, it turns out you cannot index floats After turning them off this popped beautiful gem up: ObjectBoxException: failed to create store: 10001 State condition failed in assignIdsForProperty:476: indexId <= catalog.lastIndexId() --- lib/src/bindings/bindings.dart | 12 +++++++++++- lib/src/bindings/signatures.dart | 6 +++++- lib/src/model.dart | 11 +++++++++++ test/box_test.dart | 4 ++-- test/entity.dart | 16 ---------------- 5 files changed, 29 insertions(+), 20 deletions(-) diff --git a/lib/src/bindings/bindings.dart b/lib/src/bindings/bindings.dart index 31101d773..891216377 100644 --- a/lib/src/bindings/bindings.dart +++ b/lib/src/bindings/bindings.dart @@ -43,11 +43,15 @@ class _ObjectBoxBindings { int entity_uid) obx_model_entity; int Function(Pointer model, Pointer name, int type, int property_id, int property_uid) obx_model_property; + int Function(Pointer model, int index_id, int index_uid) + obx_model_property_index_id; int Function(Pointer model, int flags) obx_model_property_flags; int Function(Pointer model, int property_id, int property_uid) obx_model_entity_last_property_id; - int Function(Pointer model, int entity_id, int entity_uid) + void Function(Pointer model, int entity_id, int entity_uid) obx_model_last_entity_id; + void Function(Pointer model, int entity_id, int index_uid) + obx_model_last_index_id; // object store management Pointer Function() obx_opt; @@ -282,6 +286,9 @@ class _ObjectBoxBindings { _fn('obx_model_entity').asFunction(); obx_model_property = _fn('obx_model_property').asFunction(); + obx_model_property_index_id = + _fn('obx_model_property_index_id') + .asFunction(); obx_model_property_flags = _fn('obx_model_property_flags') .asFunction(); @@ -292,6 +299,9 @@ class _ObjectBoxBindings { obx_model_last_entity_id = _fn('obx_model_last_entity_id') .asFunction(); + obx_model_last_index_id = + _fn('obx_model_last_entity_id') + .asFunction(); // object store management obx_opt = _fn('obx_opt').asFunction(); diff --git a/lib/src/bindings/signatures.dart b/lib/src/bindings/signatures.dart index c7261d2f8..dc0259d7d 100644 --- a/lib/src/bindings/signatures.dart +++ b/lib/src/bindings/signatures.dart @@ -33,12 +33,16 @@ typedef obx_model_entity_native_t = Int32 Function(Pointer model, Pointer name, Uint32 entity_id, Uint64 entity_uid); typedef obx_model_property_native_t = Int32 Function(Pointer model, Pointer name, Uint32 type, Uint32 property_id, Uint64 property_uid); +typedef obx_model_property_index_id_native_t = Int32 Function( + Pointer model, Uint32 index_id, Uint64 index_uid); typedef obx_model_property_flags_native_t = Int32 Function( Pointer model, Uint32 flags); typedef obx_model_entity_last_property_id_native_t = Int32 Function( Pointer model, Uint32 property_id, Uint64 property_uid); -typedef obx_model_last_entity_id_native_t = Int32 Function( +typedef obx_model_last_entity_id_native_t = Void Function( Pointer model, Uint32 entity_id, Uint64 entity_uid); +typedef obx_model_last_index_id_native_t = Void Function( + Pointer model, Uint32 entity_id, Uint64 index_uid); // object store management typedef obx_opt_native_t = Pointer Function(); diff --git a/lib/src/model.dart b/lib/src/model.dart index e6ababf2b..4aff0fdb7 100644 --- a/lib/src/model.dart +++ b/lib/src/model.dart @@ -21,6 +21,12 @@ class Model { // set last entity id bindings.obx_model_last_entity_id( _cModel, model.lastEntityId.id, model.lastEntityId.uid); + + // set last index id + if (model.lastIndexId != null) { + bindings.obx_model_last_index_id( + _cModel, model.lastIndexId.id, model.lastIndexId.uid); + } } catch (e) { bindings.obx_model_free(_cModel); _cModel = null; @@ -59,6 +65,11 @@ class Model { try { _check(bindings.obx_model_property( _cModel, name, prop.type, prop.id.id, prop.id.uid)); + + if (prop.indexId != null) { + _check(bindings.obx_model_property_index_id( + _cModel, prop.indexId.id, prop.indexId.uid)); + } } finally { free(name); } diff --git a/test/box_test.dart b/test/box_test.dart index 6a72057d5..f2e58709f 100644 --- a/test/box_test.dart +++ b/test/box_test.dart @@ -54,9 +54,9 @@ void main() { test('.put() cannot add duplicate values on a unique field', () { final u1 = TestEntity.unique(uString: 'a', uLong: 1, uInt: 1, - uShort: 1, uBool: false, uByte: 1, uChar: 1, uDouble: 0.1, uFloat: 1); + uShort: 1, uBool: false, uByte: 1, uChar: 1); final again = TestEntity.unique(uString: 'a', uLong: 1, uInt: 1, - uShort: 1, uBool: false, uByte: 1, uChar: 1, uDouble: 0.1, uFloat: 1); + uShort: 1, uBool: false, uByte: 1, uChar: 1); expect(box.putMany([u1, again]), throwsException); }); diff --git a/test/entity.dart b/test/entity.dart index 50886dbef..c37fdcdfe 100644 --- a/test/entity.dart +++ b/test/entity.dart @@ -77,9 +77,6 @@ class TestEntity { @Unique(type:OBXPropertyType.Int) int uInt; - @Unique(type:OBXPropertyType.Float) - double uFloat; - // implicitly determined types @Unique() String uString; @@ -87,9 +84,6 @@ class TestEntity { @Unique() int uLong; - @Unique() - double uDouble; - @Unique() bool uBool; @@ -101,8 +95,6 @@ class TestEntity { this.uBool, this.uByte, this.uChar, - this.uDouble, - this.uFloat }); @Index(type:OBXPropertyType.Byte) @@ -117,9 +109,6 @@ class TestEntity { @Index(type:OBXPropertyType.Int) int iInt; - @Index(type:OBXPropertyType.Float) - double iFloat; - // implicitly determined types @Index() String iString; @@ -127,9 +116,6 @@ class TestEntity { @Index() int iLong; - @Index() - double iDouble; - @Index() bool iBool; @@ -141,7 +127,5 @@ class TestEntity { this.iBool, this.iByte, this.iChar, - this.iDouble, - this.iFloat }); } From 5bc4a81075ead70adb215fd4ec3914c2254265ff Mon Sep 17 00:00:00 2001 From: Jasm Sison Date: Wed, 30 Sep 2020 14:24:55 +0200 Subject: [PATCH 09/15] TODO test how OB handles it, I expect it should handle it like floats --- test/box_test.dart | 4 ++-- test/entity.dart | 8 -------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/test/box_test.dart b/test/box_test.dart index f2e58709f..da566e577 100644 --- a/test/box_test.dart +++ b/test/box_test.dart @@ -54,9 +54,9 @@ void main() { test('.put() cannot add duplicate values on a unique field', () { final u1 = TestEntity.unique(uString: 'a', uLong: 1, uInt: 1, - uShort: 1, uBool: false, uByte: 1, uChar: 1); + uShort: 1, uByte: 1, uChar: 1); final again = TestEntity.unique(uString: 'a', uLong: 1, uInt: 1, - uShort: 1, uBool: false, uByte: 1, uChar: 1); + uShort: 1, uByte: 1, uChar: 1); expect(box.putMany([u1, again]), throwsException); }); diff --git a/test/entity.dart b/test/entity.dart index c37fdcdfe..c63e145b4 100644 --- a/test/entity.dart +++ b/test/entity.dart @@ -84,15 +84,11 @@ class TestEntity { @Unique() int uLong; - @Unique() - bool uBool; - TestEntity.unique({ this.uString, this.uLong, this.uInt, this.uShort, - this.uBool, this.uByte, this.uChar, }); @@ -116,15 +112,11 @@ class TestEntity { @Index() int iLong; - @Index() - bool iBool; - TestEntity.index({ this.iString, this.iLong, this.iInt, this.iShort, - this.iBool, this.iByte, this.iChar, }); From 1a27e36a548606a7b2de996b2bf9cba5de433308 Mon Sep 17 00:00:00 2001 From: Jasm Sison Date: Mon, 5 Oct 2020 22:12:07 +0200 Subject: [PATCH 10/15] copy paste oversight --- lib/src/bindings/bindings.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/bindings/bindings.dart b/lib/src/bindings/bindings.dart index 891216377..1d2bec35a 100644 --- a/lib/src/bindings/bindings.dart +++ b/lib/src/bindings/bindings.dart @@ -300,7 +300,7 @@ class _ObjectBoxBindings { _fn('obx_model_last_entity_id') .asFunction(); obx_model_last_index_id = - _fn('obx_model_last_entity_id') + _fn('obx_model_last_index_id') .asFunction(); // object store management From 8656f984cce88cabaf298e12ec65409903e54509 Mon Sep 17 00:00:00 2001 From: Jasm Sison Date: Mon, 5 Oct 2020 22:22:29 +0200 Subject: [PATCH 11/15] apparently, bytes are also not indexable --- test/box_test.dart | 4 ++-- test/entity.dart | 8 -------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/test/box_test.dart b/test/box_test.dart index da566e577..10a3a419e 100644 --- a/test/box_test.dart +++ b/test/box_test.dart @@ -54,9 +54,9 @@ void main() { test('.put() cannot add duplicate values on a unique field', () { final u1 = TestEntity.unique(uString: 'a', uLong: 1, uInt: 1, - uShort: 1, uByte: 1, uChar: 1); + uShort: 1, uChar: 1); final again = TestEntity.unique(uString: 'a', uLong: 1, uInt: 1, - uShort: 1, uByte: 1, uChar: 1); + uShort: 1, uChar: 1); expect(box.putMany([u1, again]), throwsException); }); diff --git a/test/entity.dart b/test/entity.dart index c63e145b4..6c73a09bf 100644 --- a/test/entity.dart +++ b/test/entity.dart @@ -65,9 +65,6 @@ class TestEntity { disregard = 1; } - @Unique(type:OBXPropertyType.Byte) - int uByte; - @Unique(type:OBXPropertyType.Short) int uShort; @@ -89,13 +86,9 @@ class TestEntity { this.uLong, this.uInt, this.uShort, - this.uByte, this.uChar, }); - @Index(type:OBXPropertyType.Byte) - int iByte; - @Index(type:OBXPropertyType.Short) int iShort; @@ -117,7 +110,6 @@ class TestEntity { this.iLong, this.iInt, this.iShort, - this.iByte, this.iChar, }); } From affef5991408027495acde33a05278e37b1e0420 Mon Sep 17 00:00:00 2001 From: Jasm Sison Date: Mon, 5 Oct 2020 22:38:22 +0200 Subject: [PATCH 12/15] Add thrown exception when using floats, doubles and bytes as index/unique --- generator/lib/src/entity_resolver.dart | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/generator/lib/src/entity_resolver.dart b/generator/lib/src/entity_resolver.dart index 9d5ec3781..8c3de65ff 100644 --- a/generator/lib/src/entity_resolver.dart +++ b/generator/lib/src/entity_resolver.dart @@ -141,6 +141,11 @@ class EntityResolver extends Builder { final isIndexer = flags.isIndexer; if (isIndexer) { + if (fieldType == OBXPropertyType.Float || fieldType == OBXPropertyType.Double || fieldType == OBXPropertyType.Byte) { + throw InvalidGenerationSourceError( + 'property ${prop.name} with type ${prop.type} cannot be used as an index or made unique'); + } + prop.indexId = indexUid == null ? IdUid.empty() : IdUid(0, indexUid); } From 648b8b5b615893b1e4da01f1b539ae1bf9dcd971 Mon Sep 17 00:00:00 2001 From: Uwe Trottmann <13865709+greenrobot-team@users.noreply.github.com> Date: Tue, 6 Oct 2020 14:43:50 +0200 Subject: [PATCH 13/15] ByteVector can not be indexed, Byte can. --- generator/lib/src/entity_resolver.dart | 2 +- test/box_test.dart | 4 ++-- test/entity.dart | 8 ++++++++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/generator/lib/src/entity_resolver.dart b/generator/lib/src/entity_resolver.dart index 8c3de65ff..8b235a010 100644 --- a/generator/lib/src/entity_resolver.dart +++ b/generator/lib/src/entity_resolver.dart @@ -141,7 +141,7 @@ class EntityResolver extends Builder { final isIndexer = flags.isIndexer; if (isIndexer) { - if (fieldType == OBXPropertyType.Float || fieldType == OBXPropertyType.Double || fieldType == OBXPropertyType.Byte) { + if (fieldType == OBXPropertyType.Float || fieldType == OBXPropertyType.Double || fieldType == OBXPropertyType.ByteVector) { throw InvalidGenerationSourceError( 'property ${prop.name} with type ${prop.type} cannot be used as an index or made unique'); } diff --git a/test/box_test.dart b/test/box_test.dart index 10a3a419e..da566e577 100644 --- a/test/box_test.dart +++ b/test/box_test.dart @@ -54,9 +54,9 @@ void main() { test('.put() cannot add duplicate values on a unique field', () { final u1 = TestEntity.unique(uString: 'a', uLong: 1, uInt: 1, - uShort: 1, uChar: 1); + uShort: 1, uByte: 1, uChar: 1); final again = TestEntity.unique(uString: 'a', uLong: 1, uInt: 1, - uShort: 1, uChar: 1); + uShort: 1, uByte: 1, uChar: 1); expect(box.putMany([u1, again]), throwsException); }); diff --git a/test/entity.dart b/test/entity.dart index 6c73a09bf..c63e145b4 100644 --- a/test/entity.dart +++ b/test/entity.dart @@ -65,6 +65,9 @@ class TestEntity { disregard = 1; } + @Unique(type:OBXPropertyType.Byte) + int uByte; + @Unique(type:OBXPropertyType.Short) int uShort; @@ -86,9 +89,13 @@ class TestEntity { this.uLong, this.uInt, this.uShort, + this.uByte, this.uChar, }); + @Index(type:OBXPropertyType.Byte) + int iByte; + @Index(type:OBXPropertyType.Short) int iShort; @@ -110,6 +117,7 @@ class TestEntity { this.iLong, this.iInt, this.iShort, + this.iByte, this.iChar, }); } From 756f92a5d5d74616a40ea42eb44edc03b1de57fb Mon Sep 17 00:00:00 2001 From: Uwe Trottmann <13865709+greenrobot-team@users.noreply.github.com> Date: Tue, 6 Oct 2020 16:27:23 +0200 Subject: [PATCH 14/15] DRAFT: @Index and @Unique annotation. --- generator/lib/src/entity_resolver.dart | 70 ++++++++++++++------ lib/src/annotations.dart | 30 +++++++-- test/entity.dart | 24 ++++--- test/objectbox-model.json | 88 +++++++++++++++++++++++++- 4 files changed, 178 insertions(+), 34 deletions(-) diff --git a/generator/lib/src/entity_resolver.dart b/generator/lib/src/entity_resolver.dart index 8b235a010..161b73c9f 100644 --- a/generator/lib/src/entity_resolver.dart +++ b/generator/lib/src/entity_resolver.dart @@ -1,12 +1,13 @@ import 'dart:async'; import 'dart:convert'; + import 'package:analyzer/dart/element/element.dart'; import 'package:build/build.dart'; -import 'package:source_gen/source_gen.dart'; import 'package:objectbox/objectbox.dart' as obx; import 'package:objectbox/src/bindings/constants.dart'; import 'package:objectbox/src/modelinfo/index.dart'; import 'package:objectbox/src/util.dart'; +import 'package:source_gen/source_gen.dart'; /// EntityResolver finds all classes with an @Entity annotation and generates '.objectbox.info' files in build cache. /// It's using some tools from source_gen but defining its custom builder because source_gen expects only dart code. @@ -90,18 +91,6 @@ class EntityResolver extends Builder { final _idAnnotation = _idChecker.firstAnnotationOfExact(f); propUid = _idAnnotation.getField('uid').toIntValue(); - } else if (_uniqueChecker.hasAnnotationOfExact(f)) { - final _uniqueAnnotation = _uniqueChecker.firstAnnotationOfExact(f); - propUid = _uniqueAnnotation.getField('uid').toIntValue(); - indexUid = _uniqueAnnotation.getField('indexUid').toIntValue(); - fieldType = _uniqueAnnotation.getField('type').toIntValue(); - flags = OBXPropertyFlag.UNIQUE; - } else if (_indexChecker.hasAnnotationOfExact(f)) { - final _indexAnnotation = _indexChecker.firstAnnotationOfExact(f); - propUid = _indexAnnotation.getField('uid').toIntValue(); - indexUid = _indexAnnotation.getField('indexUid').toIntValue(); - fieldType = _indexAnnotation.getField('type').toIntValue(); - flags = OBXPropertyFlag.INDEXED; } else if (_propertyChecker.hasAnnotationOfExact(f)) { final _propertyAnnotation = _propertyChecker.firstAnnotationOfExact(f); propUid = _propertyAnnotation.getField('uid').toIntValue(); @@ -133,6 +122,56 @@ class EntityResolver extends Builder { } } + // Index and unique index. + final indexAnnotation = _indexChecker.firstAnnotationOfExact(f); + final hasUniqueAnnotation = _uniqueChecker.hasAnnotationOfExact(f); + if (indexAnnotation != null || hasUniqueAnnotation) { + // Throw if property type does not support any index. + if (fieldType == OBXPropertyType.Float || fieldType == OBXPropertyType.Double || fieldType == OBXPropertyType.ByteVector) { + throw InvalidGenerationSourceError( + "in target ${elementBare.name}: @Index/@Unique is not supported for type '${f.type.toString()}' of field '${f.name}'"); + } + + // TODO Throw if index used on Id property. + + + // If available use index type from annotation. + var indexFlag; + if (indexAnnotation != null) { + indexFlag = indexAnnotation.getField('flag').toIntValue(); + if (indexFlag != null && + (indexFlag != OBXPropertyFlag.INDEXED + || indexFlag != OBXPropertyFlag.INDEX_HASH + || indexFlag != OBXPropertyFlag.INDEX_HASH64)) { + throw InvalidGenerationSourceError( + 'in target ${elementBare.name}: @Index flag must be one of [OBXPropertyFlag.INDEXED, OBXPropertyFlag.INDEX_HASH, OBXPropertyFlag.INDEX_HASH64] or none for auto-detection'); + } + } + + // Fall back to index type based on property type. + final supportsHashIndex = fieldType == OBXPropertyType.String; + if (indexFlag == null) { + if (supportsHashIndex) { + indexFlag = OBXPropertyFlag.INDEX_HASH; + } else { + indexFlag = OBXPropertyFlag.INDEXED; + } + } + + // Throw if HASH or HASH64 is not supported by property type. + if (!supportsHashIndex && + (indexFlag == OBXPropertyFlag.INDEX_HASH || + indexFlag == OBXPropertyFlag.INDEX_HASH64)) { + throw InvalidGenerationSourceError( + "in target ${elementBare.name}: a hash index is not supported for type '${f.type.toString()}' of field '${f.name}'"); + } + + flags |= indexFlag; + if (hasUniqueAnnotation) { + flags |= OBXPropertyFlag.UNIQUE; + } + } + // create property (do not use readEntity.createProperty in order to avoid generating new ids) final prop = ModelProperty(IdUid.empty(), f.name, fieldType, flags, readEntity); @@ -141,11 +180,6 @@ class EntityResolver extends Builder { final isIndexer = flags.isIndexer; if (isIndexer) { - if (fieldType == OBXPropertyType.Float || fieldType == OBXPropertyType.Double || fieldType == OBXPropertyType.ByteVector) { - throw InvalidGenerationSourceError( - 'property ${prop.name} with type ${prop.type} cannot be used as an index or made unique'); - } - prop.indexId = indexUid == null ? IdUid.empty() : IdUid(0, indexUid); } diff --git a/lib/src/annotations.dart b/lib/src/annotations.dart index da833617e..5b3786e36 100644 --- a/lib/src/annotations.dart +++ b/lib/src/annotations.dart @@ -25,12 +25,30 @@ class Transient { const Transient(); } -class Unique { - final int uid, indexUid, type; - const Unique({this.type, this.uid, this.indexUid}); -} +// TODO It's possible to pass the unique and index flags directly through Property, +// was it even intended to have a separate annotation for these? +/// Specifies that the property should be indexed. +/// +/// It is highly recommended to index properties that are used in a Query to +/// improve query performance. To fine tune indexing of a property you can +/// override the default index type. +/// +/// Note: indexes are currently not supported for ByteVector, Float or Double +/// properties. class Index { - final int uid, indexUid, type; - const Index({this.type, this.uid, this.indexUid}); + final int flag; + const Index({this.flag}); +} + +/// Enforces that the value of a property is unique among all Objects in a Box +/// before an Object can be put. +/// +/// Trying to put an Object with offending values will result in an exception. +/// +/// Unique properties are based on an [Index], so the same restrictions apply. +/// It is supported to explicitly add the [Index] annotation to configure the +/// index. +class Unique { + const Unique(); } diff --git a/test/entity.dart b/test/entity.dart index c63e145b4..7f0049b5d 100644 --- a/test/entity.dart +++ b/test/entity.dart @@ -65,16 +65,20 @@ class TestEntity { disregard = 1; } - @Unique(type:OBXPropertyType.Byte) + @Property(type:OBXPropertyType.Byte) + @Unique() int uByte; - @Unique(type:OBXPropertyType.Short) + @Property(type:OBXPropertyType.Short) + @Unique() int uShort; - @Unique(type:OBXPropertyType.Char) + @Property(type:OBXPropertyType.Char) + @Unique() int uChar; - @Unique(type:OBXPropertyType.Int) + @Property(type: OBXPropertyType.Int) + @Unique() int uInt; // implicitly determined types @@ -93,16 +97,20 @@ class TestEntity { this.uChar, }); - @Index(type:OBXPropertyType.Byte) + @Property(type:OBXPropertyType.Byte) + @Index() int iByte; - @Index(type:OBXPropertyType.Short) + @Property(type:OBXPropertyType.Short) + @Index() int iShort; - @Index(type:OBXPropertyType.Char) + @Property(type:OBXPropertyType.Char) + @Index() int iChar; - @Index(type:OBXPropertyType.Int) + @Property(type:OBXPropertyType.Int) + @Index() int iInt; // implicitly determined types diff --git a/test/objectbox-model.json b/test/objectbox-model.json index 658cfbaa9..c8380bfc4 100644 --- a/test/objectbox-model.json +++ b/test/objectbox-model.json @@ -5,7 +5,7 @@ "entities": [ { "id": "1:4630700155272683157", - "lastPropertyId": "14:2723176855509462268", + "lastPropertyId": "26:8702110868915772966", "name": "TestEntity", "properties": [ { @@ -58,6 +58,90 @@ "id": "14:2723176855509462268", "name": "tFloat", "type": 7 + }, + { + "id": "15:6996367526195150585", + "name": "uByte", + "type": 2, + "flags": 32, + "indexId": "1:8383955520317513621" + }, + { + "id": "16:2715615992053992193", + "name": "uShort", + "type": 3, + "flags": 32, + "indexId": "2:1444586949459820158" + }, + { + "id": "17:2726230190322219382", + "name": "uChar", + "type": 4, + "flags": 32, + "indexId": "3:4661641768447390575" + }, + { + "id": "18:8604306476073958418", + "name": "uInt", + "type": 5, + "flags": 32, + "indexId": "4:3169412207654167239" + }, + { + "id": "19:3289140694453689258", + "name": "uString", + "type": 9, + "flags": 32, + "indexId": "5:2652815973956236214" + }, + { + "id": "20:1896318477975978242", + "name": "uLong", + "type": 6, + "flags": 32, + "indexId": "6:1114971721802793243" + }, + { + "id": "21:4334508382176818281", + "name": "iByte", + "type": 2, + "flags": 8, + "indexId": "7:5119305361210421475" + }, + { + "id": "22:8689251269948347853", + "name": "iShort", + "type": 3, + "flags": 8, + "indexId": "8:8230437552832657067" + }, + { + "id": "23:9163528388059726221", + "name": "iChar", + "type": 4, + "flags": 8, + "indexId": "9:869568640260232176" + }, + { + "id": "24:7584537398448845337", + "name": "iInt", + "type": 5, + "flags": 8, + "indexId": "10:1487602683978225990" + }, + { + "id": "25:2091567837655054550", + "name": "iString", + "type": 9, + "flags": 8, + "indexId": "11:8051787750326117391" + }, + { + "id": "26:8702110868915772966", + "name": "iLong", + "type": 6, + "flags": 8, + "indexId": "12:1395621548785108735" } ] }, @@ -76,7 +160,7 @@ } ], "lastEntityId": "3:3569200127393812728", - "lastIndexId": "0:0", + "lastIndexId": "12:1395621548785108735", "lastRelationId": "0:0", "lastSequenceId": "0:0", "modelVersion": 5, From 859ba5df9fc31ac96e24a0a69d55d872d23405da Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Sat, 28 Nov 2020 22:43:53 +0100 Subject: [PATCH 15/15] index/unique annotations cleanup, fixes and tests --- .../objectbox_demo/test_driver/app.dart | 2 +- generator/integration-tests/common.dart | 7 + .../integration-tests/indexes/.gitignore | 2 + generator/integration-tests/indexes/1.dart | 47 ++++++ .../integration-tests/indexes/lib/lib.dart | 31 ++++ .../integration-tests/indexes/pubspec.yaml | 1 + generator/lib/src/code_builder.dart | 59 +++---- generator/lib/src/entity_resolver.dart | 148 +++++++++++------- generator/test.sh | 2 +- lib/integration_test.dart | 4 +- lib/src/annotations.dart | 15 +- lib/src/modelinfo/modelentity.dart | 23 +-- lib/src/modelinfo/modelinfo.dart | 12 +- lib/src/modelinfo/modelproperty.dart | 46 ++++-- lib/src/util.dart | 6 - test/box_test.dart | 13 +- test/entity.dart | 14 +- test/objectbox-model.json | 66 ++++---- test/query_test.dart | 2 +- 19 files changed, 311 insertions(+), 189 deletions(-) create mode 100644 generator/integration-tests/indexes/.gitignore create mode 100644 generator/integration-tests/indexes/1.dart create mode 100644 generator/integration-tests/indexes/lib/lib.dart create mode 120000 generator/integration-tests/indexes/pubspec.yaml diff --git a/example/flutter/objectbox_demo/test_driver/app.dart b/example/flutter/objectbox_demo/test_driver/app.dart index f4181aa4b..ace076df3 100644 --- a/example/flutter/objectbox_demo/test_driver/app.dart +++ b/example/flutter/objectbox_demo/test_driver/app.dart @@ -8,4 +8,4 @@ void main() { // Call the `main()` function of the app, or call `runApp` with // any widget you are interested in testing. app.main(); -} \ No newline at end of file +} diff --git a/generator/integration-tests/common.dart b/generator/integration-tests/common.dart index 8509e1bab..99940d4c7 100644 --- a/generator/integration-tests/common.dart +++ b/generator/integration-tests/common.dart @@ -73,3 +73,10 @@ commonModelTests(ModelDefinition defs, ModelInfo jsonModel) { ModelEntity entity(ModelInfo model, String name) { return model.entities.firstWhere((ModelEntity e) => e.name == name); } + +ModelProperty property(ModelInfo model, String path) { + final components = path.split('.'); + return entity(model, components[0]) + .properties + .firstWhere((ModelProperty p) => p.name == components[1]); +} diff --git a/generator/integration-tests/indexes/.gitignore b/generator/integration-tests/indexes/.gitignore new file mode 100644 index 000000000..d5ba0726f --- /dev/null +++ b/generator/integration-tests/indexes/.gitignore @@ -0,0 +1,2 @@ +# start with an empty project, without a objectbox-model.json +objectbox-model.json \ No newline at end of file diff --git a/generator/integration-tests/indexes/1.dart b/generator/integration-tests/indexes/1.dart new file mode 100644 index 000000000..fe84b5a31 --- /dev/null +++ b/generator/integration-tests/indexes/1.dart @@ -0,0 +1,47 @@ +import 'dart:io'; +import 'package:objectbox/objectbox.dart'; + +import 'lib/lib.dart'; +import 'lib/objectbox.g.dart'; +import 'package:test/test.dart'; +import '../test_env.dart'; +import '../common.dart'; +import 'package:objectbox/src/bindings/bindings.dart'; + +void main() { + TestEnv env; + final jsonModel = readModelJson('lib'); + final defs = getObjectBoxModel(); + + setUp(() { + env = TestEnv(defs); + }); + + tearDown(() { + env.close(); + }); + + commonModelTests(defs, jsonModel); + + test('project must be generated properly', () { + expect(TestEnv.dir.existsSync(), true); + expect(File('lib/objectbox.g.dart').existsSync(), true); + expect(File('lib/objectbox-model.json').existsSync(), true); + }); + + test('property flags', () { + expect(property(jsonModel, 'A.id').flags, equals(OBXPropertyFlags.ID)); + expect(property(jsonModel, 'A.indexed').flags, + equals(OBXPropertyFlags.INDEXED)); + expect(property(jsonModel, 'A.unique').flags, + equals(OBXPropertyFlags.INDEX_HASH | OBXPropertyFlags.UNIQUE)); + expect(property(jsonModel, 'A.uniqueValue').flags, + equals(OBXPropertyFlags.INDEXED | OBXPropertyFlags.UNIQUE)); + expect(property(jsonModel, 'A.uniqueHash').flags, + equals(OBXPropertyFlags.INDEX_HASH | OBXPropertyFlags.UNIQUE)); + expect(property(jsonModel, 'A.uniqueHash64').flags, + equals(OBXPropertyFlags.INDEX_HASH64 | OBXPropertyFlags.UNIQUE)); + expect(property(jsonModel, 'A.uid').flags, + equals(OBXPropertyFlags.INDEXED | OBXPropertyFlags.UNIQUE)); + }); +} diff --git a/generator/integration-tests/indexes/lib/lib.dart b/generator/integration-tests/indexes/lib/lib.dart new file mode 100644 index 000000000..1a78591ba --- /dev/null +++ b/generator/integration-tests/indexes/lib/lib.dart @@ -0,0 +1,31 @@ +import 'package:objectbox/objectbox.dart'; +import 'objectbox.g.dart'; + +@Entity() +class A { + @Id() + int id; + + @Index() + int indexed; + + @Unique() + String unique; + + @Unique() + @Index(type: IndexType.value) + String uniqueValue; + + @Unique() + @Index(type: IndexType.hash) + String uniqueHash; + + @Unique() + @Index(type: IndexType.hash64) + String uniqueHash64; + + @Unique() + int uid; + + A(); +} diff --git a/generator/integration-tests/indexes/pubspec.yaml b/generator/integration-tests/indexes/pubspec.yaml new file mode 120000 index 000000000..8d669e1bf --- /dev/null +++ b/generator/integration-tests/indexes/pubspec.yaml @@ -0,0 +1 @@ +../shared-pubspec.yaml \ No newline at end of file diff --git a/generator/lib/src/code_builder.dart b/generator/lib/src/code_builder.dart index 09958c8ae..3e0add71b 100644 --- a/generator/lib/src/code_builder.dart +++ b/generator/lib/src/code_builder.dart @@ -130,20 +130,21 @@ class CodeBuilder extends Builder { } void mergeProperty(ModelEntity entity, ModelProperty prop) { - final propInModel = entity.findSameProperty(prop); + var propInModel = entity.findSameProperty(prop); + if (propInModel == null) { log.info('Found new property ${entity.name}.${prop.name}'); - entity.addProperty(prop); + propInModel = entity.createProperty(prop.name, prop.id.uid); + } + + propInModel.name = prop.name; + propInModel.type = prop.type; + propInModel.flags = prop.flags; + + if (!prop.hasIndexFlag()) { + propInModel.removeIndex(); } else { - if (propInModel.name != prop.name) { - log.warning('The name of the property(${prop.name}) changed.'); - } - if (propInModel.flags != prop.flags) { - log.warning('The flags of the property(${prop.name}) changed.'); - } - if (propInModel.type != prop.type) { - log.warning('The type of the property(${prop.name}) changed.'); - } + propInModel.indexId ??= entity.model.createIndexId(); } } @@ -155,26 +156,26 @@ class CodeBuilder extends Builder { if (entityInModel == null) { log.info('Found new entity ${entity.name}'); // in case the entity is created (i.e. when its given UID or name that does not yet exist), we are done, as nothing needs to be merged - entityInModel = modelInfo.addEntity(entity); - } else { - entityInModel.name = entity.name; - entityInModel.flags = entity.flags; - - // here, the entity was found already and entityInModel and readEntity might differ, i.e. conflicts need to be resolved, so merge all properties first - entity.properties.forEach((p) => mergeProperty(entityInModel, p)); - - // then remove all properties not present anymore in readEntity - final missingProps = entityInModel.properties - .where((p) => entity.findSameProperty(p) == null) - .toList(growable: false); - - missingProps.forEach((p) { - log.warning( - 'Property ${entity.name}.${p.name}(${p.id.toString()}) not found in the code, removing from the model'); - entityInModel.removeProperty(p); - }); + entityInModel = modelInfo.createEntity(entity.name, entity.id.uid); } + entityInModel.name = entity.name; + entityInModel.flags = entity.flags; + + // here, the entity was found already and entityInModel and readEntity might differ, i.e. conflicts need to be resolved, so merge all properties first + entity.properties.forEach((p) => mergeProperty(entityInModel, p)); + + // then remove all properties not present anymore in readEntity + final missingProps = entityInModel.properties + .where((p) => entity.findSameProperty(p) == null) + .toList(growable: false); + + missingProps.forEach((p) { + log.warning( + 'Property ${entity.name}.${p.name}(${p.id.toString()}) not found in the code, removing from the model'); + entityInModel.removeProperty(p); + }); + return entityInModel.id; } } diff --git a/generator/lib/src/entity_resolver.dart b/generator/lib/src/entity_resolver.dart index 81a9425ca..5fa809595 100644 --- a/generator/lib/src/entity_resolver.dart +++ b/generator/lib/src/entity_resolver.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:convert'; import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/dart/element/type.dart'; import 'package:build/build.dart'; import 'package:objectbox/objectbox.dart' as obx; import 'package:objectbox/src/bindings/bindings.dart'; @@ -91,7 +92,7 @@ class EntityResolver extends Builder { } int fieldType, flags = 0; - int propUid, indexUid; + int propUid; if (_idChecker.hasAnnotationOfExact(f)) { if (hasIdProperty) { @@ -141,72 +142,19 @@ class EntityResolver extends Builder { } } - // Index and unique index. - final indexAnnotation = _indexChecker.firstAnnotationOfExact(f); - final hasUniqueAnnotation = _uniqueChecker.hasAnnotationOfExact(f); - if (indexAnnotation != null || hasUniqueAnnotation) { - // Throw if property type does not support any index. - if (fieldType == OBXPropertyType.Float || fieldType == OBXPropertyType.Double || fieldType == OBXPropertyType.ByteVector) { - throw InvalidGenerationSourceError( - "in target ${elementBare.name}: @Index/@Unique is not supported for type '${f.type.toString()}' of field '${f.name}'"); - } - - // TODO Throw if index used on Id property. - - - // If available use index type from annotation. - var indexFlag; - if (indexAnnotation != null) { - indexFlag = indexAnnotation.getField('flag').toIntValue(); - if (indexFlag != null && - (indexFlag != OBXPropertyFlag.INDEXED - || indexFlag != OBXPropertyFlag.INDEX_HASH - || indexFlag != OBXPropertyFlag.INDEX_HASH64)) { - throw InvalidGenerationSourceError( - 'in target ${elementBare.name}: @Index flag must be one of [OBXPropertyFlag.INDEXED, OBXPropertyFlag.INDEX_HASH, OBXPropertyFlag.INDEX_HASH64] or none for auto-detection'); - } - } - - // Fall back to index type based on property type. - final supportsHashIndex = fieldType == OBXPropertyType.String; - if (indexFlag == null) { - if (supportsHashIndex) { - indexFlag = OBXPropertyFlag.INDEX_HASH; - } else { - indexFlag = OBXPropertyFlag.INDEXED; - } - } - - // Throw if HASH or HASH64 is not supported by property type. - if (!supportsHashIndex && - (indexFlag == OBXPropertyFlag.INDEX_HASH || - indexFlag == OBXPropertyFlag.INDEX_HASH64)) { - throw InvalidGenerationSourceError( - "in target ${elementBare.name}: a hash index is not supported for type '${f.type.toString()}' of field '${f.name}'"); - } - - flags |= indexFlag; - if (hasUniqueAnnotation) { - flags |= OBXPropertyFlag.UNIQUE; - } - } - // create property (do not use readEntity.createProperty in order to avoid generating new ids) - final prop = - ModelProperty(IdUid.empty(), f.name, fieldType, flags, entity); + final prop = ModelProperty(IdUid.empty(), f.name, fieldType, + flags: flags, entity: entity); - // TODO test on @Property(...) that uses the proper flags for index - final isIndexer = flags.isIndexer; - - if (isIndexer) { - prop.indexId = indexUid == null ? IdUid.empty() : IdUid(0, indexUid); - } + // Index and unique annotation. + final indexTypeStr = + processAnnotationIndexUnique(f, fieldType, elementBare, prop); if (propUid != null) prop.id.uid = propUid; entity.properties.add(prop); log.info( - ' ${isIndexer ? "index " : ""}property ${prop.name}(${prop.id}) type:${prop.type} flags:${prop.flags}'); + ' property ${prop.name}(${prop.id}) type:${prop.type} flags:${prop.flags} ${prop.hasIndexFlag() ? "index:${indexTypeStr}" : ""}'); } // some checks on the entity's integrity @@ -217,4 +165,84 @@ class EntityResolver extends Builder { return entity; } + + String processAnnotationIndexUnique(FieldElement f, int fieldType, + Element elementBare, obx.ModelProperty prop) { + obx.IndexType indexType; + + final indexAnnotation = _indexChecker.firstAnnotationOfExact(f); + final hasUniqueAnnotation = _uniqueChecker.hasAnnotationOfExact(f); + if (indexAnnotation == null && !hasUniqueAnnotation) return null; + + // Throw if property type does not support any index. + if (fieldType == OBXPropertyType.Float || + fieldType == OBXPropertyType.Double || + fieldType == OBXPropertyType.ByteVector) { + throw InvalidGenerationSourceError( + "in target ${elementBare.name}: @Index/@Unique is not supported for type '${f.type.toString()}' of field '${f.name}'"); + } + + if (prop.hasFlag(OBXPropertyFlags.ID)) { + throw InvalidGenerationSourceError( + 'in target ${elementBare.name}: @Index/@Unique is not supported for ID field ${f.name}. IDs are unique by definition and automatically indexed'); + } + + // If available use index type from annotation. + if (indexAnnotation != null && !indexAnnotation.isNull) { + // find out @Index(type:) value - its an enum IndexType + final indexTypeField = indexAnnotation.getField('type'); + if (!indexTypeField.isNull) { + final indexTypeEnumValues = (indexTypeField.type as InterfaceType) + .element + .fields + .where((f) => f.isEnumConstant) + .toList(); + + // Find the index of the matching enum constant. + for (var i = 0; i < indexTypeEnumValues.length; i++) { + if (indexTypeEnumValues[i].computeConstantValue() == indexTypeField) { + indexType = obx.IndexType.values[i]; + break; + } + } + } + } + + // Fall back to index type based on property type. + final supportsHashIndex = fieldType == OBXPropertyType.String; + if (indexType == null) { + if (supportsHashIndex) { + indexType = obx.IndexType.hash; + } else { + indexType = obx.IndexType.value; + } + } + + // Throw if HASH or HASH64 is not supported by property type. + if (!supportsHashIndex && + (indexType == obx.IndexType.hash || + indexType == obx.IndexType.hash64)) { + throw InvalidGenerationSourceError( + "in target ${elementBare.name}: a hash index is not supported for type '${f.type.toString()}' of field '${f.name}'"); + } + + if (hasUniqueAnnotation) { + prop.flags |= OBXPropertyFlags.UNIQUE; + } + + switch (indexType) { + case obx.IndexType.value: + prop.flags |= OBXPropertyFlags.INDEXED; + return 'value'; + case obx.IndexType.hash: + prop.flags |= OBXPropertyFlags.INDEX_HASH; + return 'hash'; + case obx.IndexType.hash64: + prop.flags |= OBXPropertyFlags.INDEX_HASH64; + return 'hash64'; + default: + throw InvalidGenerationSourceError( + 'in target ${elementBare.name}: invalid index type: $indexType'); + } + } } diff --git a/generator/test.sh b/generator/test.sh index 2be995c13..0e6a002a1 100755 --- a/generator/test.sh +++ b/generator/test.sh @@ -15,7 +15,7 @@ function runTestFile() { # build before each step, except for "0.dart" if [ "${1}" != "0" ]; then echo "Running build_runner before ${file}" - dart pub run build_runner build + dart pub run build_runner build --verbose fi echo "Running ${file}" dart test "${file}" diff --git a/lib/integration_test.dart b/lib/integration_test.dart index 2c435ac75..af38d0287 100644 --- a/lib/integration_test.dart +++ b/lib/integration_test.dart @@ -18,8 +18,8 @@ class IntegrationTest { static void model() { // create a model with a single entity and a single property final modelInfo = ModelInfo(); - final property = ModelProperty( - IdUid(1, int64_max - 1), 'id', OBXPropertyType.Long, 0, null); + final property = + ModelProperty(IdUid(1, int64_max - 1), 'id', OBXPropertyType.Long); final entity = ModelEntity(IdUid(1, int64_max), 'entity', modelInfo); property.entity = entity; entity.properties.add(property); diff --git a/lib/src/annotations.dart b/lib/src/annotations.dart index 4f27f3e78..92c716a24 100644 --- a/lib/src/annotations.dart +++ b/lib/src/annotations.dart @@ -34,9 +34,6 @@ class Transient { // const Sync(); // } -// TODO It's possible to pass the unique and index flags directly through Property, -// was it even intended to have a separate annotation for these? - /// Specifies that the property should be indexed. /// /// It is highly recommended to index properties that are used in a Query to @@ -46,8 +43,14 @@ class Transient { /// Note: indexes are currently not supported for ByteVector, Float or Double /// properties. class Index { - final int flag; - const Index({this.flag}); + final IndexType /*?*/ type; + const Index({this.type}); +} + +enum IndexType { + value, + hash, + hash64, } /// Enforces that the value of a property is unique among all Objects in a Box @@ -57,7 +60,7 @@ class Index { /// /// Unique properties are based on an [Index], so the same restrictions apply. /// It is supported to explicitly add the [Index] annotation to configure the -/// index. +/// index type. class Unique { const Unique(); } diff --git a/lib/src/modelinfo/modelentity.dart b/lib/src/modelinfo/modelentity.dart index d2fa07fc5..d98b6040a 100644 --- a/lib/src/modelinfo/modelentity.dart +++ b/lib/src/modelinfo/modelentity.dart @@ -142,32 +142,13 @@ class ModelEntity { } final uniqueUid = uid == 0 ? model.generateUid() : uid; - final property = ModelProperty(IdUid(id, uniqueUid), name, 0, 0, this); + final property = ModelProperty(IdUid(id, uniqueUid), name, 0, entity: this); properties.add(property); lastPropertyId = property.id; return property; } - ModelProperty addProperty(ModelProperty prop) { - final modelProp = createProperty(prop.name, prop.id.uid); - modelProp.type = prop.type; - modelProp.flags = prop.flags; - - if (flags.isIndexer) { - final indexUid = prop.indexId.uid; - if (indexUid != 0 && model.containsUid(indexUid)) { - throw Exception('index uid already exists: $uid'); - } - modelProp.indexId = IdUid(_model.lastIndexId.id + 1, - indexUid == 0 ? model.generateUid() : indexUid); - - _model.lastIndexId = modelProp.indexId; - } - - return modelProp; - } - void removeProperty(ModelProperty prop) { final foundProp = findSameProperty(prop); if (foundProp == null) { @@ -177,7 +158,7 @@ class ModelEntity { _properties.remove(foundProp); model.retiredPropertyUids.add(prop.id.uid); - if (prop.flags.isIndexer) { + if (prop.indexId != null) { model.retiredIndexUids.add(prop.indexId.uid); } } diff --git a/lib/src/modelinfo/modelinfo.dart b/lib/src/modelinfo/modelinfo.dart index 657c596e0..d198d859d 100644 --- a/lib/src/modelinfo/modelinfo.dart +++ b/lib/src/modelinfo/modelinfo.dart @@ -152,12 +152,6 @@ class ModelInfo { return ret; } - ModelEntity addEntity(ModelEntity other) { - final modelEntity = createEntity(other.name, other.id.uid); - other.properties.forEach((p) => modelEntity.addProperty(p)); - return modelEntity; - } - ModelEntity createEntity(String name, [int uid = 0]) { var id = 1; if (entities.isNotEmpty) id = lastEntityId.id + 1; @@ -208,4 +202,10 @@ class ModelInfo { if (listContains(retiredRelationUids, uid)) return true; return false; } + + IdUid createIndexId() { + var id = lastIndexId.isEmpty ? 1 : lastIndexId.id + 1; + lastIndexId = IdUid(id, generateUid()); + return lastIndexId; + } } diff --git a/lib/src/modelinfo/modelproperty.dart b/lib/src/modelinfo/modelproperty.dart index 86f1ac2bc..32cf5312c 100644 --- a/lib/src/modelinfo/modelproperty.dart +++ b/lib/src/modelinfo/modelproperty.dart @@ -1,15 +1,13 @@ +import 'package:objectbox/src/bindings/bindings.dart'; + import 'modelentity.dart'; import 'iduid.dart'; /// ModelProperty describes a single property of an entity, i.e. its id, name, type and flags. class ModelProperty { IdUid id; - - /*late*/ - String _name; - - /*late*/ - int _type, _flags; + /*late*/ String _name; + /*late*/ int _type, _flags; IdUid /*?*/ _indexId; ModelEntity /*?*/ entity; @@ -40,17 +38,30 @@ class ModelProperty { _flags = value /*!*/; } - ModelProperty(this.id, String /*?*/ name, int /*?*/ type, int /*?*/ flags, - String indexId, this.entity) { + IdUid /*?*/ get indexId => _indexId; + + set indexId(IdUid /*?*/ value) { + if (value != null) { + if (value.id == 0 || value.uid == 0) { + throw Exception('indexId must contain valid ID & UID'); + } + } + _indexId = value /*!*/; + } + + ModelProperty(this.id, String /*?*/ name, int /*?*/ type, + {int flags = 0, String /*?*/ indexId, this.entity}) { this.name = name; this.type = type; this.flags = flags; - if (indexId != null) this._indexId = IdUid.fromString(indexId); + this.indexId = indexId == null ? null : IdUid.fromString(indexId); } ModelProperty.fromMap(Map data, ModelEntity /*?*/ entity) : this(IdUid.fromString(data['id']), data['name'], data['type'], - data['flags'] ?? 0, data['indexId'], entity); + flags: data['flags'] ?? 0, + indexId: data['indexId'], + entity: entity); Map toMap() { final ret = {}; @@ -58,7 +69,7 @@ class ModelProperty { ret['name'] = name; ret['type'] = type; if (flags != 0) ret['flags'] = flags; - if (_indexId != null) ret['indexId'] = _indexId /*!*/ .toString(); + if (indexId != null) ret['indexId'] = indexId /*!*/ .toString(); return ret; } @@ -69,4 +80,17 @@ class ModelProperty { bool hasFlag(int flag) { return (flags & flag) == flag; } + + bool hasIndexFlag() { + return hasFlag(OBXPropertyFlags.INDEXED) || + hasFlag(OBXPropertyFlags.INDEX_HASH) || + hasFlag(OBXPropertyFlags.INDEX_HASH64); + } + + void removeIndex() { + if (_indexId != null) { + entity.model.retiredIndexUids.add(_indexId.uid); + indexId = null; + } + } } diff --git a/lib/src/util.dart b/lib/src/util.dart index b30b136d9..0140ad68c 100644 --- a/lib/src/util.dart +++ b/lib/src/util.dart @@ -62,9 +62,3 @@ class SyncOrObserversExclusive { } final syncOrObserversExclusive = SyncOrObserversExclusive(); - -extension Indexer on int { - bool get isIndexer => - this & OBXPropertyFlag.INDEXED == 8 || - this & OBXPropertyFlag.UNIQUE == 32; -} diff --git a/test/box_test.dart b/test/box_test.dart index 060bddabb..c3f10bdf5 100644 --- a/test/box_test.dart +++ b/test/box_test.dart @@ -59,12 +59,15 @@ void main() { }); test('.put() cannot add duplicate values on a unique field', () { - final u1 = TestEntity.unique(uString: 'a', uLong: 1, uInt: 1, - uShort: 1, uByte: 1, uChar: 1); - final again = TestEntity.unique(uString: 'a', uLong: 1, uInt: 1, - uShort: 1, uByte: 1, uChar: 1); + final u1 = TestEntity.unique( + uString: 'a', uLong: 1, uInt: 1, uShort: 1, uByte: 1, uChar: 1); + final again = TestEntity.unique( + uString: 'a', uLong: 1, uInt: 1, uShort: 1, uByte: 1, uChar: 1); - expect(box.putMany([u1, again]), throwsException); + expect( + () => box.putMany([u1, again]), + throwsA(predicate((ObjectBoxException e) => + e.toString().contains('same property value already exists')))); }); test('.getAll retrieves all items', () { diff --git a/test/entity.dart b/test/entity.dart index 1837f5466..2086191b4 100644 --- a/test/entity.dart +++ b/test/entity.dart @@ -66,15 +66,15 @@ class TestEntity { disregard = 1; } - @Property(type:OBXPropertyType.Byte) + @Property(type: OBXPropertyType.Byte) @Unique() int uByte; - @Property(type:OBXPropertyType.Short) + @Property(type: OBXPropertyType.Short) @Unique() int uShort; - @Property(type:OBXPropertyType.Char) + @Property(type: OBXPropertyType.Char) @Unique() int uChar; @@ -98,19 +98,19 @@ class TestEntity { this.uChar, }); - @Property(type:OBXPropertyType.Byte) + @Property(type: OBXPropertyType.Byte) @Index() int iByte; - @Property(type:OBXPropertyType.Short) + @Property(type: OBXPropertyType.Short) @Index() int iShort; - @Property(type:OBXPropertyType.Char) + @Property(type: OBXPropertyType.Char) @Index() int iChar; - @Property(type:OBXPropertyType.Int) + @Property(type: OBXPropertyType.Int) @Index() int iInt; diff --git a/test/objectbox-model.json b/test/objectbox-model.json index e0483fef6..2ad506fce 100644 --- a/test/objectbox-model.json +++ b/test/objectbox-model.json @@ -5,7 +5,7 @@ "entities": [ { "id": "1:4630700155272683157", - "lastPropertyId": "26:8702110868915772966", + "lastPropertyId": "26:3059114515142777989", "name": "TestEntity", "flags": 2, "properties": [ @@ -61,88 +61,88 @@ "type": 7 }, { - "id": "15:6996367526195150585", + "id": "15:5960097736918862689", "name": "uByte", "type": 2, - "flags": 32, - "indexId": "1:8383955520317513621" + "flags": 40, + "indexId": "1:759265819613755228" }, { - "id": "16:2715615992053992193", + "id": "16:3014870337091941693", "name": "uShort", "type": 3, - "flags": 32, - "indexId": "2:1444586949459820158" + "flags": 40, + "indexId": "2:3731424852781354288" }, { - "id": "17:2726230190322219382", + "id": "17:3004362051550241897", "name": "uChar", "type": 4, - "flags": 32, - "indexId": "3:4661641768447390575" + "flags": 40, + "indexId": "3:1180301620695216647" }, { - "id": "18:8604306476073958418", + "id": "18:1083874867412561588", "name": "uInt", "type": 5, - "flags": 32, - "indexId": "4:3169412207654167239" + "flags": 40, + "indexId": "4:9159391309201422019" }, { - "id": "19:3289140694453689258", + "id": "19:1849261948480372113", "name": "uString", "type": 9, - "flags": 32, - "indexId": "5:2652815973956236214" + "flags": 2080, + "indexId": "5:7090314366957370493" }, { - "id": "20:1896318477975978242", + "id": "20:1700905145011245158", "name": "uLong", "type": 6, - "flags": 32, - "indexId": "6:1114971721802793243" + "flags": 40, + "indexId": "6:9146355272521890356" }, { - "id": "21:4334508382176818281", + "id": "21:1256214340403371310", "name": "iByte", "type": 2, "flags": 8, - "indexId": "7:5119305361210421475" + "indexId": "7:2597915862881161952" }, { - "id": "22:8689251269948347853", + "id": "22:5768208836201983752", "name": "iShort", "type": 3, "flags": 8, - "indexId": "8:8230437552832657067" + "indexId": "8:5900854653402683567" }, { - "id": "23:9163528388059726221", + "id": "23:2550226115516189529", "name": "iChar", "type": 4, "flags": 8, - "indexId": "9:869568640260232176" + "indexId": "9:6832522351412488843" }, { - "id": "24:7584537398448845337", + "id": "24:8654682464442924778", "name": "iInt", "type": 5, "flags": 8, - "indexId": "10:1487602683978225990" + "indexId": "10:7301436684067319059" }, { - "id": "25:2091567837655054550", + "id": "25:6667369331692110338", "name": "iString", "type": 9, - "flags": 8, - "indexId": "11:8051787750326117391" + "flags": 2048, + "indexId": "11:4395163045094957195" }, { - "id": "26:8702110868915772966", + "id": "26:3059114515142777989", "name": "iLong", "type": 6, "flags": 8, - "indexId": "12:1395621548785108735" + "indexId": "12:5335419474465474748" } ] }, @@ -161,7 +161,7 @@ } ], "lastEntityId": "3:3569200127393812728", - "lastIndexId": "12:1395621548785108735", + "lastIndexId": "12:5335419474465474748", "lastRelationId": "0:0", "lastSequenceId": "0:0", "modelVersion": 5, diff --git a/test/query_test.dart b/test/query_test.dart index 843029999..e4c92bead 100644 --- a/test/query_test.dart +++ b/test/query_test.dart @@ -295,7 +295,7 @@ void main() { final remaining = box.getAll(); expect(remaining.length, 2); - expect(remaining.map((e) => e.id), equals([1,4])); + expect(remaining.map((e) => e.id), equals([1, 4])); }); test('.count items after grouping with and/or', () {