diff --git a/generator/integration-tests/common.dart b/generator/integration-tests/common.dart index 7585f6590..848d5db31 100644 --- a/generator/integration-tests/common.dart +++ b/generator/integration-tests/common.dart @@ -59,13 +59,18 @@ commonModelTests(ModelDefinition defs, ModelInfo jsonModel) { jsonModel.retiredEntityUids); }); - // TODO when indexes are available +// TODO when indexes are available +// TODO see one-liner in the next lastRelationId test // test('lastIndexId', () { // testLastId(defs.model.lastIndexId, defs.model.entities.map((el) => ...), jsonModel.retiredIndexUids); // }); - // TODO when relations are available -// test('lastRelationId', () { -// testLastId(defs.model.lastRelationId, defs.model.entities.map((el) => ...), jsonModel.retiredRelationUids); -// }); + test('lastRelationId', () { + testLastId( + defs.model.lastRelationId, + defs.model.entities + .map((e) => e.properties.where((p) => p.isRelation)) + .fold([], (a, b) => a + b).map((e) => e.id), + jsonModel.retiredRelationUids); + }); } diff --git a/generator/lib/src/code_builder.dart b/generator/lib/src/code_builder.dart index 607c688a7..9359aa235 100644 --- a/generator/lib/src/code_builder.dart +++ b/generator/lib/src/code_builder.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:io'; +import 'dart:math'; import 'dart:convert'; import 'package:build/build.dart'; import 'package:glob/glob.dart'; @@ -82,6 +83,7 @@ class CodeBuilder extends Builder { // merge existing model and annotated model that was just read, then write new final model to file merge(model, entities); + assignLastPropertyIds(model); model.validate(); // write model info @@ -93,6 +95,20 @@ class CodeBuilder extends Builder { return model; } + void assignLastPropertyIds(ModelInfo model) { + model.entities.forEach((e) { + e.properties.forEach((p) { + // if (p.isIndexer) { + // model.lastIndexId = p.id; }else + if (p.isToOne) { + model.lastIndexId = p.relIndexId; + } else if (p.isToMany) { + model.lastRelationId = p.relationId; + } + }); + }); + } + void updateCode( ModelInfo model, List infoFiles, BuildStep buildStep) async { // transform '/lib/path/entity.objectbox.info' to 'path/entity.dart' @@ -131,6 +147,17 @@ class CodeBuilder extends Builder { entities.forEach((entity) => mergeEntity(model, entity)); } + int generateUid(int seed, Set prohibited) { + final rng = Random(seed + DateTime.now().millisecondsSinceEpoch); + for (int i = 0; i < 1000; i++) { + var uid = rng.nextInt(1 << 32); + uid |= rng.nextInt(1 << 32) << 32; + uid &= ~(1 << 63); + if (!prohibited.contains(uid)) return uid; + } + return 0; + } + void mergeProperty(ModelEntity entity, ModelProperty prop) { final propInModel = entity.findSameProperty(prop); if (propInModel == null) { @@ -140,6 +167,10 @@ class CodeBuilder extends Builder { propInModel.name = prop.name; propInModel.type = prop.type; propInModel.flags = prop.flags; + if (prop.type.isRelation) { + propInModel.targetEntityName = prop.targetEntityName; + propInModel.relIndexId = prop.relIndexId; + } } } @@ -160,6 +191,50 @@ class CodeBuilder extends Builder { // 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)); + // one to one relation: get the highest relIndexId.id + // TODO Ask: Do we want to repair contiguous ids, if one prop is removed in the middle? + var relIndexIdCounter = entityInModel.properties + .where((p) => p.isToOne && p.relIndexId != null) + .map((p) => p.relIndexId.id) + .fold(1, (a, b) => a < b ? b : a); + + // Prohibit reuse of indexUids + final avoidSameUidSet = Set()..addAll(modelInfo.retiredIndexUids); + + // one to one relation + entityInModel.properties.where((p) => p.isToOne).forEach((p) { + if (p.relIndexId == null) { + p.relIndexId = IdUid.empty(); + } + if (p.relIndexId.isEmpty) { + p.relIndexId.id = relIndexIdCounter++; + p.relIndexId.uid = generateUid(entityInModel.id.uid, avoidSameUidSet); + avoidSameUidSet.add(p.relIndexId.uid); + } + }); + + // many to many relation + var relationIdCounter = entityInModel.properties + .where((p) => p.isToMany && p.relationId != null) + .map((p) => p.relationId.id) + .fold(1, (a, b) => a < b ? b : a); + + final targetEntityMap = {}; + final mapEntityNameToId = + modelInfo.entities.forEach((e) => targetEntityMap[e.name] = e.id); + entityInModel.properties.where((p) => p.isToMany).forEach((p) { + if (p.relationId == null) { + p.relationId = IdUid.empty(); + } + if (p.relationId.isEmpty) { + p.relationId.id = relationIdCounter++; + p.relationId.uid = generateUid(entityInModel.id.uid, avoidSameUidSet); + avoidSameUidSet.add(p.relationId.uid); + } + + p.targetEntityId = targetEntityMap[p.targetEntityName]; + }); + // then remove all properties not present anymore in readEntity entityInModel.properties .where((p) => entity.findSameProperty(p) == null) diff --git a/generator/lib/src/code_chunks.dart b/generator/lib/src/code_chunks.dart index ca208c779..293921cf2 100644 --- a/generator/lib/src/code_chunks.dart +++ b/generator/lib/src/code_chunks.dart @@ -57,6 +57,9 @@ class CodeChunks { case OBXPropertyType.String: fieldType = "String"; break; + case OBXPropertyType.Relation: + fieldType = 'Relation'; + break; float: case OBXPropertyType.Double: fieldType = "Double"; @@ -80,8 +83,11 @@ class CodeChunks { "Unsupported property type (${prop.type}): ${entity.name}.${name}"); } + final relationTypeGenericParam = + prop.type == OBXPropertyType.Relation ? '<${prop.targetEntityName}>' : ''; + ret.add(""" - static final ${name} = Query${fieldType}Property(entityId:${entity.id.id}, propertyId:${prop.id.id}, obxType:${prop.type}); + static final ${name} = Query${fieldType}Property$relationTypeGenericParam(entityId:${entity.id.id}, propertyId:${prop.id.id}, obxType:${prop.type}); """); } return ret.join(); diff --git a/generator/lib/src/entity_resolver.dart b/generator/lib/src/entity_resolver.dart index 7e866c0e6..fc0d5358a 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/constants.dart'; @@ -17,7 +18,7 @@ class EntityResolver extends Builder { '.dart': [suffix] }; - final _annotationChecker = const TypeChecker.fromRuntime(obx.Entity); + final _entityChecker = const TypeChecker.fromRuntime(obx.Entity); final _propertyChecker = const TypeChecker.fromRuntime(obx.Property); final _idChecker = const TypeChecker.fromRuntime(obx.Id); final _transientChecker = const TypeChecker.fromRuntime(obx.Transient); @@ -30,9 +31,13 @@ class EntityResolver extends Builder { // generate for all entities final entities = >[]; - for (var annotatedEl in libReader.annotatedWith(_annotationChecker)) { - entities.add(generateForAnnotatedElement( - annotatedEl.element, annotatedEl.annotation) + final annotatedWithEntity = libReader.annotatedWith(_entityChecker); + final entityNames = annotatedWithEntity.map((a) => a.element.name).toSet(); + final entityNamesAsList = + annotatedWithEntity.map((a) => 'List<${a.element.name}>').toSet(); + for (var annotatedEl in annotatedWithEntity) { + entities.add(generateForAnnotatedElement(annotatedEl.element, + annotatedEl.annotation, entityNames, entityNamesAsList) .toMap()); } @@ -44,7 +49,10 @@ class EntityResolver extends Builder { } ModelEntity generateForAnnotatedElement( - Element elementBare, ConstantReader annotation) { + Element elementBare, + ConstantReader annotation, + Set relatableEntityNames, + Set relatableEntityNamesAsList) { if (elementBare is! ClassElement) { throw InvalidGenerationSourceError( "in target ${elementBare.name}: annotated element isn't a class"); @@ -70,6 +78,7 @@ class EntityResolver extends Builder { int fieldType, flags = 0; int propUid; + final dartTypeString = f.type.toString().replaceAll('*', ''); if (_idChecker.hasAnnotationOfExact(f)) { if (hasIdProperty) { @@ -96,7 +105,7 @@ class EntityResolver extends Builder { } if (fieldType == null) { - var fieldTypeDart = f.type; + final fieldTypeDart = f.type; if (fieldTypeDart.isDartCoreInt) { // dart: 8 bytes @@ -112,6 +121,9 @@ class EntityResolver extends Builder { // dart: 8 bytes // ob: 8 bytes fieldType = OBXPropertyType.Double; + } else if (areRelated( + dartTypeString, relatableEntityNames, relatableEntityNamesAsList)) { + fieldType = OBXPropertyType.Relation; } else { log.warning( " skipping property '${f.name}' in entity '${element.name}', as it has the unsupported type '${fieldTypeDart.toString()}'"); @@ -120,8 +132,20 @@ 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); + final isRelation = fieldType == OBXPropertyType.Relation; + + // setup relations + final isToOne = + isRelation && areRelated(dartTypeString, relatableEntityNames); + final isToMany = + isRelation && areRelated(dartTypeString, relatableEntityNamesAsList); + final prop = ModelProperty( + IdUid.empty(), f.name, fieldType, flags, readEntity, + targetEntityName: isRelation ? dartTypeString : null, + relIndexId: isToOne ? IdUid.empty() : null, + relationId: isToMany ? IdUid.empty() : null, + targetEntityId: isToMany ? IdUid.empty() : null); + if (propUid != null) prop.id.uid = propUid; readEntity.properties.add(prop); @@ -137,4 +161,7 @@ class EntityResolver extends Builder { return readEntity; } + + bool areRelated(String typeString, Set s1, [Set s2]) => + s1.contains(typeString) || (s2 != null && s2.contains(typeString)); } diff --git a/generator/pubspec.yaml b/generator/pubspec.yaml index ee1b2949f..14c0e8308 100644 --- a/generator/pubspec.yaml +++ b/generator/pubspec.yaml @@ -5,7 +5,7 @@ homepage: https://objectbox.io description: ObjectBox binding code generator - finds annotated entities and adds them to the ObjectBox DB model. environment: - sdk: ">=2.5.0 <3.0.0" + sdk: ">=2.6.0 <3.0.0" dependencies: objectbox: 0.8.0 diff --git a/lib/objectbox.dart b/lib/objectbox.dart index e3b693bcf..b66ae27f7 100644 --- a/lib/objectbox.dart +++ b/lib/objectbox.dart @@ -12,3 +12,4 @@ export 'src/model.dart'; export 'src/modelinfo/index.dart'; export 'src/query/query.dart'; export 'src/store.dart'; +export 'src/util.dart'; diff --git a/lib/src/bindings/bindings.dart b/lib/src/bindings/bindings.dart index 16d852586..1dacf6bab 100644 --- a/lib/src/bindings/bindings.dart +++ b/lib/src/bindings/bindings.dart @@ -46,10 +46,20 @@ class _ObjectBoxBindings { int Function(Pointer model, Pointer name, int type, int property_id, int property_uid) obx_model_property; int Function(Pointer model, int flags) obx_model_property_flags; + int Function(Pointer model, int index_id, int index_uid) + obx_model_property_index_id; 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; + int Function(Pointer model, Pointer target_entity, int entity_id, + int entity_uid) obx_model_property_relation; + int Function(Pointer model, int relation_id, int relation_uid, + int target_id, int target_uid) obx_model_relation; + void Function(Pointer model, int relation_id, int relation_uid) + obx_model_last_relation_id; // object store management Pointer Function() obx_opt; @@ -61,6 +71,7 @@ class _ObjectBoxBindings { void Function(Pointer opt) obx_opt_free; Pointer Function(Pointer opt) obx_store_open; int Function(Pointer store) obx_store_close; + int Function(Pointer store, int flags) obx_store_debug_flags; // transactions Pointer Function(Pointer store) obx_txn_write; @@ -109,6 +120,13 @@ class _ObjectBoxBindings { obx_box_count; int Function(Pointer box, Pointer is_empty) obx_box_is_empty; + // box relations + int Function(Pointer box, int relation_id, int source_id, int target_id) + obx_box_rel_put, obx_box_rel_remove; + + Pointer Function(Pointer box, int relation_id, int id) + obx_box_rel_get_ids, obx_box_rel_get_backlink_ids; + // query builder obx_query_builder_dart_t obx_qb_create; obx_qb_close_dart_t obx_qb_close; @@ -155,6 +173,18 @@ class _ObjectBoxBindings { obx_qb_order_dart_t obx_qb_order; + Pointer Function(Pointer builder, int property_id) + obx_qb_link_property; + Pointer Function( + Pointer builder, int source_entity_id, int source_property_id) + obx_qb_backlink_property; + Pointer Function(Pointer builder, int relation_id) + obx_qb_link_standalone; + Pointer Function(Pointer builder, int relation_id) + obx_qb_backlink_standalone; + Pointer Function(Pointer builder, int linked_entity_id, + int begin_property_id, int end_property_id) obx_qb_link_time; + // query obx_query_t obx_query_create; obx_query_close_dart_t obx_query_close; @@ -299,6 +329,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(); @@ -309,6 +342,17 @@ class _ObjectBoxBindings { obx_model_last_entity_id = _fn('obx_model_last_entity_id') .asFunction(); + obx_model_last_index_id = + _fn('obx_model_last_index_id') + .asFunction(); + obx_model_relation = + _fn('obx_model_relation').asFunction(); + obx_model_property_relation = + _fn('obx_model_property_relation') + .asFunction(); + obx_model_last_relation_id = + _fn('obx_model_last_relation_id') + .asFunction(); // object store management obx_opt = _fn('obx_opt').asFunction(); @@ -326,6 +370,9 @@ class _ObjectBoxBindings { _fn('obx_store_open').asFunction(); obx_store_close = _fn('obx_store_close').asFunction(); + obx_store_debug_flags = + _fn('obx_store_debug_flags') + .asFunction(); // transactions obx_txn_write = _fn('obx_txn_write').asFunction(); @@ -372,6 +419,17 @@ class _ObjectBoxBindings { obx_box_is_empty = _fn('obx_box_is_empty').asFunction(); + // box relations + obx_box_rel_put = + _fn('obx_box_rel_put').asFunction(); + obx_box_rel_remove = + _fn('obx_box_rel_remove').asFunction(); + obx_box_rel_get_ids = + _fn('obx_box_rel_get_ids').asFunction(); + obx_box_rel_get_backlink_ids = _fn( + 'obx_box_rel_get_backlink_ids') + .asFunction(); + // query builder obx_qb_create = _fn('obx_query_builder').asFunction(); @@ -487,6 +545,20 @@ class _ObjectBoxBindings { obx_qb_order = _fn('obx_qb_order').asFunction(); + obx_qb_link_property = + _fn('obx_qb_link_property').asFunction(); + obx_qb_backlink_property = + _fn('obx_qb_backlink_property') + .asFunction(); + obx_qb_link_standalone = + _fn('obx_qb_link_standalone') + .asFunction(); + obx_qb_backlink_standalone = + _fn('obx_qb_backlink_standalone') + .asFunction(); + obx_qb_link_time = + _fn('obx_qb_link_time').asFunction(); + // query obx_query_create = _fn('obx_query').asFunction(); obx_query_close = diff --git a/lib/src/bindings/constants.dart b/lib/src/bindings/constants.dart index 741f53ac1..0f292e2af 100644 --- a/lib/src/bindings/constants.dart +++ b/lib/src/bindings/constants.dart @@ -91,3 +91,11 @@ class OBXError { /// A requested schema object (e.g. entity or property) was not found in the schema static const int OBX_ERROR_SCHEMA_OBJECT_NOT_FOUND = 10503; } + +class OBXDebugFlags { + static const int LOG_TRANSACTIONS_READ = 1; + static const int LOG_TRANSACTIONS_WRITE = 2; + static const int LOG_QUERIES = 4; + static const int LOG_QUERY_PARAMETERS = 8; + static const int LOG_ASYNC_QUEUE = 16; +} diff --git a/lib/src/bindings/signatures.dart b/lib/src/bindings/signatures.dart index a0bbf70f8..a2f6786e8 100644 --- a/lib/src/bindings/signatures.dart +++ b/lib/src/bindings/signatures.dart @@ -35,12 +35,29 @@ 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); +typedef obx_model_property_relation_native_t = Int32 Function( + Pointer model, + Pointer target_entity, + Uint32 entity_id, + Uint64 entity_uid); +typedef obx_model_relation_native_t = Int32 Function( + Pointer model, + Uint32 relation_id, + Uint64 relation_uid, + Uint32 target_id, + Uint64 target_uid); +typedef obx_model_last_relation_id_native_t = Void Function( + Pointer model, Uint32 relation_id, Uint64 relation_uid); // object store management typedef obx_opt_native_t = Pointer Function(); @@ -56,6 +73,8 @@ typedef obx_opt_model_native_t = Int32 Function( Pointer opt, Pointer model); typedef obx_store_open_native_t = Pointer Function(Pointer opt); typedef obx_store_close_native_t = Int32 Function(Pointer store); +typedef obx_store_debug_flags_native_t = Int32 Function( + Pointer store, Int32 flags); // transactions typedef obx_txn_write_native_t = Pointer Function(Pointer store); @@ -106,6 +125,15 @@ typedef obx_box_count_native_t = Int32 Function( typedef obx_box_is_empty_native_t = Int32 Function( Pointer box, Pointer is_empty); +typedef obx_box_rel_put_native_t = Int32 Function( + Pointer box, Uint32 relation_id, Uint64 source_id, Uint64 target_id); +typedef obx_box_rel_remove_native_t = Int32 Function( + Pointer box, Uint32 relation_id, Uint64 source_id, Uint64 target_id); +typedef obx_box_rel_get_ids_native_t = Pointer Function( + Pointer box, Uint32 relation_id, Uint64 id); +typedef obx_box_rel_get_backlink_ids_native_t = Pointer Function( + Pointer box, Uint32 relation_id, Uint64 id); + // no typedef for non-functions yet, see https://github.com/dart-lang/language/issues/65 // typedef obx_err = Int32 // typedef Pointer -> char[] @@ -180,6 +208,20 @@ typedef obx_qb_order_native_t = Int32 Function( typedef obx_qb_order_dart_t = int Function( Pointer builder, int property_id, int flags); +typedef obx_qb_link_property_native_t = Pointer Function( + Pointer builder, Uint32 property_id); +typedef obx_qb_backlink_property_native_t = Pointer Function( + Pointer builder, Uint32 source_entity_id, Uint32 source_property_id); +typedef obx_qb_link_standalone_native_t = Pointer Function( + Pointer builder, Uint32 relation_id); +typedef obx_qb_backlink_standalone_native_t = Pointer Function( + Pointer builder, Uint32 relation_id); +typedef obx_qb_link_time_native_t = Pointer Function( + Pointer builder, + Uint32 linked_entity_id, + Uint32 begin_property_id, + Uint32 end_property_id); + // query typedef obx_query_t = Pointer Function(Pointer builder); diff --git a/lib/src/model.dart b/lib/src/model.dart index e6ababf2b..3fc595345 100644 --- a/lib/src/model.dart +++ b/lib/src/model.dart @@ -21,6 +21,16 @@ class Model { // set last entity id bindings.obx_model_last_entity_id( _cModel, model.lastEntityId.id, model.lastEntityId.uid); + + if (model.lastRelationId != null) { + bindings.obx_model_last_relation_id( + _cModel, model.lastRelationId.id, model.lastRelationId.uid); + } + + 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; @@ -55,16 +65,32 @@ class Model { } void addProperty(ModelProperty prop) { - var name = Utf8.toUtf8(prop.name); + final name = Utf8.toUtf8(prop.name); try { _check(bindings.obx_model_property( _cModel, name, prop.type, prop.id.id, prop.id.uid)); + + if (prop.flags != 0) { + _check(bindings.obx_model_property_flags(_cModel, prop.flags)); + } + + if (prop.isToOne) { + final targetEntityName = Utf8.toUtf8(prop.targetEntityName); + + _check(bindings.obx_model_property_relation(_cModel, targetEntityName, + prop.relIndexId.id, prop.relIndexId.uid)); + + free(targetEntityName); + } else if (prop.isToMany) { + _check(bindings.obx_model_relation( + _cModel, + prop.relationId.id, + prop.relationId.uid, + prop.targetEntityId.id, + prop.targetEntityId.uid)); + } } finally { free(name); } - - if (prop.flags != 0) { - _check(bindings.obx_model_property_flags(_cModel, prop.flags)); - } } } diff --git a/lib/src/modelinfo/iduid.dart b/lib/src/modelinfo/iduid.dart index 94a2a3ce6..822ef0eba 100644 --- a/lib/src/modelinfo/iduid.dart +++ b/lib/src/modelinfo/iduid.dart @@ -49,6 +49,10 @@ class IdUid { int get uid => _uid; + bool get isEmpty { + return _id == 0 && _uid == 0; + } + @override String toString() => '$_id:$_uid'; } diff --git a/lib/src/modelinfo/modelentity.dart b/lib/src/modelinfo/modelentity.dart index c7240cd44..ccc73204b 100644 --- a/lib/src/modelinfo/modelentity.dart +++ b/lib/src/modelinfo/modelentity.dart @@ -128,6 +128,9 @@ class ModelEntity { final modelProp = createProperty(prop.name, prop.id.uid); modelProp.type = prop.type; modelProp.flags = prop.flags; + + _hasRelations |= modelProp.isRelation; + return modelProp; } @@ -138,8 +141,13 @@ class ModelEntity { throw Exception( "cannot remove property '${prop.name}' with id ${prop.id.toString()}: not found"); } + properties = properties.where((p) => p != foundProp).toList(); - model.retiredPropertyUids.add(prop.id.uid); + if (prop.type.isRelation) { + model.retiredRelationUids.add(prop.id.uid); + } else { + model.retiredPropertyUids.add(prop.id.uid); + } } bool containsUid(int searched) { @@ -150,4 +158,7 @@ class ModelEntity { } return false; } + + bool _hasRelations = false; + bool get hasRelations => _hasRelations; } diff --git a/lib/src/modelinfo/modelinfo.dart b/lib/src/modelinfo/modelinfo.dart index 3b42ea895..2a5bc0a99 100644 --- a/lib/src/modelinfo/modelinfo.dart +++ b/lib/src/modelinfo/modelinfo.dart @@ -199,7 +199,18 @@ class ModelInfo { } entities = entities.where((p) => p != foundEntity).toList(); retiredEntityUids.add(entity.id.uid); - entity.properties.forEach((prop) => retiredPropertyUids.add(prop.id.uid)); + + entity.properties.forEach((prop) { + if (prop.isRelation) { + if (prop.targetEntityName.contains('<')) { + retiredRelationUids.add(prop.relationId.uid); + } else { + retiredIndexUids.add(prop.relIndexId.uid); + } + } + // if (prop.isIndexer) { retiredIndexUids.add(prop.indexId.uid); } + retiredPropertyUids.add(prop.id.uid); + }); } int generateUid() { diff --git a/lib/src/modelinfo/modelproperty.dart b/lib/src/modelinfo/modelproperty.dart index 717014750..764cd88c4 100644 --- a/lib/src/modelinfo/modelproperty.dart +++ b/lib/src/modelinfo/modelproperty.dart @@ -1,3 +1,5 @@ +import 'package:objectbox/src/bindings/constants.dart'; + import 'modelentity.dart'; import 'iduid.dart'; @@ -7,8 +9,14 @@ class ModelProperty { String name; int type, flags; ModelEntity entity; + String targetEntityName; + IdUid relIndexId, relationId, targetEntityId; - ModelProperty(this.id, this.name, this.type, this.flags, this.entity) { + ModelProperty(this.id, this.name, this.type, this.flags, this.entity, + {this.targetEntityName, + this.relIndexId, + this.relationId, + this.targetEntityId}) { validate(); } @@ -18,6 +26,22 @@ class ModelProperty { name = data['name']; type = data['type']; flags = data.containsKey('flags') ? data['flags'] : 0; + + // relations + targetEntityName = + data.containsKey('targetEntityName') ? data['targetEntityName'] : null; + relIndexId = data.containsKey('relIndexId') + ? IdUid.fromString(data['relIndexId']) + : null; + + relationId = data.containsKey('relationId') + ? IdUid.fromString(data['relationId']) + : null; + + targetEntityId = data.containsKey('targetEntityId') + ? IdUid.fromString(data['targetEntityId']) + : null; + if (check) validate(); } @@ -35,6 +59,18 @@ class ModelProperty { ret['id'] = id.toString(); ret['name'] = name; ret['type'] = type; + if (targetEntityName != null) { + ret['targetEntityName'] = targetEntityName; + } + if (relIndexId != null) { + ret['relIndexId'] = relIndexId.toString(); + } + if (targetEntityId != null) { + ret['targetEntityId'] = targetEntityId.toString(); + } + if (relationId != null) { + ret['relationId'] = relationId.toString(); + } if (flags != 0) ret['flags'] = flags; return ret; } @@ -42,4 +78,16 @@ class ModelProperty { bool containsUid(int searched) { return id.uid == searched; } + + bool get isRelation => OBXPropertyType.Relation == type; + bool get isToOne => isRelation && !targetEntityName.contains('<'); + bool get isToMany => isRelation && targetEntityName.contains('<'); + // bool get isIndexer => [ + // OBXPropertyFlag.INDEXED, + // OBXPropertyFlag.UNIQUE, + // OBXPropertyFlag.INDEX_PARTIAL_SKIP_NULL, + // OBXPropertyFlag.INDEX_PARTIAL_SKIP_ZERO, + // OBXPropertyFlag.INDEX_HASH, + // OBXPropertyFlag.INDEX_HASH64 + // ].any((i) => (i & flags) == i); } diff --git a/lib/src/query/query.dart b/lib/src/query/query.dart index cb2dc6f8b..6e1287134 100644 --- a/lib/src/query/query.dart +++ b/lib/src/query/query.dart @@ -225,6 +225,11 @@ class QueryBooleanProperty extends QueryProperty { // Condition operator ==(bool p) => equals(p); // see issue #43 } +class QueryRelationProperty extends QueryProperty { + QueryRelationProperty({int entityId, int propertyId, int obxType}) + : super(entityId, propertyId, obxType); +} + enum ConditionOp { isNull, notNull, diff --git a/lib/src/store.dart b/lib/src/store.dart index 95ef13bcf..deb710bc2 100644 --- a/lib/src/store.dart +++ b/lib/src/store.dart @@ -34,7 +34,11 @@ class Store { /// /// See our examples for more details. Store(this.defs, - {String directory, int maxDBSizeInKB, int fileMode, int maxReaders}) { + {String directory, + int maxDBSizeInKB, + int fileMode, + int maxReaders, + int debugFlags = 0}) { var model = Model(defs.model); var opt = bindings.obx_opt(); @@ -64,6 +68,9 @@ class Store { rethrow; } _cStore = bindings.obx_store_open(opt); + if (debugFlags != 0) { + checkObx(bindings.obx_store_debug_flags(_cStore, debugFlags)); + } try { checkObxPtr(_cStore, 'failed to create store'); diff --git a/lib/src/util.dart b/lib/src/util.dart index 24fd40d7f..c7bc53cbf 100644 --- a/lib/src/util.dart +++ b/lib/src/util.dart @@ -1,2 +1,8 @@ +import 'package:objectbox/src/bindings/constants.dart'; + bool listContains(List list, T item) => list.indexWhere((x) => x == item) != -1; + +extension PropertyType on int { + bool get isRelation => this == OBXPropertyType.Relation; +} diff --git a/test/entity.dart b/test/entity.dart index aaaeef2d4..e79a17ea2 100644 --- a/test/entity.dart +++ b/test/entity.dart @@ -64,3 +64,27 @@ class TestEntity { disregard = 1; } } + +@Entity() +class RelatedEntityA { + @Id() + int id; + + int tInt; + bool tBool; + RelatedEntityB relB; + + RelatedEntityA({this.id, this.tInt, this.tBool, this.relB}); +} + +@Entity() +class RelatedEntityB { + @Id() + int id; + + String tString; + double tDouble; + List listTestEntity; + + RelatedEntityB({this.id, this.tString, this.tDouble, this.listTestEntity}); +}