diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index bd768597ee..7c6bddfeda 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -24,6 +24,7 @@ import 'dart:io'; import 'dart:typed_data'; import 'package:cancellation_token/cancellation_token.dart'; +import 'package:collection/collection.dart'; // Hide StringUtf8Pointer.toNativeUtf8 and StringUtf16Pointer since these allows silently allocating memory. Use toUtf8Ptr instead import 'package:ffi/ffi.dart' hide StringUtf8Pointer, StringUtf16Pointer; import 'package:logging/logging.dart'; @@ -120,15 +121,17 @@ class _RealmCore { for (var i = 0; i < classCount; i++) { final schemaObject = schema.elementAt(i); final classInfo = schemaClasses.elementAt(i).ref; + final propertiesCount = schemaObject.properties.length; + final computedCount = schemaObject.properties.where((p) => p.isComputed).length; + final persistedCount = propertiesCount - computedCount; classInfo.name = schemaObject.name.toCharPtr(arena); classInfo.primary_key = "".toCharPtr(arena); - classInfo.num_properties = schemaObject.properties.length; - classInfo.num_computed_properties = 0; + classInfo.num_properties = persistedCount; + classInfo.num_computed_properties = computedCount; classInfo.key = RLM_INVALID_CLASS_KEY; classInfo.flags = schemaObject.baseType.flags; - final propertiesCount = schemaObject.properties.length; final properties = arena(propertiesCount); for (var j = 0; j < propertiesCount; j++) { @@ -138,7 +141,7 @@ class _RealmCore { //TODO: Assign the correct public name value https://github.com/realm/realm-dart/issues/697 propInfo.public_name = "".toCharPtr(arena); propInfo.link_target = (schemaProperty.linkTarget ?? "").toCharPtr(arena); - propInfo.link_origin_property_name = "".toCharPtr(arena); + propInfo.link_origin_property_name = (schemaProperty.linkOriginProperty ?? "").toCharPtr(arena); propInfo.type = schemaProperty.propertyType.index; propInfo.collection_type = schemaProperty.collectionType.index; propInfo.flags = realm_property_flags.RLM_PROPERTY_NORMAL; @@ -695,10 +698,11 @@ class _RealmCore { final property = propertiesPtr.elementAt(i); final propertyName = property.ref.name.cast().toRealmDartString()!; final objectType = property.ref.link_target.cast().toRealmDartString(treatEmptyAsNull: true); + final linkOriginProperty = property.ref.link_origin_property_name.cast().toRealmDartString(treatEmptyAsNull: true); final isNullable = property.ref.flags & realm_property_flags.RLM_PROPERTY_NULLABLE != 0; final isPrimaryKey = propertyName == primaryKeyName; - final propertyMeta = RealmPropertyMetadata(property.ref.key, objectType, RealmPropertyType.values.elementAt(property.ref.type), isNullable, - isPrimaryKey, RealmCollectionType.values.elementAt(property.ref.collection_type)); + final propertyMeta = RealmPropertyMetadata(property.ref.key, objectType, linkOriginProperty, RealmPropertyType.values.elementAt(property.ref.type), + isNullable, isPrimaryKey, RealmCollectionType.values.elementAt(property.ref.collection_type)); result[propertyName] = propertyMeta; } return result; @@ -967,6 +971,11 @@ class _RealmCore { return RealmListHandle._(pointer, object.realm.handle); } + RealmResultsHandle getBacklinks(RealmObjectBase object, int sourceTableKey, int propertyKey) { + final pointer = _realmLib.invokeGetPointer(() => _realmLib.realm_get_backlinks(object.handle._pointer, sourceTableKey, propertyKey)); + return RealmResultsHandle._(pointer, object.realm.handle); + } + int getListSize(RealmListHandle handle) { return using((Arena arena) { final size = arena(); diff --git a/lib/src/realm_object.dart b/lib/src/realm_object.dart index 735bb69040..797aab7435 100644 --- a/lib/src/realm_object.dart +++ b/lib/src/realm_object.dart @@ -21,10 +21,11 @@ import 'dart:ffi'; import 'package:collection/collection.dart'; +import 'configuration.dart'; import 'list.dart'; import 'native/realm_core.dart'; import 'realm_class.dart'; -import 'configuration.dart'; +import 'results.dart'; typedef DartDynamic = dynamic; @@ -126,8 +127,9 @@ class RealmPropertyMetadata { final RealmPropertyType propertyType; final bool isNullable; final String? objectType; + final String? linkOriginProperty; final bool isPrimaryKey; - const RealmPropertyMetadata(this.key, this.objectType, this.propertyType, this.isNullable, this.isPrimaryKey, + const RealmPropertyMetadata(this.key, this.objectType, this.linkOriginProperty, this.propertyType, this.isNullable, this.isPrimaryKey, [this.collectionType = RealmCollectionType.none]); } @@ -142,24 +144,31 @@ class RealmCoreAccessor implements RealmAccessor { try { final propertyMeta = metadata[name]; if (propertyMeta.collectionType == RealmCollectionType.list) { - final handle = realmCore.getListProperty(object, propertyMeta.key); - final listMetadata = propertyMeta.objectType == null ? null : object.realm.metadata.getByName(propertyMeta.objectType!); - - // listMetadata is not null when we have list of RealmObjects. If the API was - // called with a generic object arg - get we construct a list of - // RealmObjects since we don't know the type of the object. - if (listMetadata != null && _isTypeGenericObject()) { - switch (listMetadata.schema.baseType) { - case ObjectType.realmObject: - return object.realm.createList(handle, listMetadata); - case ObjectType.embeddedObject: - return object.realm.createList(handle, listMetadata); - default: - throw RealmError('List of ${listMetadata.schema.baseType} is not supported yet'); + if (propertyMeta.propertyType == RealmPropertyType.linkingObjects) { + final sourceMeta = object.realm.metadata.getByName(propertyMeta.objectType!); + final sourceProperty = sourceMeta[propertyMeta.linkOriginProperty!]; + final handle = realmCore.getBacklinks(object, sourceMeta.classKey, sourceProperty.key); + return RealmResultsInternal.create(handle, object.realm, metadata); + } else { + final handle = realmCore.getListProperty(object, propertyMeta.key); + final listMetadata = propertyMeta.objectType == null ? null : object.realm.metadata.getByName(propertyMeta.objectType!); + + // listMetadata is not null when we have list of RealmObjects. If the API was + // called with a generic object arg - get we construct a list of + // RealmObjects since we don't know the type of the object. + if (listMetadata != null && _isTypeGenericObject()) { + switch (listMetadata.schema.baseType) { + case ObjectType.realmObject: + return object.realm.createList(handle, listMetadata); + case ObjectType.embeddedObject: + return object.realm.createList(handle, listMetadata); + default: + throw RealmError('List of ${listMetadata.schema.baseType} is not supported yet'); + } } - } - return object.realm.createList(handle, listMetadata); + return object.realm.createList(handle, listMetadata); + } } Object? value = realmCore.getProperty(object, propertyMeta.key); diff --git a/lib/src/realm_property.dart b/lib/src/realm_property.dart index e33abf86a7..0efa96c142 100644 --- a/lib/src/realm_property.dart +++ b/lib/src/realm_property.dart @@ -26,6 +26,8 @@ class SchemaProperty { final String? linkTarget; + final String? linkOriginProperty; + /// Defines the `Realm` collection type if this property is a collection final RealmCollectionType collectionType; @@ -35,6 +37,9 @@ class SchemaProperty { /// The `Realm` type of the property final RealmPropertyType propertyType; + /// `true` if the property is computed + bool get isComputed => propertyType == RealmPropertyType.linkingObjects; + /// `true` if the property is optional final bool optional; @@ -42,6 +47,14 @@ class SchemaProperty { final String? mapTo; /// @nodoc - const SchemaProperty(this.name, this.propertyType, - {this.optional = false, this.mapTo, this.primaryKey = false, this.linkTarget, this.collectionType = RealmCollectionType.none}); + const SchemaProperty( + this.name, + this.propertyType, { + this.optional = false, + this.mapTo, + this.primaryKey = false, + this.linkTarget, + this.linkOriginProperty, + this.collectionType = RealmCollectionType.none, + }); }