From 02bb301c444afa8b7d4d718a1774ada02541994b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 20 Jun 2023 16:21:16 +0200 Subject: [PATCH 01/41] Add support for collections in mixed properties --- .../kotlin/internal/interop/RealmEnums.kt | 3 + .../kotlin/internal/interop/RealmInterop.kt | 1 + .../kotlin/internal/interop/RealmInterop.kt | 9 +- .../kotlin/internal/interop/ValueType.kt | 5 +- packages/external/core | 2 +- packages/jni-swig-stub/realm.i | 3 +- .../io/realm/kotlin/internal/Converters.kt | 28 ++- .../io/realm/kotlin/internal/RealmAnyImpl.kt | 150 +++++++++++++++ .../kotlin/internal/RealmObjectHelper.kt | 60 +++--- .../realm/kotlin/internal/RealmSetInternal.kt | 1 + .../kotlin/serializers/RealmKSerializers.kt | 6 + .../kotlin/io/realm/kotlin/types/RealmAny.kt | 24 ++- .../realm/kotlin/test/common/RealmAnyTests.kt | 181 +++++++++++++++++- .../test/common/RealmDictionaryTests.kt | 4 + .../kotlin/test/common/RealmListTests.kt | 4 + 15 files changed, 430 insertions(+), 51 deletions(-) diff --git a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmEnums.kt b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmEnums.kt index e6f390cd40..0b625e3ff3 100644 --- a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmEnums.kt +++ b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmEnums.kt @@ -94,4 +94,7 @@ expect enum class ValueType { RLM_TYPE_OBJECT_ID, RLM_TYPE_LINK, RLM_TYPE_UUID, + RLM_TYPE_LIST, + RLM_TYPE_SET, + RLM_TYPE_DICTIONARY, } diff --git a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt index f553d15266..c8787c7563 100644 --- a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt +++ b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt @@ -292,6 +292,7 @@ expect object RealmInterop { isDefault: Boolean ) fun realm_set_embedded(obj: RealmObjectPointer, key: PropertyKey): RealmObjectPointer + fun realm_set_collection(obj: RealmObjectPointer, key: PropertyKey, collectionType: CollectionType) fun realm_object_add_int(obj: RealmObjectPointer, key: PropertyKey, value: Long) fun realm_object_get_parent( obj: RealmObjectPointer, diff --git a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt index 3ef0c015d4..c0cf58b032 100644 --- a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt +++ b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt @@ -458,6 +458,10 @@ actual object RealmInterop { return LongPointerWrapper(realmc.realm_set_embedded(obj.cptr(), key.key)) } + actual fun realm_set_collection(obj: RealmObjectPointer, key: PropertyKey, collectionType: CollectionType) { + realmc.realm_set_collection(obj.cptr(), key.key, collectionType.nativeValue) + } + actual fun realm_object_add_int(obj: RealmObjectPointer, key: PropertyKey, value: Long) { realmc.realm_object_add_int(obj.cptr(), key.key, value) } @@ -983,6 +987,8 @@ actual object RealmInterop { val deletionStructs = realmc.new_valueArray(deletions[0].toInt()) val insertionStructs = realmc.new_valueArray(insertions[0].toInt()) val modificationStructs = realmc.new_valueArray(modifications[0].toInt()) + // FIXME New in core ... what does it do? + val collection_was_cleared = booleanArrayOf(false) realmc.realm_dictionary_get_changed_keys( change.cptr(), deletionStructs, @@ -990,7 +996,8 @@ actual object RealmInterop { insertionStructs, insertions, modificationStructs, - modifications + modifications, + collection_was_cleared ) // TODO optimize - integrate within mem allocator? diff --git a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/ValueType.kt b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/ValueType.kt index d0363e037d..8bdceac38e 100644 --- a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/ValueType.kt +++ b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/ValueType.kt @@ -28,7 +28,10 @@ actual enum class ValueType(override val nativeValue: Int) : NativeEnumerated { RLM_TYPE_DECIMAL128(realm_value_type_e.RLM_TYPE_DECIMAL128), RLM_TYPE_OBJECT_ID(realm_value_type_e.RLM_TYPE_OBJECT_ID), RLM_TYPE_LINK(realm_value_type_e.RLM_TYPE_LINK), - RLM_TYPE_UUID(realm_value_type_e.RLM_TYPE_UUID); + RLM_TYPE_UUID(realm_value_type_e.RLM_TYPE_UUID), + RLM_TYPE_LIST(realm_value_type_e.RLM_TYPE_LIST), + RLM_TYPE_SET(realm_value_type_e.RLM_TYPE_SET), + RLM_TYPE_DICTIONARY(realm_value_type_e.RLM_TYPE_DICTIONARY); companion object { fun from(nativeValue: Int): ValueType = values().find { diff --git a/packages/external/core b/packages/external/core index d86103556a..26d37e2c9b 160000 --- a/packages/external/core +++ b/packages/external/core @@ -1 +1 @@ -Subproject commit d86103556a139686836dd565862e1a8b36917c76 +Subproject commit 26d37e2c9bd7f1db055255dbb79fc44ed4b3a93b diff --git a/packages/jni-swig-stub/realm.i b/packages/jni-swig-stub/realm.i index 9bf2bedd97..f88e093eef 100644 --- a/packages/jni-swig-stub/realm.i +++ b/packages/jni-swig-stub/realm.i @@ -356,7 +356,8 @@ bool realm_object_is_valid(const realm_object_t*); // bool output parameter %apply bool* OUTPUT { bool* out_found, bool* did_create, bool* did_delete_realm, bool* out_inserted, bool* erased, bool* out_erased, bool* did_refresh, bool* did_run, - bool* found, bool* out_collection_was_cleared, bool* did_compact }; + bool* found, bool* out_collection_was_cleared, bool* did_compact, + bool* collection_was_cleared}; // uint64_t output parameter for realm_get_num_versions %apply int64_t* OUTPUT { uint64_t* out_versions_count }; diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/Converters.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/Converters.kt index 4267be41c1..d8c17e1285 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/Converters.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/Converters.kt @@ -103,18 +103,16 @@ public inline fun realmValueToDecimal128(transport: RealmValue): Decimal128 = internal inline fun realmValueToRealmAny( transport: RealmValue, - mediator: Mediator, - owner: RealmReference, + container: RealmAnyContainer, issueDynamicObject: Boolean = false ): RealmAny? { - return realmValueToRealmAny(transport, mediator, owner, issueDynamicObject, false) + return realmValueToRealmAny(transport, container, issueDynamicObject, false) } @Suppress("ComplexMethod", "NestedBlockDepth") internal inline fun realmValueToRealmAny( transport: RealmValue, - mediator: Mediator, - owner: RealmReference, + container: RealmAnyContainer, issueDynamicObject: Boolean, issueDynamicMutableObject: Boolean, ): RealmAny? { @@ -134,6 +132,8 @@ internal inline fun realmValueToRealmAny( RealmAny.create(BsonObjectId(transport.getObjectIdBytes())) ValueType.RLM_TYPE_UUID -> RealmAny.create(RealmUUIDImpl(transport.getUUIDBytes())) ValueType.RLM_TYPE_LINK -> { + val mediator = container.mediator + val owner = container.realm if (issueDynamicObject) { val clazz = when (issueDynamicMutableObject) { true -> DynamicMutableRealmObject::class @@ -150,6 +150,9 @@ internal inline fun realmValueToRealmAny( RealmAny.create(realmObject!! as RealmObject, clazz as KClass) } } + ValueType.RLM_TYPE_LIST -> { RealmAny.create(container.getList()) } + ValueType.RLM_TYPE_SET -> { RealmAny.create(container.getSet()) } + ValueType.RLM_TYPE_DICTIONARY -> { RealmAny.create(container.getDictionary()) } else -> throw IllegalArgumentException("Unsupported type: ${type.name}") } } @@ -346,7 +349,9 @@ internal object RealmValueArgumentConverter { with(converter as RealmValueConverter) { publicToRealmValue(value) } - } ?: throw IllegalArgumentException("Cannot use object '$value' of type '${value::class.simpleName}' as query argument") + } + // This is also hit from primary key + ?: throw IllegalArgumentException("Cannot use object '$value' of type '${value::class.simpleName}' as query argument") } } } ?: nullTransport() @@ -492,7 +497,7 @@ internal fun realmAnyConverter( } /** - * Used for converting values to query arguments. Importing objects isn't allowed here. + * Used for converting values to query arguments. */ internal inline fun MemTrackingAllocator.realmAnyToRealmValueWithObjectImport( value: RealmAny?, @@ -511,13 +516,16 @@ internal inline fun MemTrackingAllocator.realmAnyToRealmValueWithObjectImport( val objRef = realmObjectToRealmReferenceWithImport(obj, mediator, realmReference) realmObjectTransport(objRef as RealmObjectInterop) } + RealmAny.Type.SET -> TODO() + RealmAny.Type.LIST -> { TODO() } + RealmAny.Type.DICTIONARY -> TODO() else -> realmAnyPrimitiveToRealmValue(value) } } } /** - * Used for converting RealmAny values to RealmValues suitable for query arguments. + * Used for converting RealmAny values to RealmValues suitable for query arguments and primary keys. * Importing objects isn't allowed here. */ internal inline fun MemTrackingAllocator.realmAnyToRealmValue(value: RealmAny?): RealmValue { @@ -528,6 +536,10 @@ internal inline fun MemTrackingAllocator.realmAnyToRealmValue(value: RealmAny?): val objRef = realmObjectToRealmReferenceOrError(value.asRealmObject()) realmObjectTransport(objRef) } + RealmAny.Type.SET, + RealmAny.Type.LIST, + RealmAny.Type.DICTIONARY -> + throw IllegalArgumentException("Cannot use nested collections as primary keys or query arguments") else -> realmAnyPrimitiveToRealmValue(value) } } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmAnyImpl.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmAnyImpl.kt index 0a2791fab8..f259c8a8f9 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmAnyImpl.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmAnyImpl.kt @@ -16,16 +16,158 @@ package io.realm.kotlin.internal +import io.realm.kotlin.Realm +import io.realm.kotlin.dynamic.DynamicMutableRealmObject +import io.realm.kotlin.dynamic.DynamicRealmObject +import io.realm.kotlin.internal.RealmObjectHelper.setValueTransportByKey +import io.realm.kotlin.internal.interop.CollectionType +import io.realm.kotlin.internal.interop.MemTrackingAllocator +import io.realm.kotlin.internal.interop.PropertyKey +import io.realm.kotlin.internal.interop.RealmInterop +import io.realm.kotlin.internal.interop.RealmListPointer +import io.realm.kotlin.internal.interop.RealmMapPointer +import io.realm.kotlin.internal.interop.RealmPointer +import io.realm.kotlin.internal.interop.RealmSetPointer import io.realm.kotlin.types.BaseRealmObject import io.realm.kotlin.types.RealmAny +import io.realm.kotlin.types.RealmDictionary import io.realm.kotlin.types.RealmInstant +import io.realm.kotlin.types.RealmList +import io.realm.kotlin.types.RealmMap import io.realm.kotlin.types.RealmObject +import io.realm.kotlin.types.RealmSet import io.realm.kotlin.types.RealmUUID import org.mongodb.kbson.BsonObjectId import org.mongodb.kbson.Decimal128 import kotlin.reflect.KClass import kotlin.reflect.cast +internal sealed interface RealmAnyContainer { + + val mediator: Mediator + val realm: RealmReference + val obj: RealmObjectReference<*> + fun set(allocator: MemTrackingAllocator, value: RealmAny) { + with(allocator) { + when (value.type) { + RealmAny.Type.INT, + RealmAny.Type.BOOL, + RealmAny.Type.STRING, + RealmAny.Type.BINARY, + RealmAny.Type.TIMESTAMP, + RealmAny.Type.FLOAT, + RealmAny.Type.DOUBLE, + RealmAny.Type.DECIMAL128, + RealmAny.Type.OBJECT_ID, + RealmAny.Type.UUID, + RealmAny.Type.OBJECT -> + setPrimitive(value) + RealmAny.Type.SET -> { + createCollection(CollectionType.RLM_COLLECTION_TYPE_SET) + getSet().addAll(value.asSet()) + } + + RealmAny.Type.LIST -> { + createCollection(CollectionType.RLM_COLLECTION_TYPE_LIST) + getList().addAll(value.asList()) + } + RealmAny.Type.DICTIONARY -> { + createCollection(CollectionType.RLM_COLLECTION_TYPE_DICTIONARY) + getDictionary().putAll(value.asDictionary()) + } + } + } + } + + fun MemTrackingAllocator.setPrimitive(value: RealmAny) + fun createCollection(collectionType: CollectionType) + fun getSet(): RealmSet + fun getList(): RealmList + fun getDictionary(): RealmDictionary +} + +internal class RealmAnyProperty( + override val obj: RealmObjectReference<*>, + val key: PropertyKey +) : RealmAnyContainer { + + override val mediator = obj.mediator + override val realm: RealmReference = obj.owner + + override fun createCollection(collectionType: CollectionType) { + RealmInterop.realm_set_collection(obj.objectPointer, key, collectionType) + } + + override fun MemTrackingAllocator.setPrimitive(value: RealmAny) { + val converter = if (value.type == RealmAny.Type.OBJECT) { + when ((value as RealmAnyImpl<*>).clazz) { + DynamicRealmObject::class -> + realmAnyConverter(obj.mediator, obj.owner, true) + DynamicMutableRealmObject::class -> + realmAnyConverter( + obj.mediator, + obj.owner, + issueDynamicObject = true, + issueDynamicMutableObject = true + ) + else -> + realmAnyConverter(obj.mediator, obj.owner) + } + } else { + realmAnyConverter(obj.mediator, obj.owner) + } + with(converter) { + setValueTransportByKey(obj, key, publicToRealmValue(value)) + } + } + + override fun getSet(): RealmSet { + val nativePointer = RealmInterop.realm_get_set(obj.objectPointer, key) + val operator: PrimitiveSetOperator = PrimitiveSetOperator( + mediator, + realm, + realmAnyConverter( + mediator, + realm, + false, + false + ), //issueDynamicObject, issueDynamicMutableObject), + nativePointer + ) + return ManagedRealmSet(obj, nativePointer, operator) + } + + override fun getList(): RealmList { + val nativePointer = RealmInterop.realm_get_list(obj.objectPointer, key) + val operator = PrimitiveListOperator( + mediator, + realm, + realmAnyConverter( + mediator, + realm, + false, + false + ),// issueDynamicObject, issueDynamicMutableObject) + nativePointer + ) + val realmAnyList = ManagedRealmList(obj, nativePointer, operator) + return realmAnyList + } + + override fun getDictionary(): RealmDictionary { + val nativePointer = RealmInterop.realm_get_dictionary(obj.objectPointer, key) + val operator = RealmAnyMapOperator( + mediator, + realm, + realmAnyConverter(mediator, realm, false, false), // issueDynamicObject, issueDynamicMutableObject), + converter(String::class, mediator, realm), + nativePointer + ) + val realmAnyDictionary = ManagedRealmDictionary(obj, nativePointer, operator) + return realmAnyDictionary + } +} + internal class RealmAnyImpl constructor( override val type: RealmAny.Type, internal val clazz: KClass, @@ -90,6 +232,14 @@ internal class RealmAnyImpl constructor( return clazz.cast(getValue) } + override fun asSet(): RealmSet = getValue(RealmAny.Type.SET) as RealmSet + + override fun asList(): RealmList = + getValue(RealmAny.Type.LIST) as RealmList + + override fun asDictionary(): RealmDictionary = + getValue(RealmAny.Type.DICTIONARY) as RealmDictionary + private fun getValue(type: RealmAny.Type): Any { if (this.type != type) { throw IllegalStateException("RealmAny type mismatch, wanted a '${type.name}' but the instance is a '${this.type.name}'.") diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt index d89777e267..8200cb2aee 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt @@ -217,26 +217,7 @@ internal object RealmObjectHelper { ) is MutableRealmInt -> setValueTransportByKey(obj, key, longTransport(value.get())) is RealmAny -> { - val converter = if (value.type == RealmAny.Type.OBJECT) { - when ((value as RealmAnyImpl<*>).clazz) { - DynamicRealmObject::class -> - realmAnyConverter(obj.mediator, obj.owner, true) - DynamicMutableRealmObject::class -> - realmAnyConverter( - obj.mediator, - obj.owner, - issueDynamicObject = true, - issueDynamicMutableObject = true - ) - else -> - realmAnyConverter(obj.mediator, obj.owner) - } - } else { - realmAnyConverter(obj.mediator, obj.owner) - } - with(converter) { - setValueTransportByKey(obj, key, publicToRealmValue(value)) - } + RealmAnyProperty(obj, key).set(this, value) } else -> throw IllegalArgumentException("Unsupported value for transport: $value") } @@ -249,69 +230,75 @@ internal object RealmObjectHelper { internal inline fun getString( obj: RealmObjectReference, propertyName: String - ): String? = getterScope { getValue(obj, propertyName)?.let { realmValueToString(it) } } + ): String? = getterScope { getRealmValue(obj, propertyName)?.let { realmValueToString(it) } } internal inline fun getLong( obj: RealmObjectReference, propertyName: String - ): Long? = getterScope { getValue(obj, propertyName)?.let { realmValueToLong(it) } } + ): Long? = getterScope { getRealmValue(obj, propertyName)?.let { realmValueToLong(it) } } internal inline fun getBoolean( obj: RealmObjectReference, propertyName: String - ): Boolean? = getterScope { getValue(obj, propertyName)?.let { realmValueToBoolean(it) } } + ): Boolean? = getterScope { getRealmValue(obj, propertyName)?.let { realmValueToBoolean(it) } } internal inline fun getFloat( obj: RealmObjectReference, propertyName: String - ): Float? = getterScope { getValue(obj, propertyName)?.let { realmValueToFloat(it) } } + ): Float? = getterScope { getRealmValue(obj, propertyName)?.let { realmValueToFloat(it) } } internal inline fun getDouble( obj: RealmObjectReference, propertyName: String - ): Double? = getterScope { getValue(obj, propertyName)?.let { realmValueToDouble(it) } } + ): Double? = getterScope { getRealmValue(obj, propertyName)?.let { realmValueToDouble(it) } } internal inline fun getDecimal128( obj: RealmObjectReference, propertyName: String - ): Decimal128? = getterScope { getValue(obj, propertyName)?.let { realmValueToDecimal128(it) } } + ): Decimal128? = getterScope { getRealmValue(obj, propertyName)?.let { realmValueToDecimal128(it) } } internal inline fun getInstant( obj: RealmObjectReference, propertyName: String ): RealmInstant? = - getterScope { getValue(obj, propertyName)?.let { realmValueToRealmInstant(it) } } + getterScope { getRealmValue(obj, propertyName)?.let { realmValueToRealmInstant(it) } } internal inline fun getObjectId( obj: RealmObjectReference, propertyName: String - ): BsonObjectId? = getterScope { getValue(obj, propertyName)?.let { realmValueToObjectId(it) } } + ): BsonObjectId? = getterScope { getRealmValue(obj, propertyName)?.let { realmValueToObjectId(it) } } internal inline fun getUUID( obj: RealmObjectReference, propertyName: String - ): RealmUUID? = getterScope { getValue(obj, propertyName)?.let { realmValueToRealmUUID(it) } } + ): RealmUUID? = getterScope { getRealmValue(obj, propertyName)?.let { realmValueToRealmUUID(it) } } internal inline fun getByteArray( obj: RealmObjectReference, propertyName: String - ): ByteArray? = getterScope { getValue(obj, propertyName)?.let { realmValueToByteArray(it) } } + ): ByteArray? = getterScope { getRealmValue(obj, propertyName)?.let { realmValueToByteArray(it) } } - internal inline fun getRealmAny( + internal fun getRealmAny( obj: RealmObjectReference, propertyName: String ): RealmAny? = getterScope { - getValue(obj, propertyName) - ?.let { realmValueToRealmAny(it, obj.mediator, obj.owner) } + val key = obj.propertyInfoOrThrow(propertyName).key + getRealmValueFromKey(obj, key) + ?.let { realmValueToRealmAny(it, RealmAnyProperty(obj, key)) } } - internal inline fun MemAllocator.getValue( + internal inline fun MemAllocator.getRealmValue( obj: RealmObjectReference, propertyName: String, + ): RealmValue? = getRealmValueFromKey(obj, obj.propertyInfoOrThrow(propertyName).key) + + internal inline fun MemAllocator.getRealmValueFromKey( + obj: RealmObjectReference, + propertyKey: PropertyKey ): RealmValue? { val realmValue = realm_get_value( obj.objectPointer, - obj.propertyInfoOrThrow(propertyName).key + propertyKey ) return when (realmValue.isNull()) { true -> null @@ -893,8 +880,7 @@ internal object RealmObjectHelper { ) RealmAny::class -> realmValueToRealmAny( transport, - obj.mediator, - obj.owner, + RealmAnyProperty(obj, propertyInfo.key), true, issueDynamicMutableObject ) diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmSetInternal.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmSetInternal.kt index e87f099ba5..e7a4be28d2 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmSetInternal.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmSetInternal.kt @@ -60,6 +60,7 @@ internal class UnmanagedRealmSet : RealmSet, InternalDeleteable, MutableSe * Implementation for managed sets, backed by Realm. */ internal class ManagedRealmSet constructor( + // Rework to allow RealmAny internal val parent: RealmObjectReference<*>, internal val nativePointer: RealmSetPointer, val operator: SetOperator diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/serializers/RealmKSerializers.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/serializers/RealmKSerializers.kt index 4d0b19277e..d62a51752c 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/serializers/RealmKSerializers.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/serializers/RealmKSerializers.kt @@ -370,6 +370,9 @@ public object RealmAnyKSerializer : KSerializer { Type.OBJECT_ID -> RealmAny.create(it.objectId!!) Type.UUID -> RealmAny.create(it.uuid!!) Type.OBJECT -> RealmAny.create(it.realmObject!!) + Type.SET -> TODO() + Type.LIST -> TODO() + Type.DICTIONARY -> TODO() } } } @@ -393,6 +396,9 @@ public object RealmAnyKSerializer : KSerializer { ) Type.UUID -> uuid = value.asRealmUUID() Type.OBJECT -> realmObject = value.asRealmObject() + Type.SET -> TODO() + Type.LIST -> TODO() + Type.DICTIONARY -> TODO() } } ) diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmAny.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmAny.kt index f1343b23e1..5adbe76b9b 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmAny.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmAny.kt @@ -112,7 +112,7 @@ public interface RealmAny { * Supported Realm data types that can be stored in a `RealmAny` instance. */ public enum class Type { - INT, BOOL, STRING, BINARY, TIMESTAMP, FLOAT, DOUBLE, DECIMAL128, OBJECT_ID, UUID, OBJECT + INT, BOOL, STRING, BINARY, TIMESTAMP, FLOAT, DOUBLE, DECIMAL128, OBJECT_ID, UUID, OBJECT, SET, LIST, DICTIONARY } /** @@ -233,6 +233,13 @@ public interface RealmAny { */ public fun asRealmObject(clazz: KClass): T + // FIXME Docs + public fun asSet(): RealmSet + // FIXME Docs + public fun asList(): RealmList + // FIXME Docs + public fun asDictionary(): RealmDictionary + /** * Two [RealmAny] instances are equal if and only if their types and contents are the equal. */ @@ -343,5 +350,20 @@ public interface RealmAny { */ public fun create(realmObject: DynamicRealmObject): RealmAny = RealmAnyImpl(Type.OBJECT, DynamicRealmObject::class, realmObject) + + // FIXME Docs + // Directly from Set? + public fun create(value: RealmSet): RealmAny = + RealmAnyImpl(Type.SET, RealmAny::class, value) + + // FIXME Docs + // Directly from Collection? + public fun create(value: RealmList): RealmAny = + RealmAnyImpl(Type.LIST, RealmAny::class, value) + + // FIXME Docs + // Directly from map? + public fun create(value: RealmDictionary): RealmAny = + RealmAnyImpl(Type.DICTIONARY, RealmAny::class, value) } } diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyTests.kt index 777babb336..ca06ddc2a9 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyTests.kt @@ -25,6 +25,9 @@ import io.realm.kotlin.entities.embedded.EmbeddedParent import io.realm.kotlin.entities.embedded.embeddedSchema import io.realm.kotlin.ext.asRealmObject import io.realm.kotlin.ext.query +import io.realm.kotlin.ext.realmDictionaryOf +import io.realm.kotlin.ext.realmListOf +import io.realm.kotlin.ext.realmSetOf import io.realm.kotlin.internal.platform.runBlocking import io.realm.kotlin.notifications.DeletedObject import io.realm.kotlin.notifications.InitialObject @@ -61,6 +64,15 @@ import kotlin.test.assertNotNull import kotlin.test.assertNull import kotlin.test.fail +class JsonStyleRealmObject : RealmObject { + var value: RealmAny? = null + +} + +class JsonParent : RealmObject { + var child: JsonStyleRealmObject? = null +} + @Suppress("LargeClass") class RealmAnyTests { @@ -76,7 +88,9 @@ class RealmAnyTests { embeddedSchema + IndexedRealmAnyContainer::class + RealmAnyContainer::class + - Sample::class + Sample::class + + JsonParent::class + + JsonStyleRealmObject::class ).directory(tmpDir) configuration = configBuilder.build() realm = Realm.open(configuration) @@ -89,6 +103,171 @@ class RealmAnyTests { } PlatformUtils.deleteTempDir(tmpDir) } + @Test + fun setInMixed() = runBlocking { + val o = realm.write { + val instance = JsonParent().apply { + // Normal realm link/object reference + child = JsonStyleRealmObject().apply { + // Assigning set + // - How to prevent adding a set containing non-any elements? + // - Can we do this on the fly!? + value = RealmAny.create( + realmSetOf( + RealmAny.create(5), + RealmAny.create(4), + RealmAny.create(6) + ) + ) + } + } + copyToRealm(instance) + } + val jsonStyleRealmObject: JsonStyleRealmObject = o.child!! + val anyValue: RealmAny = jsonStyleRealmObject.value!! + assertEquals(RealmAny.Type.SET, anyValue.type) + TabbedStringBuilder().dumpRealmAny(anyValue) + } + + @Test + fun listInMixed() = runBlocking { + val o = realm.write { + val instance = JsonParent().apply { + // Normal realm link/object reference + child = JsonStyleRealmObject().apply { + // Assigning list + // - Quite verbose!? + // - How to prevent/support adding a set containing non-any elements? + // - Can we do this on the fly!? + // - Type system to allow only `RealmAny.create(list: RealmList)` + value = RealmAny.create( + realmListOf( + RealmAny.create(5), + RealmAny.create(4), + RealmAny.create(6) + ) + ) + // - Could add: + // fun realmAnyListOf(vararg: Any): RealmList + // or + // fun Iterable.toRealmAnyList(): RealmList + // to allow convenience like + // realmAnyListOf(5, "Realm", realmAnyListOf()) + // listOf(3, "Realm", realmAnyListOf()).toRealmAnyList() + // Assigning dictornary with nested lists and dictionaries +// value = RealmAny.create( +// realmDictionaryOf( +// "key1" to RealmAny.create(5), +// "key2" to RealmAny.create( +// realmListOf( +// RealmAny.create(6), +// RealmAny.create("Realm"), +// RealmAny.create( +// realmListOf( +// RealmAny.create(5) +// ) +// ) +// ) +// ), +// "key3" to RealmAny.create( +// realmDictionaryOf( +// "key31" to RealmAny.create(6), +// "key32" to RealmAny.create("Realm"), +// ) +// ), +// ) +// ) + } + } + copyToRealm(instance) + } + val jsonStyleRealmObject: JsonStyleRealmObject = o.child!! + val anyValue: RealmAny = jsonStyleRealmObject.value!! + assertEquals(RealmAny.Type.LIST, anyValue.type) + TabbedStringBuilder().dumpRealmAny(anyValue) + } + + @Test + fun dictionaryInMixed() = runBlocking { + val o = realm.write { + val instance = JsonParent().apply { + // Normal realm link/object reference + child = JsonStyleRealmObject().apply { + // Assigning dictornary with nested lists and dictionaries + value = RealmAny.create( + realmDictionaryOf( + "key1" to RealmAny.create(5), +// "key2" to RealmAny.create( +// realmListOf( +// RealmAny.create(6), +// RealmAny.create("Realm"), +// RealmAny.create( +// realmListOf( +// RealmAny.create(5) +// ) +// ) +// ) +// ), +// "key3" to RealmAny.create( +// realmDictionaryOf( +// "key31" to RealmAny.create(6), +// "key32" to RealmAny.create("Realm"), +// ) +// ), + ) + ) + } + } + copyToRealm(instance) + } + val jsonStyleRealmObject: JsonStyleRealmObject = o.child!! + val anyValue: RealmAny = jsonStyleRealmObject.value!! + assertEquals(RealmAny.Type.DICTIONARY, anyValue.type) + val tabbedStringBuilder = TabbedStringBuilder() + tabbedStringBuilder.dumpRealmAny(anyValue) + println(tabbedStringBuilder) + } + + // Importing objects with cache through a setter for nested collections + + class TabbedStringBuilder() { + private val builder = StringBuilder() + internal var indentation = 0 + internal fun append(s: String) = builder.append("\t".repeat(indentation) + s + "\n") + override fun toString(): String { + return builder.toString() + } + } + + fun TabbedStringBuilder.dumpRealmAny(value: RealmAny?) { + if (value == null) { + append("null") + return + } + when (value.type) { + RealmAny.Type.SET, RealmAny.Type.LIST -> { + val collection: Collection = if (value.type == RealmAny.Type.SET) value.asSet() else value.asList() + append("[") + indentation += 1 + collection.map { dumpRealmAny(it) } + indentation -= 1 + append("]") + } + RealmAny.Type.DICTIONARY -> value.asDictionary().let { dictionary -> + append("{") + indentation += 1 + dictionary.map { (key, element) -> + append("$key:") + indentation += 1 + dumpRealmAny(element) + indentation -= 1 + } + indentation -= 1 + append("}") + } + else -> append(value.toString()) + } + } @Test fun missingClassFromSchema_unmanagedWorks() { diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmDictionaryTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmDictionaryTests.kt index 4c903effe0..50c5b9347f 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmDictionaryTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmDictionaryTests.kt @@ -3063,6 +3063,10 @@ internal class RealmAnyDictionaryTester( assertEquals(expectedObj.stringField, assertNotNull(actualObj).stringField) } null -> assertNull(actualValue) + // FIXME Should we rather test nested collections somewhere else? + RealmAny.Type.SET -> TODO() + RealmAny.Type.LIST -> TODO() + RealmAny.Type.DICTIONARY -> TODO() } } } diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmListTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmListTests.kt index cd336a15eb..8c0ddd09e4 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmListTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmListTests.kt @@ -1278,6 +1278,10 @@ internal class RealmAnyListTester constructor( expected.asRealmObject().stringField, actual.asRealmObject().stringField ) + // FIXME Should we rather test nested collections somewhere else? + RealmAny.Type.SET -> TODO() + RealmAny.Type.LIST -> TODO() + RealmAny.Type.DICTIONARY -> TODO() } } else if (expected != null || actual != null) { fail("One of the RealmAny values is null, expected = $expected, actual = $actual") From cd3ed424530b4af1259392e811c45040d76a25fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 27 Jun 2023 14:30:18 +0200 Subject: [PATCH 02/41] Add nested RealmAny collection support --- .../kotlin/internal/interop/RealmInterop.kt | 19 + .../kotlin/internal/interop/RealmInterop.kt | 37 + .../io/realm/kotlin/internal/Converters.kt | 35 +- .../io/realm/kotlin/internal/RealmAnyImpl.kt | 36 +- .../kotlin/internal/RealmListInternal.kt | 143 +++- .../realm/kotlin/internal/RealmMapInternal.kt | 187 ++++- .../kotlin/internal/RealmObjectHelper.kt | 13 +- .../realm/kotlin/internal/RealmSetInternal.kt | 57 +- .../io/realm/kotlin/internal/RealmUtils.kt | 3 +- .../kotlin/io/realm/kotlin/types/RealmAny.kt | 9 +- .../common/RealmAnyNestedCollectionTests.kt | 705 ++++++++++++++++++ .../realm/kotlin/test/common/RealmAnyTests.kt | 199 +---- .../dynamic/DynamicMutableRealmObjectTests.kt | 11 + 13 files changed, 1203 insertions(+), 251 deletions(-) create mode 100644 packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt diff --git a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt index c8787c7563..83d29d4c39 100644 --- a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt +++ b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt @@ -304,10 +304,15 @@ expect object RealmInterop { fun realm_get_backlinks(obj: RealmObjectPointer, sourceClassKey: ClassKey, sourcePropertyKey: PropertyKey): RealmResultsPointer fun realm_list_size(list: RealmListPointer): Long fun MemAllocator.realm_list_get(list: RealmListPointer, index: Long): RealmValue + fun realm_list_get_set(list: RealmListPointer, index: Long): RealmSetPointer + fun realm_list_get_list(list: RealmListPointer, index: Long): RealmListPointer + fun realm_list_get_dictionary(list: RealmListPointer, index: Long): RealmMapPointer fun realm_list_add(list: RealmListPointer, index: Long, transport: RealmValue) fun realm_list_insert_embedded(list: RealmListPointer, index: Long): RealmObjectPointer // Returns the element previously at the specified position fun realm_list_set(list: RealmListPointer, index: Long, inputTransport: RealmValue) + fun realm_list_insert_collection(list: RealmListPointer, index: Long, collectionType: CollectionType) + fun realm_list_set_collection(list: RealmListPointer, index: Long, collectionType: CollectionType) // Returns the newly inserted element as the previous embedded element is automatically delete // by this operation @@ -339,10 +344,23 @@ expect object RealmInterop { dictionary: RealmMapPointer, mapKey: RealmValue ): RealmValue + fun realm_dictionary_find_set( + dictionary: RealmMapPointer, + mapKey: RealmValue + ): RealmSetPointer + fun realm_dictionary_find_list( + dictionary: RealmMapPointer, + mapKey: RealmValue + ): RealmListPointer + fun realm_dictionary_find_dictionary( + dictionary: RealmMapPointer, + mapKey: RealmValue + ): RealmMapPointer fun MemAllocator.realm_dictionary_get( dictionary: RealmMapPointer, pos: Int ): Pair + fun MemAllocator.realm_dictionary_insert( dictionary: RealmMapPointer, mapKey: RealmValue, @@ -364,6 +382,7 @@ expect object RealmInterop { dictionary: RealmMapPointer, mapKey: RealmValue ): RealmValue + fun realm_dictionary_insert_collection(dictionary: RealmMapPointer, mapKey: RealmValue, collectionType: CollectionType) fun realm_dictionary_get_keys(dictionary: RealmMapPointer): RealmResultsPointer fun realm_dictionary_resolve_in( dictionary: RealmMapPointer, diff --git a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt index c0cf58b032..5d20a52733 100644 --- a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt +++ b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt @@ -17,6 +17,7 @@ package io.realm.kotlin.internal.interop import io.realm.kotlin.internal.interop.Constants.ENCRYPTION_KEY_LENGTH +import io.realm.kotlin.internal.interop.RealmInterop.cptr import io.realm.kotlin.internal.interop.sync.ApiKeyWrapper import io.realm.kotlin.internal.interop.sync.AuthProvider import io.realm.kotlin.internal.interop.sync.CoreConnectionState @@ -518,6 +519,14 @@ actual object RealmInterop { realmc.realm_list_get(list.cptr(), index, struct) return RealmValue(struct) } + actual fun realm_list_get_set(list: RealmListPointer, index: Long): RealmSetPointer = + LongPointerWrapper(realmc.realm_list_get_set(list.cptr(), index)) + actual fun realm_list_get_list(list: RealmListPointer, index: Long): RealmListPointer = + LongPointerWrapper(realmc.realm_list_get_list(list.cptr(), index)) + + actual fun realm_list_get_dictionary(list: RealmListPointer, index: Long): RealmMapPointer = + LongPointerWrapper(realmc.realm_list_get_dictionary(list.cptr(), index)) + actual fun realm_list_add(list: RealmListPointer, index: Long, transport: RealmValue) { realmc.realm_list_insert(list.cptr(), index, transport.value) @@ -526,6 +535,12 @@ actual object RealmInterop { actual fun realm_list_insert_embedded(list: RealmListPointer, index: Long): RealmObjectPointer { return LongPointerWrapper(realmc.realm_list_insert_embedded(list.cptr(), index)) } + actual fun realm_list_insert_collection(list: RealmListPointer, index: Long, collectionType: CollectionType) { + realmc.realm_list_insert_collection(list.cptr(), index, collectionType.nativeValue) + } + actual fun realm_list_set_collection(list: RealmListPointer, index: Long, collectionType: CollectionType) { + realmc.realm_list_set_collection(list.cptr(), index, collectionType.nativeValue) + } actual fun realm_list_set( list: RealmListPointer, @@ -682,6 +697,25 @@ actual object RealmInterop { realmc.realm_dictionary_find(dictionary.cptr(), mapKey.value, struct, found) return RealmValue(struct) } + actual fun realm_dictionary_find_set( + dictionary: RealmMapPointer, + mapKey: RealmValue + ): RealmSetPointer { + return LongPointerWrapper(realmc.realm_dictionary_get_set(dictionary.cptr(), mapKey.value)) + } + + actual fun realm_dictionary_find_list( + dictionary: RealmMapPointer, + mapKey: RealmValue + ): RealmListPointer { + return LongPointerWrapper(realmc.realm_dictionary_get_list(dictionary.cptr(), mapKey.value)) + } + actual fun realm_dictionary_find_dictionary( + dictionary: RealmMapPointer, + mapKey: RealmValue + ): RealmMapPointer { + return LongPointerWrapper(realmc.realm_dictionary_get_dictionary(dictionary.cptr(), mapKey.value)) + } actual fun MemAllocator.realm_dictionary_get( dictionary: RealmMapPointer, @@ -750,6 +784,9 @@ actual object RealmInterop { } ) } + actual fun realm_dictionary_insert_collection(dictionary: RealmMapPointer, mapKey: RealmValue, collectionType: CollectionType) { + realmc.realm_dictionary_insert_collection(dictionary.cptr(), mapKey.value, collectionType.nativeValue) + } actual fun realm_dictionary_get_keys(dictionary: RealmMapPointer): RealmResultsPointer { val size = LongArray(1) diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/Converters.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/Converters.kt index d8c17e1285..e0c5a7eb27 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/Converters.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/Converters.kt @@ -20,7 +20,9 @@ import io.realm.kotlin.UpdatePolicy import io.realm.kotlin.dynamic.DynamicMutableRealmObject import io.realm.kotlin.dynamic.DynamicRealmObject import io.realm.kotlin.ext.asRealmObject +import io.realm.kotlin.internal.interop.CollectionType import io.realm.kotlin.internal.interop.MemTrackingAllocator +import io.realm.kotlin.internal.interop.RealmInterop import io.realm.kotlin.internal.interop.RealmObjectInterop import io.realm.kotlin.internal.interop.RealmQueryArgument import io.realm.kotlin.internal.interop.RealmQueryArgumentList @@ -337,6 +339,24 @@ internal val primitiveTypeConverters: Map, RealmValueConverter<*>> = // Dynamic default primitive value converter to translate primary keys and query arguments to RealmValues @Suppress("NestedBlockDepth") internal object RealmValueArgumentConverter { + fun MemTrackingAllocator.kAnyToPrimaryKeyRealmValue(value: Any?): RealmValue { + return value?.let { value -> + when (value) { + is RealmObject -> { + realmObjectTransport(realmObjectToRealmReferenceOrError(value)) + } + else -> { + primitiveTypeConverters[value::class]?.let { converter -> + with(converter as RealmValueConverter) { + publicToRealmValue(value) + } + } + // This is also hit from primary key + ?: throw IllegalArgumentException("Cannot use object '$value' of type '${value::class.simpleName}' as primary key argument") + } + } + } ?: nullTransport() + } fun MemTrackingAllocator.kAnyToRealmValue(value: Any?): RealmValue { return value?.let { value -> when (value) { @@ -441,7 +461,9 @@ internal fun realmAnyConverter( mediator: Mediator, realmReference: RealmReference, issueDynamicObject: Boolean = false, - issueDynamicMutableObject: Boolean = false + issueDynamicMutableObject: Boolean = false, + updatePolicy: UpdatePolicy = UpdatePolicy.ERROR, + cache: UnmanagedToManagedObjectCache = mutableMapOf() ): RealmValueConverter { return object : PassThroughPublicConverter() { override inline fun fromRealmValue(realmValue: RealmValue): RealmAny? = @@ -486,11 +508,12 @@ internal fun realmAnyConverter( } override inline fun MemTrackingAllocator.toRealmValue(value: RealmAny?): RealmValue { - return realmAnyToRealmValueWithObjectImport( + return realmAnyToRealmValueWithImport( value, mediator, realmReference, issueDynamicObject, + updatePolicy, cache ) } } @@ -499,11 +522,13 @@ internal fun realmAnyConverter( /** * Used for converting values to query arguments. */ -internal inline fun MemTrackingAllocator.realmAnyToRealmValueWithObjectImport( +internal inline fun MemTrackingAllocator.realmAnyToRealmValueWithImport( value: RealmAny?, mediator: Mediator, realmReference: RealmReference, - issueDynamicObject: Boolean = false + issueDynamicObject: Boolean = false, + updatePolicy: UpdatePolicy = UpdatePolicy.ERROR, + cache: UnmanagedToManagedObjectCache = mutableMapOf() ): RealmValue { return when (value) { null -> nullTransport() @@ -513,7 +538,7 @@ internal inline fun MemTrackingAllocator.realmAnyToRealmValueWithObjectImport( true -> value.asRealmObject() false -> value.asRealmObject() } - val objRef = realmObjectToRealmReferenceWithImport(obj, mediator, realmReference) + val objRef = realmObjectToRealmReferenceWithImport(obj, mediator, realmReference, updatePolicy, cache) realmObjectTransport(objRef as RealmObjectInterop) } RealmAny.Type.SET -> TODO() diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmAnyImpl.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmAnyImpl.kt index f259c8a8f9..24aed1b51b 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmAnyImpl.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmAnyImpl.kt @@ -17,6 +17,7 @@ package io.realm.kotlin.internal import io.realm.kotlin.Realm +import io.realm.kotlin.UpdatePolicy import io.realm.kotlin.dynamic.DynamicMutableRealmObject import io.realm.kotlin.dynamic.DynamicRealmObject import io.realm.kotlin.internal.RealmObjectHelper.setValueTransportByKey @@ -47,7 +48,10 @@ internal sealed interface RealmAnyContainer { val mediator: Mediator val realm: RealmReference val obj: RealmObjectReference<*> - fun set(allocator: MemTrackingAllocator, value: RealmAny) { + fun set(allocator: MemTrackingAllocator, value: RealmAny, + updatePolicy: UpdatePolicy = UpdatePolicy.ALL, + cache: UnmanagedToManagedObjectCache = mutableMapOf() + ) { with(allocator) { when (value.type) { RealmAny.Type.INT, @@ -64,16 +68,16 @@ internal sealed interface RealmAnyContainer { setPrimitive(value) RealmAny.Type.SET -> { createCollection(CollectionType.RLM_COLLECTION_TYPE_SET) - getSet().addAll(value.asSet()) + (getSet() as ManagedRealmSet).operator.addAll(value.asSet(), updatePolicy, cache) } RealmAny.Type.LIST -> { createCollection(CollectionType.RLM_COLLECTION_TYPE_LIST) - getList().addAll(value.asList()) + (getList() as ManagedRealmList).operator.insertAll(0, value.asList(), updatePolicy, cache) } RealmAny.Type.DICTIONARY -> { createCollection(CollectionType.RLM_COLLECTION_TYPE_DICTIONARY) - getDictionary().putAll(value.asDictionary()) + (getDictionary() as ManagedRealmDictionary).operator.putAll(value.asDictionary(), updatePolicy, cache) } } } @@ -123,33 +127,13 @@ internal class RealmAnyProperty( override fun getSet(): RealmSet { val nativePointer = RealmInterop.realm_get_set(obj.objectPointer, key) - val operator: PrimitiveSetOperator = PrimitiveSetOperator( - mediator, - realm, - realmAnyConverter( - mediator, - realm, - false, - false - ), //issueDynamicObject, issueDynamicMutableObject), - nativePointer - ) + val operator = RealmAnySetOperator( mediator, realm, nativePointer ) return ManagedRealmSet(obj, nativePointer, operator) } override fun getList(): RealmList { val nativePointer = RealmInterop.realm_get_list(obj.objectPointer, key) - val operator = PrimitiveListOperator( - mediator, - realm, - realmAnyConverter( - mediator, - realm, - false, - false - ),// issueDynamicObject, issueDynamicMutableObject) - nativePointer - ) + val operator = RealmAnyListOperator( mediator, realm, nativePointer ) val realmAnyList = ManagedRealmList(obj, nativePointer, operator) return realmAnyList } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmListInternal.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmListInternal.kt index ab59b422fb..2fd886c99b 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmListInternal.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmListInternal.kt @@ -21,6 +21,7 @@ import io.realm.kotlin.Versioned import io.realm.kotlin.internal.RealmValueArgumentConverter.convertToQueryArgs import io.realm.kotlin.internal.interop.Callback import io.realm.kotlin.internal.interop.ClassKey +import io.realm.kotlin.internal.interop.CollectionType import io.realm.kotlin.internal.interop.RealmChangesPointer import io.realm.kotlin.internal.interop.RealmInterop import io.realm.kotlin.internal.interop.RealmInterop.realm_list_get @@ -28,6 +29,7 @@ import io.realm.kotlin.internal.interop.RealmInterop.realm_list_set_embedded import io.realm.kotlin.internal.interop.RealmListPointer import io.realm.kotlin.internal.interop.RealmNotificationTokenPointer import io.realm.kotlin.internal.interop.RealmObjectInterop +import io.realm.kotlin.internal.interop.ValueType import io.realm.kotlin.internal.interop.getterScope import io.realm.kotlin.internal.interop.inputScope import io.realm.kotlin.internal.query.ObjectBoundQuery @@ -38,6 +40,7 @@ import io.realm.kotlin.notifications.internal.InitialListImpl import io.realm.kotlin.notifications.internal.UpdatedListImpl import io.realm.kotlin.query.RealmQuery import io.realm.kotlin.types.BaseRealmObject +import io.realm.kotlin.types.RealmAny import io.realm.kotlin.types.RealmList import kotlinx.coroutines.channels.ProducerScope import kotlinx.coroutines.flow.Flow @@ -59,7 +62,7 @@ internal class UnmanagedRealmList : RealmList, InternalDeleteable, Mutable * Implementation for managed lists, backed by Realm. */ internal class ManagedRealmList( - internal val parent: RealmObjectReference<*>, + internal val parent: RealmObjectReference<*>?, internal val nativePointer: RealmListPointer, val operator: ListOperator, ) : AbstractMutableList(), RealmList, InternalDeleteable, CoreNotifiable, ListChange>, Versioned by operator.realmReference { @@ -171,6 +174,7 @@ internal fun ManagedRealmList.query( throw IllegalArgumentException(e.message, e.cause) } } + if (parent == null) TODO() return ObjectBoundQuery( parent, ObjectQuery( @@ -289,6 +293,143 @@ internal class PrimitiveListOperator( PrimitiveListOperator(mediator, realmReference, valueConverter, nativePointer) } +internal class RealmAnyListOperator( + override val mediator: Mediator, + override val realmReference: RealmReference, + override val nativePointer: RealmListPointer, + updatePolicy: UpdatePolicy = UpdatePolicy.ALL, + cache: UnmanagedToManagedObjectCache = mutableMapOf() +) : ListOperator { + + override val valueConverter: RealmValueConverter = realmAnyConverter(mediator, realmReference, false, false) + + @Suppress("UNCHECKED_CAST") + override fun get(index: Int): RealmAny? { + return getterScope { + val transport = realm_list_get(nativePointer, index.toLong()) + val element: ValueType = transport.getType() + if (element == ValueType.RLM_TYPE_SET) { + val newNativePointer = RealmInterop.realm_list_get_set(nativePointer, index.toLong()) + val operator = RealmAnySetOperator( mediator, realmReference, newNativePointer ) + val realmAnySet = ManagedRealmSet(null, newNativePointer, operator) + RealmAny.Companion.create(realmAnySet) + } else if (element == ValueType.RLM_TYPE_LIST) { + val newNativePointer = RealmInterop.realm_list_get_list(nativePointer, index.toLong()) + val operator = RealmAnyListOperator( mediator, realmReference, newNativePointer ) + val realmAnyList = ManagedRealmList(null, newNativePointer, operator) + RealmAny.Companion.create(realmAnyList) + } else if (element == ValueType.RLM_TYPE_DICTIONARY) { + val newNativePointer = RealmInterop.realm_list_get_dictionary(nativePointer, index.toLong()) + val operator = RealmAnyMapOperator( mediator, realmReference, + realmAnyConverter(mediator, realmReference, false, false), + converter(String::class, mediator, realmReference), + newNativePointer ) + val realmDict = ManagedRealmDictionary( + null, + newNativePointer, + operator + ) + RealmAny.create(realmDict) + } else { + with(valueConverter) { + realmValueToPublic(transport) + } + } + } + } + + override fun insert( + index: Int, + element: RealmAny?, + updatePolicy: UpdatePolicy, + cache: UnmanagedToManagedObjectCache + ) { + inputScope { + if (element != null && element.type in RealmAny.Type.COLLECTION_TYPES) { + when(element.type) { + RealmAny.Type.SET -> { + RealmInterop.realm_list_insert_collection(nativePointer, index.toLong(), CollectionType.RLM_COLLECTION_TYPE_SET) + val newNativePointer = RealmInterop.realm_list_get_set(nativePointer, index.toLong()) + val operator = RealmAnySetOperator( mediator, realmReference, newNativePointer)// , updatePolicy, cache) + operator.addAllInternal(element.asSet(), updatePolicy, cache) + } + RealmAny.Type.LIST -> { + RealmInterop.realm_list_insert_collection(nativePointer, index.toLong(), CollectionType.RLM_COLLECTION_TYPE_LIST) + val newNativePointer = RealmInterop.realm_list_get_list(nativePointer, index.toLong()) + val operator = RealmAnyListOperator( mediator, realmReference, newNativePointer, updatePolicy, cache) + operator.insertAll(0, element.asList(), updatePolicy, cache) + } + RealmAny.Type.DICTIONARY -> { + RealmInterop.realm_list_insert_collection(nativePointer, index.toLong(), CollectionType.RLM_COLLECTION_TYPE_DICTIONARY) + val newNativePointer = RealmInterop.realm_list_get_dictionary(nativePointer, index.toLong()) + val operator = RealmAnyMapOperator( mediator, realmReference, + realmAnyConverter(mediator, realmReference, false, false), + converter(String::class, mediator, realmReference), + newNativePointer ) + operator.putAll(element.asDictionary(), updatePolicy, cache) + } + else -> TODO() + } + } else { + with(valueConverter) { + val transport = publicToRealmValue(element) + RealmInterop.realm_list_add(nativePointer, index.toLong(), transport) + } + } + } + } + + @Suppress("UNCHECKED_CAST") + override fun set( + index: Int, + element: RealmAny?, + updatePolicy: UpdatePolicy, + cache: UnmanagedToManagedObjectCache + ): RealmAny? { + return get(index).also { + inputScope { + if (element != null && element.type in RealmAny.Type.COLLECTION_TYPES) { + when(element.type) { + RealmAny.Type.SET -> { + RealmInterop.realm_list_set_collection(nativePointer, index.toLong(), CollectionType.RLM_COLLECTION_TYPE_SET) + val newNativePointer = RealmInterop.realm_list_get_set(nativePointer, index.toLong()) + val operator = RealmAnySetOperator( mediator, realmReference, newNativePointer)// , updatePolicy, cache) + operator.addAllInternal(element.asSet(), updatePolicy, cache) + } + RealmAny.Type.LIST -> { + RealmInterop.realm_list_set_collection(nativePointer, index.toLong(), CollectionType.RLM_COLLECTION_TYPE_LIST) + val newNativePointer = RealmInterop.realm_list_get_list(nativePointer, index.toLong()) + val operator = RealmAnyListOperator( mediator, realmReference, newNativePointer, updatePolicy, cache) + operator.insertAll(0, element.asList(), updatePolicy, cache) + } + RealmAny.Type.DICTIONARY -> { + RealmInterop.realm_list_set_collection(nativePointer, index.toLong(), CollectionType.RLM_COLLECTION_TYPE_DICTIONARY) + val newNativePointer = RealmInterop.realm_list_get_dictionary(nativePointer, index.toLong()) + val operator = RealmAnyMapOperator( mediator, realmReference, + realmAnyConverter(mediator, realmReference, false, false), + converter(String::class, mediator, realmReference), + newNativePointer ) + operator.putAll(element.asDictionary(), updatePolicy, cache) + } + else -> TODO() + } + } else { + with(valueConverter) { + val transport = publicToRealmValue(element) + RealmInterop.realm_list_set(nativePointer, index.toLong(), transport) + } + } + } + } + } + + override fun copy( + realmReference: RealmReference, + nativePointer: RealmListPointer + ): ListOperator = + RealmAnyListOperator(mediator, realmReference, nativePointer) +} + internal abstract class BaseRealmObjectListOperator( override val mediator: Mediator, override val realmReference: RealmReference, diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt index 884f771f72..a665571fd9 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt @@ -23,18 +23,23 @@ import io.realm.kotlin.ext.isManaged import io.realm.kotlin.internal.RealmValueArgumentConverter.convertToQueryArgs import io.realm.kotlin.internal.interop.Callback import io.realm.kotlin.internal.interop.ClassKey +import io.realm.kotlin.internal.interop.CollectionType import io.realm.kotlin.internal.interop.RealmChangesPointer import io.realm.kotlin.internal.interop.RealmInterop import io.realm.kotlin.internal.interop.RealmInterop.realm_dictionary_erase import io.realm.kotlin.internal.interop.RealmInterop.realm_dictionary_find +import io.realm.kotlin.internal.interop.RealmInterop.realm_dictionary_find_set import io.realm.kotlin.internal.interop.RealmInterop.realm_dictionary_get import io.realm.kotlin.internal.interop.RealmInterop.realm_dictionary_insert import io.realm.kotlin.internal.interop.RealmInterop.realm_dictionary_insert_embedded +import io.realm.kotlin.internal.interop.RealmInterop.realm_list_get import io.realm.kotlin.internal.interop.RealmInterop.realm_results_get import io.realm.kotlin.internal.interop.RealmMapPointer import io.realm.kotlin.internal.interop.RealmNotificationTokenPointer import io.realm.kotlin.internal.interop.RealmObjectInterop import io.realm.kotlin.internal.interop.RealmResultsPointer +import io.realm.kotlin.internal.interop.RealmValue +import io.realm.kotlin.internal.interop.ValueType import io.realm.kotlin.internal.interop.getterScope import io.realm.kotlin.internal.interop.inputScope import io.realm.kotlin.internal.query.ObjectBoundQuery @@ -58,10 +63,11 @@ import kotlin.reflect.KClass // ---------------------------------------------------------------------- internal abstract class ManagedRealmMap constructor( - internal val parent: RealmObjectReference<*>, + internal val parent: RealmObjectReference<*>?, internal val nativePointer: RealmMapPointer, val operator: MapOperator -) : AbstractMutableMap(), RealmMap, CoreNotifiable, MapChange>, Flowable> { +) : AbstractMutableMap(), RealmMap, + CoreNotifiable, MapChange>, Flowable> { private val keysPointer by lazy { RealmInterop.realm_dictionary_get_keys(nativePointer) } private val valuesPointer by lazy { RealmInterop.realm_dictionary_to_results(nativePointer) } @@ -127,6 +133,7 @@ internal fun ManagedRealmMap.query( val mapValues = values as RealmMapValues<*, *> RealmInterop.realm_query_parse_for_results(mapValues.resultsPointer, query, queryArgs) } + if (parent == null) TODO() return ObjectBoundQuery( parent, ObjectQuery( @@ -399,6 +406,106 @@ internal class RealmAnyMapOperator constructor( } } } + + @Suppress("UNCHECKED_CAST") + override fun getEntryInternal(position: Int): Pair { + return getterScope { + realm_dictionary_get(nativePointer, position) + .let { + val keyTransport: K = with(keyConverter) { realmValueToPublic(it.first) as K } + // FIXME Should we have realm_dictionaryy_get_list, ..._dict, ..._set + return keyTransport to getInternal(keyTransport) + } + } + } + override fun getInternal(key: K): RealmAny? { + return inputScope { + val keyTransport: RealmValue = with(keyConverter) { publicToRealmValue(key) } + val valueTransport: RealmValue = realm_dictionary_find(nativePointer, keyTransport) + val element = valueTransport.getType() + if (element == ValueType.RLM_TYPE_SET) { + val newNativePointer = RealmInterop.realm_dictionary_find_set(nativePointer, keyTransport) + val operator = RealmAnySetOperator( mediator, realmReference, newNativePointer ) + val realmAnySet = ManagedRealmSet(null, newNativePointer, operator) + RealmAny.Companion.create(realmAnySet) + } else if (element == ValueType.RLM_TYPE_LIST) { + val newNativePointer = RealmInterop.realm_dictionary_find_list(nativePointer, keyTransport) + val operator = RealmAnyListOperator( mediator, realmReference, newNativePointer ) + val realmAnyList = ManagedRealmList(null, newNativePointer, operator) + RealmAny.Companion.create(realmAnyList) + } else if (element == ValueType.RLM_TYPE_DICTIONARY) { + val newNativePointer = RealmInterop.realm_dictionary_find_dictionary(nativePointer, keyTransport) + val operator = RealmAnyMapOperator( mediator, realmReference, + realmAnyConverter(mediator, realmReference, false, false), + converter(String::class, mediator, realmReference), + newNativePointer ) + val realmDict = ManagedRealmDictionary( + null, + newNativePointer, + operator + ) + RealmAny.create(realmDict) + } else { + with(valueConverter) { + realmValueToPublic(valueTransport) + } + } + } + } + + override fun insertInternal( + key: K, + value: RealmAny?, + updatePolicy: UpdatePolicy, + cache: UnmanagedToManagedObjectCache + ): Pair { + return inputScope { + val keyTransport = with(keyConverter) { publicToRealmValue(key) } + if (value != null && value.type in RealmAny.Type.COLLECTION_TYPES) { + when(value.type) { + RealmAny.Type.SET -> { + RealmInterop.realm_dictionary_insert_collection(nativePointer, keyTransport, CollectionType.RLM_COLLECTION_TYPE_SET) + val newNativePointer = RealmInterop.realm_dictionary_find_set(nativePointer, keyTransport) + val operator = RealmAnySetOperator( mediator, realmReference, newNativePointer)// , updatePolicy, cache) + operator.addAllInternal(value.asSet(), updatePolicy, cache) + // FIXME Return value for updates??!? + RealmAny.create(ManagedRealmSet(null, newNativePointer, operator)) to true + } + RealmAny.Type.LIST -> { + RealmInterop.realm_dictionary_insert_collection(nativePointer, keyTransport, CollectionType.RLM_COLLECTION_TYPE_LIST) + val newNativePointer = RealmInterop.realm_dictionary_find_list(nativePointer, keyTransport) + val operator = RealmAnyListOperator( mediator, realmReference, newNativePointer, updatePolicy, cache) + // FIXME Return value for updates??!? + operator.insertAll(0, value.asList(), updatePolicy, cache) + RealmAny.create(ManagedRealmList(null, newNativePointer, operator)) to true + } + RealmAny.Type.DICTIONARY -> { + RealmInterop.realm_dictionary_insert_collection(nativePointer, keyTransport, CollectionType.RLM_COLLECTION_TYPE_DICTIONARY) + val newNativePointer = RealmInterop.realm_dictionary_find_dictionary(nativePointer, keyTransport) + val operator = RealmAnyMapOperator( mediator, realmReference, + realmAnyConverter(mediator, realmReference, false, false), + converter(String::class, mediator, realmReference), + newNativePointer ) + operator.putAll(value.asDictionary(), updatePolicy, cache) + // FIXME Return value for updates??!? + RealmAny.create(ManagedRealmDictionary(null, nativePointer, operator)) to true + } + else -> TODO() + } + } else { + with(valueConverter) { + val valueTransport = publicToRealmValue(value) + realm_dictionary_insert( + nativePointer, + keyTransport, + valueTransport + ).let { + Pair(realmValueToPublic(it.first), it.second) + } + } + } + } + } } @Suppress("LongParameterList") @@ -639,30 +746,37 @@ internal class UnmanagedRealmDictionary( } internal class ManagedRealmDictionary constructor( - parent: RealmObjectReference<*>, + parent: RealmObjectReference<*>?, nativePointer: RealmMapPointer, operator: MapOperator -) : ManagedRealmMap(parent, nativePointer, operator), RealmDictionary, Versioned by operator.realmReference { +) : ManagedRealmMap(parent, nativePointer, operator), RealmDictionary, + Versioned by operator.realmReference { override fun freeze(frozenRealm: RealmReference): ManagedRealmDictionary? { - return RealmInterop.realm_dictionary_resolve_in(nativePointer, frozenRealm.dbPointer)?.let { - ManagedRealmDictionary(parent, it, operator.copy(frozenRealm, it)) - } + return RealmInterop.realm_dictionary_resolve_in(nativePointer, frozenRealm.dbPointer) + ?.let { + ManagedRealmDictionary(parent, it, operator.copy(frozenRealm, it)) + } } override fun changeFlow(scope: ProducerScope>): ChangeFlow, MapChange> = RealmDictonaryChangeFlow(scope) override fun thaw(liveRealm: RealmReference): ManagedRealmDictionary? { - return RealmInterop.realm_dictionary_resolve_in(nativePointer, liveRealm.dbPointer)?.let { - ManagedRealmDictionary(parent, it, operator.copy(liveRealm, it)) - } + return RealmInterop.realm_dictionary_resolve_in(nativePointer, liveRealm.dbPointer) + ?.let { + ManagedRealmDictionary(parent, it, operator.copy(liveRealm, it)) + } } override fun toString(): String { - val owner = parent.className - val version = parent.owner.version().version - val objKey = RealmInterop.realm_object_get_key(parent.objectPointer).key + val (owner, version, objKey) = parent?.run { + Triple( + className, + owner.version().version, + RealmInterop.realm_object_get_key(parent.objectPointer).key + ) + } ?: TODO() return "RealmDictionary{size=$size,owner=$owner,objKey=$objKey,version=$version}" } @@ -671,7 +785,8 @@ internal class ManagedRealmDictionary constructor( internal class RealmDictonaryChangeFlow(scope: ProducerScope>) : ChangeFlow, MapChange>(scope) { - override fun initial(frozenRef: ManagedRealmMap): MapChange = InitialDictionaryImpl(frozenRef) + override fun initial(frozenRef: ManagedRealmMap): MapChange = + InitialDictionaryImpl(frozenRef) override fun update( frozenRef: ManagedRealmMap, @@ -681,7 +796,8 @@ internal class RealmDictonaryChangeFlow(scope: ProducerScope = DeletedDictionaryImpl(UnmanagedRealmDictionary()) + override fun delete(): MapChange = + DeletedDictionaryImpl(UnmanagedRealmDictionary()) } // ---------------------------------------------------------------------- @@ -694,7 +810,7 @@ internal class RealmDictonaryChangeFlow(scope: ProducerScope constructor( private val keysPointer: RealmResultsPointer, private val operator: MapOperator, - private val parent: RealmObjectReference<*> + private val parent: RealmObjectReference<*>? ) : AbstractMutableSet() { override val size: Int @@ -710,9 +826,13 @@ internal class KeySet constructor( } override fun toString(): String { - val owner = parent.className - val version = parent.owner.version().version - val objKey = RealmInterop.realm_object_get_key(parent.objectPointer).key + val (owner, version, objKey) = parent?.run { + Triple( + className, + owner.version().version, + RealmInterop.realm_object_get_key(parent.objectPointer).key + ) + } ?: TODO() return "RealmDictionary.keys{size=$size,owner=$owner,objKey=$objKey,version=$version}" } @@ -740,7 +860,7 @@ internal class KeySet constructor( internal class RealmMapValues constructor( internal val resultsPointer: RealmResultsPointer, private val operator: MapOperator, - private val parent: RealmObjectReference<*> + private val parent: RealmObjectReference<*>? ) : AbstractMutableCollection() { override val size: Int @@ -817,9 +937,13 @@ internal class RealmMapValues constructor( } override fun toString(): String { - val owner = parent.className - val version = parent.owner.version().version - val objKey = RealmInterop.realm_object_get_key(parent.objectPointer).key + val (owner, version, objKey) = parent?.run { + Triple( + className, + owner.version().version, + RealmInterop.realm_object_get_key(parent.objectPointer).key + ) + } ?: TODO() return "RealmDictionary.values{size=$size,owner=$owner,objKey=$objKey,version=$version}" } @@ -935,7 +1059,7 @@ internal abstract class RealmMapGenericIterator( internal class RealmMapEntrySetImpl constructor( private val nativePointer: RealmMapPointer, private val operator: MapOperator, - private val parent: RealmObjectReference<*> + private val parent: RealmObjectReference<*>? ) : AbstractMutableSet>(), RealmMapEntrySet { override val size: Int @@ -956,7 +1080,10 @@ internal class RealmMapEntrySetImpl constructor( @Suppress("UNCHECKED_CAST") override fun getNext(position: Int): MutableMap.MutableEntry { val pair = operator.getEntry(position) - return ManagedRealmMapEntry(pair.first, operator) as MutableMap.MutableEntry + return ManagedRealmMapEntry( + pair.first, + operator + ) as MutableMap.MutableEntry } } @@ -974,9 +1101,13 @@ internal class RealmMapEntrySetImpl constructor( } override fun toString(): String { - val owner = parent.className - val version = parent.owner.version().version - val objKey = RealmInterop.realm_object_get_key(parent.objectPointer).key + val (owner, version, objKey) = parent?.run { + Triple( + className, + owner.version().version, + RealmInterop.realm_object_get_key(parent.objectPointer).key + ) + } ?: TODO() return "RealmDictionary.entries{size=$size,owner=$owner,objKey=$objKey,version=$version}" } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt index 8200cb2aee..a88160c30b 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt @@ -156,7 +156,7 @@ internal object RealmObjectHelper { // Primitives // --------------------------------------------------------------------- - internal inline fun setValue( + internal fun setValue( obj: RealmObjectReference, propertyName: String, value: Any? @@ -181,7 +181,7 @@ internal object RealmObjectHelper { } @Suppress("ComplexMethod", "LongMethod") - internal inline fun setValueByKey( + internal fun setValueByKey( obj: RealmObjectReference, key: PropertyKey, value: Any? @@ -418,10 +418,9 @@ internal object RealmObjectHelper { converter(clazz, mediator, realm) as CompositeConverter, listPtr ) - CollectionOperatorType.REALM_ANY -> PrimitiveListOperator( + CollectionOperatorType.REALM_ANY -> RealmAnyListOperator( mediator, realm, - realmAnyConverter(mediator, realm, issueDynamicObject, issueDynamicMutableObject), listPtr ) as ListOperator CollectionOperatorType.REALM_OBJECT -> { @@ -509,10 +508,9 @@ internal object RealmObjectHelper { converter(clazz, mediator, realm), setPtr ) - CollectionOperatorType.REALM_ANY -> PrimitiveSetOperator( + CollectionOperatorType.REALM_ANY -> RealmAnySetOperator( mediator, realm, - realmAnyConverter(mediator, realm, issueDynamicObject, issueDynamicMutableObject), setPtr ) as SetOperator CollectionOperatorType.REALM_OBJECT -> { @@ -1074,7 +1072,8 @@ internal object RealmObjectHelper { } else -> inputScope { val transport = - realmAnyToRealmValueWithObjectImport(value, obj.mediator, obj.owner) + // FIXME Lacks tests + realmAnyToRealmValueWithImport(value, obj.mediator, obj.owner, true, updatePolicy, cache) setValueTransportByKey(obj, propertyMetadata.key, transport) } } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmSetInternal.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmSetInternal.kt index e7a4be28d2..54b190bfd9 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmSetInternal.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmSetInternal.kt @@ -38,6 +38,7 @@ import io.realm.kotlin.notifications.internal.InitialSetImpl import io.realm.kotlin.notifications.internal.UpdatedSetImpl import io.realm.kotlin.query.RealmQuery import io.realm.kotlin.types.BaseRealmObject +import io.realm.kotlin.types.RealmAny import io.realm.kotlin.types.RealmSet import kotlinx.coroutines.channels.ProducerScope import kotlinx.coroutines.flow.Flow @@ -61,7 +62,7 @@ internal class UnmanagedRealmSet : RealmSet, InternalDeleteable, MutableSe */ internal class ManagedRealmSet constructor( // Rework to allow RealmAny - internal val parent: RealmObjectReference<*>, + internal val parent: RealmObjectReference<*>?, internal val nativePointer: RealmSetPointer, val operator: SetOperator ) : AbstractMutableSet(), RealmSet, InternalDeleteable, CoreNotifiable, SetChange>, Versioned by operator.realmReference { @@ -199,6 +200,7 @@ internal fun ManagedRealmSet.query( queryArgs ) } + if (parent == null) TODO() return ObjectBoundQuery( parent, ObjectQuery( @@ -289,6 +291,59 @@ internal interface SetOperator : CollectionOperator { fun copy(realmReference: RealmReference, nativePointer: RealmSetPointer): SetOperator } +internal class RealmAnySetOperator( + override val mediator: Mediator, + override val realmReference: RealmReference, + override val nativePointer: RealmSetPointer +) : SetOperator { + + override val valueConverter: RealmValueConverter = realmAnyConverter(mediator, realmReference, false, false) + override var modCount: Int = 0 + + @Suppress("UNCHECKED_CAST") + override fun get(index: Int): RealmAny? { + return getterScope { + with(valueConverter) { + val transport = realm_set_get(nativePointer, index.toLong()) + // Primitive + objects + realmValueToPublic(transport) + } + } + } + + override fun addInternal( + element: RealmAny?, + updatePolicy: UpdatePolicy, + cache: UnmanagedToManagedObjectCache + ): Boolean { + if (element?.type in RealmAny.Type.COLLECTION_TYPES) { + throw IllegalArgumentException("Cannot add collections to RealmSets") + } + return inputScope { + with(valueConverter) { + // Primitive + objects with Import + val transport = publicToRealmValue(element) + RealmInterop.realm_set_insert(nativePointer, transport) + } + } + } + + override fun contains(element: RealmAny?): Boolean { + return inputScope { + with(valueConverter) { + val transport = publicToRealmValue(element) + RealmInterop.realm_set_find(nativePointer, transport) + } + } + } + + override fun copy( + realmReference: RealmReference, + nativePointer: RealmSetPointer + ): SetOperator = + PrimitiveSetOperator(mediator, realmReference, valueConverter, nativePointer) +} + internal class PrimitiveSetOperator( override val mediator: Mediator, override val realmReference: RealmReference, diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmUtils.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmUtils.kt index 6798d73ed5..09106489a1 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmUtils.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmUtils.kt @@ -23,6 +23,7 @@ import io.realm.kotlin.VersionId import io.realm.kotlin.ext.isManaged import io.realm.kotlin.ext.isValid import io.realm.kotlin.internal.RealmObjectHelper.assign +import io.realm.kotlin.internal.RealmValueArgumentConverter.kAnyToPrimaryKeyRealmValue import io.realm.kotlin.internal.RealmValueArgumentConverter.kAnyToRealmValue import io.realm.kotlin.internal.dynamic.DynamicUnmanagedRealmObject import io.realm.kotlin.internal.interop.ClassKey @@ -197,7 +198,7 @@ internal fun copyToRealm( realmReference, element::class, className, - kAnyToRealmValue(primaryKey), + kAnyToPrimaryKeyRealmValue(primaryKey), updatePolicy ) } catch (e: IllegalStateException) { diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmAny.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmAny.kt index 5adbe76b9b..beb000bc72 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmAny.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmAny.kt @@ -112,9 +112,16 @@ public interface RealmAny { * Supported Realm data types that can be stored in a `RealmAny` instance. */ public enum class Type { - INT, BOOL, STRING, BINARY, TIMESTAMP, FLOAT, DOUBLE, DECIMAL128, OBJECT_ID, UUID, OBJECT, SET, LIST, DICTIONARY + INT, BOOL, STRING, BINARY, TIMESTAMP, FLOAT, DOUBLE, DECIMAL128, OBJECT_ID, UUID, OBJECT, SET, LIST, DICTIONARY; + + // FIXME Should this be public!? + public companion object { + public val COLLECTION_TYPES: Set = setOf(SET, LIST, DICTIONARY) + } } + + /** * Returns the [Type] of the `RealmAny` instance. */ diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt new file mode 100644 index 0000000000..84498055a2 --- /dev/null +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt @@ -0,0 +1,705 @@ +/* + * Copyright 2023 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.realm.kotlin.test.common + +import io.realm.kotlin.Realm +import io.realm.kotlin.RealmConfiguration +import io.realm.kotlin.entities.Sample +import io.realm.kotlin.ext.asRealmObject +import io.realm.kotlin.ext.query +import io.realm.kotlin.ext.realmDictionaryOf +import io.realm.kotlin.ext.realmListOf +import io.realm.kotlin.ext.realmSetOf +import io.realm.kotlin.internal.platform.runBlocking +import io.realm.kotlin.test.common.utils.assertFailsWithMessage +import io.realm.kotlin.test.platform.PlatformUtils +import io.realm.kotlin.types.RealmAny +import io.realm.kotlin.types.RealmObject +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +internal class JsonStyleRealmObject : RealmObject { + var id: String = "JsonStyleRealmObject" + var value: RealmAny? = null +} + +class RealmAnyNestedCollectionTests { + + private lateinit var configBuilder: RealmConfiguration.Builder + private lateinit var configuration: RealmConfiguration + private lateinit var tmpDir: String + private lateinit var realm: Realm + + @BeforeTest + fun setup() { + tmpDir = PlatformUtils.createTempDir() + configBuilder = RealmConfiguration.Builder( + setOf( + JsonStyleRealmObject::class, + Sample::class, + ) + ).directory(tmpDir) + configuration = configBuilder.build() + realm = Realm.open(configuration) + } + + @AfterTest + fun tearDown() { + if (this::realm.isInitialized && !realm.isClosed()) { + realm.close() + } + PlatformUtils.deleteTempDir(tmpDir) + } + + // Set + // - Import primitive values + // - Set primitive values + // - Notifications + @Test + fun setInRealmAny_copyToRealm() = runBlocking { + val sample = Sample().apply { stringField = "SAMPLE" } + val o = realm.write { + val instance = JsonStyleRealmObject().apply { + // Assigning set + // - How to prevent adding a set containing non-any elements? + // - Can we do this on the fly!? + value = RealmAny.create( + realmSetOf( + RealmAny.create(5), + RealmAny.create("Realm"), + RealmAny.create(sample) + ) + ) + } + copyToRealm(instance) + } + val anyValue: RealmAny = realm.query().find().single().value!! + assertEquals(RealmAny.Type.SET, anyValue.type) + } + + @Test + fun setInRealmAny_assignment() = runBlocking { + val o = realm.write { + val sample = copyToRealm(Sample().apply { stringField = "SAMPLE" }) + val instance = copyToRealm(JsonStyleRealmObject()) + // Assigning set + // - How to prevent adding a set containing non-any elements? + // - Can we do this on the fly!? + instance.value = RealmAny.create( + realmSetOf( + RealmAny.create(5), + RealmAny.create(4), + RealmAny.create(6) + ) + ) + instance + } + val anyValue: RealmAny = realm.query().find().single().value!! + assertEquals(RealmAny.Type.SET, anyValue.type) + } + + + @Test + fun setInRealmAny_throwsOnNestedCollections_copyToRealm() = runBlocking { + realm.write { + JsonStyleRealmObject().apply { + value = + RealmAny.create(realmSetOf(RealmAny.create(realmListOf(RealmAny.create(5))))) + }.let { + assertFailsWithMessage("Cannot add collections to RealmSets") { + copyToRealm(it) + } + } + JsonStyleRealmObject().apply { + value = RealmAny.create( + realmSetOf( + RealmAny.create(realmDictionaryOf("key" to RealmAny.create(5))) + ) + ) + }.let { + assertFailsWithMessage("Cannot add collections to RealmSets") { + copyToRealm(it) + } + } + } + } + + @Test + fun setInRealmAny_throwsOnNestedCollections_add() = runBlocking { + realm.write { + val instance = copyToRealm(JsonStyleRealmObject().apply { + value = RealmAny.create(realmSetOf()) + }) + val set = instance.value!!.asSet() + + assertFailsWithMessage("Cannot add collections to RealmSets") { + set.add(RealmAny.create(realmListOf())) + } + + assertFailsWithMessage("Cannot add collections to RealmSets") { + set.add(RealmAny.create(realmDictionaryOf())) + } + } + } + + @Test + fun listInRealmAny_copyToRealm() = runBlocking { + val sample = Sample().apply { stringField = "SAMPLE" } + realm.write { + JsonStyleRealmObject().apply { + // Assigning list + // - Quite verbose!? + // - How to prevent/support adding a set containing non-any elements? + // - Can we do this on the fly!? + // - Type system to allow only `RealmAny.create(list: RealmList)` + value = RealmAny.create( + realmListOf( + RealmAny.create(5), + RealmAny.create("Realm"), + RealmAny.create(sample), + ) + ) + // - Could add: + // fun realmAnyListOf(vararg: Any): RealmList + // or + // fun Iterable.toRealmAnyList(): RealmList + // to allow convenience like + // realmAnyListOf(5, "Realm", realmAnyListOf()) + // listOf(3, "Realm", realmAnyListOf()).toRealmAnyList() + }.let { + copyToRealm(it) + } + } + val instance = realm.query().find().single() + val anyValue: RealmAny = instance.value!! + assertEquals(RealmAny.Type.LIST, anyValue.type) +// TabbedStringBuilder().dumpRealmAny(anyValue) + } + + @Test + fun nestedCollectionsInList_copyToRealm() = runBlocking { + val sample = Sample().apply { stringField = "SAMPLE" } + val o = realm.write { + JsonStyleRealmObject().apply { + value = RealmAny.create( + realmListOf( + // Primitive values + RealmAny.create(5), + RealmAny.create("Realm"), + RealmAny.create(sample), + // Embedded list + RealmAny.create( + realmListOf( + RealmAny.create(5), + RealmAny.create("Realm"), + RealmAny.create(sample), + ) + ), + // Embedded set + RealmAny.create( + realmSetOf( + RealmAny.create(5), + RealmAny.create("Realm"), + RealmAny.create(sample), + ) + ), + // Embedded map + RealmAny.create( + realmDictionaryOf( + "keyInt" to RealmAny.create(5), + "keyString" to RealmAny.create("Realm"), + "keyObject" to RealmAny.create(sample) + ) + ), + ) + ) + }.let { + copyToRealm(it) + } + } + val instance = realm.query().find().single() + val anyValue: RealmAny = instance.value!! + assertEquals(RealmAny.Type.LIST, anyValue.type) + // FIXME Duplicate references not identified through RealmAny imports +// assertEquals(1, realm.query().find().size) + + // Assert structure + anyValue.asList().let { + assertEquals(RealmAny.create(5), it[0]) + assertEquals(RealmAny.create("Realm"), it[1]) + assertEquals("SAMPLE", it[2]!!.asRealmObject().stringField) + it[3]!!.asList().let { embeddedList -> + assertEquals(RealmAny.create(5), embeddedList[0]) + assertEquals(RealmAny.create("Realm"), embeddedList[1]) + assertEquals("SAMPLE", embeddedList[2]!!.asRealmObject().stringField) + } + it[4]!!.asSet().toMutableSet().let { embeddedSet -> + assertTrue { embeddedSet.remove(RealmAny.create(5)) } + assertTrue { embeddedSet.remove(RealmAny.create("Realm")) } + assertEquals("SAMPLE", embeddedSet.single()!!.asRealmObject().stringField) + } + it[5]!!.asDictionary().toMutableMap().let { embeddedDict -> + assertEquals(RealmAny.create(5), embeddedDict["keyInt"]) + assertEquals(RealmAny.create("Realm"), embeddedDict["keyString"]) + assertEquals( + "SAMPLE", + embeddedDict["keyObject"]!!.asRealmObject().stringField + ) + } + } + } + + @Test + fun nestedCollectionsInList_add() = runBlocking { + realm.write { + val sample = copyToRealm(Sample().apply { stringField = "SAMPLE" }) + val instance = + copyToRealm(JsonStyleRealmObject().apply { value = RealmAny.create(realmListOf()) }) + instance.value!!.asList().run { + add(RealmAny.create(5)) + add(RealmAny.create("Realm")) + add(RealmAny.create(sample)) + // Embedded list + add( + RealmAny.create( + realmListOf( + RealmAny.create(5), + RealmAny.create("Realm"), + RealmAny.create(sample), + ) + ), + ) + // Embedded set + add( + RealmAny.create( + realmSetOf( + RealmAny.create(5), + RealmAny.create("Realm"), + RealmAny.create(sample), + ) + ), + ) + // Embedded map + add( + RealmAny.create( + realmDictionaryOf( + "keyInt" to RealmAny.create(5), + "keyString" to RealmAny.create("Realm"), + "keyObject" to RealmAny.create(sample) + ) + ), + ) + } + + } + val anyList: RealmAny = realm.query().find().single().value!! + anyList.asList().let { + assertEquals(RealmAny.create(5), it[0]) + assertEquals(RealmAny.create("Realm"), it[1]) + assertEquals("SAMPLE", it[2]!!.asRealmObject().stringField) + it[3]!!.asList().let { embeddedList -> + assertEquals(RealmAny.create(5), embeddedList[0]) + assertEquals(RealmAny.create("Realm"), embeddedList[1]) + assertEquals("SAMPLE", embeddedList[2]!!.asRealmObject().stringField) + } + it[4]!!.asSet().toMutableSet().let { embeddedSet -> + assertTrue { embeddedSet.remove(RealmAny.create(5)) } + assertTrue { embeddedSet.remove(RealmAny.create("Realm")) } + assertEquals("SAMPLE", embeddedSet.single()!!.asRealmObject().stringField) + } + it[5]!!.asDictionary().toMutableMap().let { embeddedDict -> + assertEquals(RealmAny.create(5), embeddedDict["keyInt"]) + assertEquals(RealmAny.create("Realm"), embeddedDict["keyString"]) + assertEquals( + "SAMPLE", + embeddedDict["keyObject"]!!.asRealmObject().stringField + ) + } + } + } + + @Test + fun nestedCollectionsInList_set() = runBlocking { + realm.write { + val sample = copyToRealm(Sample().apply { stringField = "SAMPLE" }) + val instance = + copyToRealm(JsonStyleRealmObject().apply { + value = RealmAny.create( + realmListOf( + RealmAny.create(1), + RealmAny.create(1), + RealmAny.create(1), + RealmAny.create(1), + ) + ) + }) + instance!!.value!!.asList().run { + // Embedded list + set( + 0, + RealmAny.create( + realmListOf( + RealmAny.create(5), + RealmAny.create(sample), + ) + ), + ) + // Embedded set + set( + 1, + RealmAny.create( + realmSetOf( + RealmAny.create(5), + RealmAny.create("Realm"), + RealmAny.create(sample), + ) + ), + ) + // Embedded map + set( + 2, + RealmAny.create( + realmDictionaryOf( + "keyInt" to RealmAny.create(5), + "keyString" to RealmAny.create("Realm"), + "keyObject" to RealmAny.create(sample) + ) + ), + ) + } + } + + val anyValue3: RealmAny = realm.query().find().single().value!! + anyValue3.asList().let { + it[0]!!.asList().let { embeddedList -> + assertEquals(RealmAny.create(5), embeddedList[0]) + assertEquals("SAMPLE", embeddedList[1]!!.asRealmObject().stringField) + } + it[1]!!.asSet().toMutableSet().let { embeddedSet -> + assertTrue { embeddedSet.remove(RealmAny.create(5)) } + assertTrue { embeddedSet.remove(RealmAny.create("Realm")) } + assertEquals("SAMPLE", embeddedSet.single()!!.asRealmObject().stringField) + } + it[2]!!.asDictionary().toMutableMap().let { embeddedDict -> + assertEquals(RealmAny.create(5), embeddedDict["keyInt"]) + assertEquals(RealmAny.create("Realm"), embeddedDict["keyString"]) + assertEquals( + "SAMPLE", + embeddedDict["keyObject"]!!.asRealmObject().stringField + ) + } + } + } + + @Test + fun nestedCollectionsInList_set_invalidatesOldElement() = runBlocking { + realm.write { + val instance = copyToRealm(JsonStyleRealmObject().apply { + value = RealmAny.create( + realmListOf( + RealmAny.create( + realmListOf( + RealmAny.create(5), + ) + ) + ) + ) + } + ) + val nestedList = instance.value!!.asList()[0]!!.asList() + assertEquals(5, nestedList[0]!!.asInt()) + // Overwrite nested list element with new list + instance.value!!.asList()[0] = RealmAny.create(realmListOf(RealmAny.create(7))) + // FIXME This should be true. We shouldn't have overwrite the old list +// assertEquals(5, nestedList[0]!!.asInt()) + // Overwrite nested list element with new collection of different type + instance.value!!.asList()[0] = RealmAny.create(realmSetOf(RealmAny.create(8))) + // Access the old list + // FIXME Seems like we don't throw a nice error when accessing a delete collection + // Overwriting with different collection type seems to ruin original item without + // throwing proper fix +// val realmAny = nestedList[0] +// assertEquals(7, realmAny!!.asInt()) + } + } + + // List + // - Notifications + // - Parent bound deletion + + // Dict + // - Import primitive values, SET, LIST, MAP + // - Put primitive values, SET, LIST, MAP + // - Deletes other lists + // - Notifications + // - Parent bound deletion + + // Others + // - Queries for nested elements?? + // - No collections as primary key arguments - RealmAny is not supported at all + // - No collections as query arguments - DONE + // - toJson/fromJson + // - Serialization + // - Dynamic API + // - Importing objects with cache through a setter for nested collections + + @Test + fun dictionaryInRealmAny_copyToRealm() = runBlocking { + val sample = Sample().apply { stringField = "SAMPLE" } + // Import + val o = realm.write { + // Normal realm link/object reference + JsonStyleRealmObject().apply { + // Assigning dictornary with nested lists and dictionaries + value = RealmAny.create( + realmDictionaryOf( + "keyInt" to RealmAny.create(5), + "keySet" to RealmAny.create( + realmSetOf( + RealmAny.create(5), + RealmAny.create("Realm"), + RealmAny.create(sample), + ) + ), + "keyList" to RealmAny.create( + realmListOf( + RealmAny.create(5), + RealmAny.create("Realm"), + RealmAny.create(sample) + ) + ), + "keyDictionary" to RealmAny.create( + realmDictionaryOf( + "keyInt" to RealmAny.create(5), + "keyString" to RealmAny.create("Realm"), + "keyObject" to RealmAny.create(sample) + ) + ), + ) + ) + }.let { + copyToRealm(it) + } + } + + val jsonStyleRealmObject: JsonStyleRealmObject = + realm.query().find().single() + val anyValue: RealmAny = jsonStyleRealmObject.value!! + assertEquals(RealmAny.Type.DICTIONARY, anyValue.type) + anyValue.asDictionary().run { + assertEquals(4, size) + get("keyInt")!!.let { + assertEquals(5, it.asInt()) + } + get("keySet")!!.asSet().toMutableSet().let { embeddedSet -> + assertEquals(3, embeddedSet.size) + assertTrue { embeddedSet.remove(RealmAny.create(5)) } + assertTrue { embeddedSet.remove(RealmAny.create("Realm")) } + assertEquals( + "SAMPLE", + embeddedSet.single()!!.asRealmObject().stringField + ) + assertEquals(1, embeddedSet.size) + } + + get("keyList")!!.asList().let { embeddedList -> + assertEquals(RealmAny.create(5), embeddedList[0]) + assertEquals(RealmAny.create("Realm"), embeddedList[1]) + assertEquals("SAMPLE", embeddedList[2]!!.asRealmObject().stringField) + } + get("keyDictionary")!!.asDictionary().let { embeddedDict -> + assertEquals(RealmAny.create(5), embeddedDict["keyInt"]) + assertEquals(RealmAny.create("Realm"), embeddedDict["keyString"]) + assertEquals( + "SAMPLE", + embeddedDict["keyObject"]!!.asRealmObject().stringField + ) + } + } + // FIXME Duplicate references not identified through RealmAny imports +// assertEquals(1, realm.query().find().size) + } + @Test + fun dictionaryInRealmAny_put() = runBlocking { + val sample = Sample().apply { stringField = "SAMPLE" } + // Import + realm.write { + copyToRealm(JsonStyleRealmObject().apply { + // Assigning dictornary with nested lists and dictionaries + value = RealmAny.create( + realmDictionaryOf() + ) + }) + query().find().single().value!!.asDictionary().run { + put("keyInt", RealmAny.create(5)) + put( + "keySet", RealmAny.create( + realmSetOf( + RealmAny.create(5), + RealmAny.create("Realm"), + RealmAny.create(sample), + ) + ) + ) + put( + "keyList", RealmAny.create( + realmListOf( + RealmAny.create(5), + RealmAny.create("Realm"), + RealmAny.create(sample) + ) + ) + ) + put( + "keyDictionary", + RealmAny.create( + realmDictionaryOf( + "keyInt" to RealmAny.create(5), + "keyString" to RealmAny.create("Realm"), + "keyObject" to RealmAny.create(sample) + ) + ), + ) + } + } + + val jsonStyleRealmObject: JsonStyleRealmObject = + realm.query().find().single() + val anyValue: RealmAny = jsonStyleRealmObject.value!! + assertEquals(RealmAny.Type.DICTIONARY, anyValue.type) + anyValue.asDictionary().run { + assertEquals(4, size) + get("keyInt")!!.let { + assertEquals(5, it.asInt()) + } + get("keySet")!!.asSet().toMutableSet().let { embeddedSet -> + assertEquals(3, embeddedSet.size) + assertTrue { embeddedSet.remove(RealmAny.create(5)) } + assertTrue { embeddedSet.remove(RealmAny.create("Realm")) } + assertEquals( + "SAMPLE", + embeddedSet.single()!!.asRealmObject().stringField + ) + assertEquals(1, embeddedSet.size) + } + + get("keyList")!!.asList().let { embeddedList -> + assertEquals(RealmAny.create(5), embeddedList[0]) + assertEquals(RealmAny.create("Realm"), embeddedList[1]) + assertEquals("SAMPLE", embeddedList[2]!!.asRealmObject().stringField) + } + get("keyDictionary")!!.asDictionary().let { embeddedDict -> + assertEquals(RealmAny.create(5), embeddedDict["keyInt"]) + assertEquals(RealmAny.create("Realm"), embeddedDict["keyString"]) + assertEquals( + "SAMPLE", + embeddedDict["keyObject"]!!.asRealmObject().stringField + ) + } + } // FIXME Duplicate references not identified through RealmAny imports +// assertEquals(1, realm.query().find().size) + } + + @Test + fun nestedCollectionsInDictionary_put_invalidatesOldElement() = runBlocking { + realm.write { + val instance = copyToRealm(JsonStyleRealmObject().apply { + value = RealmAny.create( + realmDictionaryOf( + "key" to RealmAny.create( + realmListOf( + RealmAny.create(5), + ) + ) + ) + ) + } + ) + val nestedList = instance.value!!.asDictionary()!!.get("key")!!.asList() + assertEquals(5, nestedList[0]!!.asInt()) + // Overwrite nested list element with new list + instance.value!!.asDictionary()!!["key"] = RealmAny.create(realmListOf(RealmAny.create(7))) + // FIXME This shouldn't be true. We shouldn't have overwrite the old list +// assertEquals(5, nestedList[0]!!.asInt()) + // Overwrite nested list element with new collection of different type + instance.value!!.asDictionary()!!["key"] = RealmAny.create(realmSetOf(RealmAny.create(8))) + // Access the old list + // FIXME Seems like we don't throw a nice error when accessing a delete collection + // Overwriting with different collection type seems to ruin original item without + // throwing proper fix +// val realmAny = nestedList[0] +// assertEquals(7, realmAny!!.asInt()) + } + } + + @Test + fun query_ThrowsOnNestedCollectionArguments() { + assertFailsWithMessage("Cannot use nested collections as primary keys or query arguments") { + realm.query("value == $0", RealmAny.create(realmSetOf())) + } + assertFailsWithMessage("Cannot use nested collections as primary keys or query arguments") { + realm.query("value == $0", RealmAny.create(realmListOf())) + } + assertFailsWithMessage("Cannot use nested collections as primary keys or query arguments") { + realm.query("value == $0", RealmAny.create(realmDictionaryOf())) + } + } +} + + +class TabbedStringBuilder() { + private val builder = StringBuilder() + internal var indentation = 0 + internal fun append(s: String) = builder.append("\t".repeat(indentation) + s + "\n") + override fun toString(): String { + return builder.toString() + } +} + +fun TabbedStringBuilder.dumpRealmAny(value: RealmAny?) { + if (value == null) { + append("null") + return + } + when (value.type) { + RealmAny.Type.SET, RealmAny.Type.LIST -> { + val collection: Collection = + if (value.type == RealmAny.Type.SET) value.asSet() else value.asList() + append("[") + indentation += 1 + collection.map { dumpRealmAny(it) } + indentation -= 1 + append("]") + } + RealmAny.Type.DICTIONARY -> value.asDictionary().let { dictionary -> + append("{") + indentation += 1 + dictionary.map { (key, element) -> + append("$key:") + indentation += 1 + dumpRealmAny(element) + indentation -= 1 + } + indentation -= 1 + append("}") + } + else -> append(value.toString()) + } +} diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyTests.kt index ca06ddc2a9..508e15d206 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyTests.kt @@ -25,9 +25,7 @@ import io.realm.kotlin.entities.embedded.EmbeddedParent import io.realm.kotlin.entities.embedded.embeddedSchema import io.realm.kotlin.ext.asRealmObject import io.realm.kotlin.ext.query -import io.realm.kotlin.ext.realmDictionaryOf import io.realm.kotlin.ext.realmListOf -import io.realm.kotlin.ext.realmSetOf import io.realm.kotlin.internal.platform.runBlocking import io.realm.kotlin.notifications.DeletedObject import io.realm.kotlin.notifications.InitialObject @@ -64,15 +62,6 @@ import kotlin.test.assertNotNull import kotlin.test.assertNull import kotlin.test.fail -class JsonStyleRealmObject : RealmObject { - var value: RealmAny? = null - -} - -class JsonParent : RealmObject { - var child: JsonStyleRealmObject? = null -} - @Suppress("LargeClass") class RealmAnyTests { @@ -86,11 +75,9 @@ class RealmAnyTests { tmpDir = PlatformUtils.createTempDir() configBuilder = RealmConfiguration.Builder( embeddedSchema + - IndexedRealmAnyContainer::class + - RealmAnyContainer::class + - Sample::class + - JsonParent::class + - JsonStyleRealmObject::class + IndexedRealmAnyContainer::class + + RealmAnyContainer::class + + Sample::class ).directory(tmpDir) configuration = configBuilder.build() realm = Realm.open(configuration) @@ -103,171 +90,6 @@ class RealmAnyTests { } PlatformUtils.deleteTempDir(tmpDir) } - @Test - fun setInMixed() = runBlocking { - val o = realm.write { - val instance = JsonParent().apply { - // Normal realm link/object reference - child = JsonStyleRealmObject().apply { - // Assigning set - // - How to prevent adding a set containing non-any elements? - // - Can we do this on the fly!? - value = RealmAny.create( - realmSetOf( - RealmAny.create(5), - RealmAny.create(4), - RealmAny.create(6) - ) - ) - } - } - copyToRealm(instance) - } - val jsonStyleRealmObject: JsonStyleRealmObject = o.child!! - val anyValue: RealmAny = jsonStyleRealmObject.value!! - assertEquals(RealmAny.Type.SET, anyValue.type) - TabbedStringBuilder().dumpRealmAny(anyValue) - } - - @Test - fun listInMixed() = runBlocking { - val o = realm.write { - val instance = JsonParent().apply { - // Normal realm link/object reference - child = JsonStyleRealmObject().apply { - // Assigning list - // - Quite verbose!? - // - How to prevent/support adding a set containing non-any elements? - // - Can we do this on the fly!? - // - Type system to allow only `RealmAny.create(list: RealmList)` - value = RealmAny.create( - realmListOf( - RealmAny.create(5), - RealmAny.create(4), - RealmAny.create(6) - ) - ) - // - Could add: - // fun realmAnyListOf(vararg: Any): RealmList - // or - // fun Iterable.toRealmAnyList(): RealmList - // to allow convenience like - // realmAnyListOf(5, "Realm", realmAnyListOf()) - // listOf(3, "Realm", realmAnyListOf()).toRealmAnyList() - // Assigning dictornary with nested lists and dictionaries -// value = RealmAny.create( -// realmDictionaryOf( -// "key1" to RealmAny.create(5), -// "key2" to RealmAny.create( -// realmListOf( -// RealmAny.create(6), -// RealmAny.create("Realm"), -// RealmAny.create( -// realmListOf( -// RealmAny.create(5) -// ) -// ) -// ) -// ), -// "key3" to RealmAny.create( -// realmDictionaryOf( -// "key31" to RealmAny.create(6), -// "key32" to RealmAny.create("Realm"), -// ) -// ), -// ) -// ) - } - } - copyToRealm(instance) - } - val jsonStyleRealmObject: JsonStyleRealmObject = o.child!! - val anyValue: RealmAny = jsonStyleRealmObject.value!! - assertEquals(RealmAny.Type.LIST, anyValue.type) - TabbedStringBuilder().dumpRealmAny(anyValue) - } - - @Test - fun dictionaryInMixed() = runBlocking { - val o = realm.write { - val instance = JsonParent().apply { - // Normal realm link/object reference - child = JsonStyleRealmObject().apply { - // Assigning dictornary with nested lists and dictionaries - value = RealmAny.create( - realmDictionaryOf( - "key1" to RealmAny.create(5), -// "key2" to RealmAny.create( -// realmListOf( -// RealmAny.create(6), -// RealmAny.create("Realm"), -// RealmAny.create( -// realmListOf( -// RealmAny.create(5) -// ) -// ) -// ) -// ), -// "key3" to RealmAny.create( -// realmDictionaryOf( -// "key31" to RealmAny.create(6), -// "key32" to RealmAny.create("Realm"), -// ) -// ), - ) - ) - } - } - copyToRealm(instance) - } - val jsonStyleRealmObject: JsonStyleRealmObject = o.child!! - val anyValue: RealmAny = jsonStyleRealmObject.value!! - assertEquals(RealmAny.Type.DICTIONARY, anyValue.type) - val tabbedStringBuilder = TabbedStringBuilder() - tabbedStringBuilder.dumpRealmAny(anyValue) - println(tabbedStringBuilder) - } - - // Importing objects with cache through a setter for nested collections - - class TabbedStringBuilder() { - private val builder = StringBuilder() - internal var indentation = 0 - internal fun append(s: String) = builder.append("\t".repeat(indentation) + s + "\n") - override fun toString(): String { - return builder.toString() - } - } - - fun TabbedStringBuilder.dumpRealmAny(value: RealmAny?) { - if (value == null) { - append("null") - return - } - when (value.type) { - RealmAny.Type.SET, RealmAny.Type.LIST -> { - val collection: Collection = if (value.type == RealmAny.Type.SET) value.asSet() else value.asList() - append("[") - indentation += 1 - collection.map { dumpRealmAny(it) } - indentation -= 1 - append("]") - } - RealmAny.Type.DICTIONARY -> value.asDictionary().let { dictionary -> - append("{") - indentation += 1 - dictionary.map { (key, element) -> - append("$key:") - indentation += 1 - dumpRealmAny(element) - indentation -= 1 - } - indentation -= 1 - append("}") - } - else -> append(value.toString()) - } - } @Test fun missingClassFromSchema_unmanagedWorks() { @@ -593,6 +415,21 @@ class RealmAnyTests { assertEquals(1, realm.query().count().find()) } + @Test + fun importWithDuplicateReference() = runBlocking { + val child = realm.write { + Sample().apply { stringField = "CHILD" } + } + realm.write { + val parent = Sample().apply { + nullableRealmAnyField = RealmAny.create(child) + nullableRealmAnyListField = realmListOf(RealmAny.create(child)) + } + copyToRealm(parent) + } + assertEquals(1, realm.query("stringField = 'CHILD'").find().size) + } + private fun assertCoreIntValuesAreTheSame( fromInt: RealmAny, fromLong: RealmAny, diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/dynamic/DynamicMutableRealmObjectTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/dynamic/DynamicMutableRealmObjectTests.kt index a62a1e9180..4cb07eec84 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/dynamic/DynamicMutableRealmObjectTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/dynamic/DynamicMutableRealmObjectTests.kt @@ -1771,4 +1771,15 @@ class DynamicMutableRealmObjectTests { assertEquals(4, size) } } + + @Test + fun throwsOnRealmAnyPrimaryKey() { + val instance = DynamicMutableRealmObject.create( + "PrimaryKeyString", + "primaryKey" to RealmAny.create("PRIMARY_KEY"), + ) + assertFailsWithMessage("Cannot use object 'RealmAny{type=STRING, value=PRIMARY_KEY}' of type 'RealmAnyImpl' as primary key argument") { + dynamicMutableRealm.copyToRealm(instance) + } + } } From b1f5b2dc683757d259ead691e2819e68862d9531 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Thu, 29 Jun 2023 15:51:13 +0200 Subject: [PATCH 03/41] Dynamic API for nested collections in RealmAny --- .../kotlin/internal/interop/RealmInterop.kt | 1 - .../io/realm/kotlin/internal/Converters.kt | 9 +- .../io/realm/kotlin/internal/RealmAnyImpl.kt | 52 ++-- .../kotlin/internal/RealmListInternal.kt | 71 +++-- .../realm/kotlin/internal/RealmMapInternal.kt | 55 ++-- .../kotlin/internal/RealmObjectHelper.kt | 33 ++- .../realm/kotlin/internal/RealmSetInternal.kt | 8 +- .../io/realm/kotlin/internal/RealmUtils.kt | 1 - .../kotlin/serializers/RealmKSerializers.kt | 2 + .../kotlin/io/realm/kotlin/types/RealmAny.kt | 2 - .../common/RealmAnyNestedCollectionTests.kt | 81 +++--- .../realm/kotlin/test/common/RealmAnyTests.kt | 6 +- .../dynamic/DynamicMutableRealmObjectTests.kt | 255 ++++++++++++++++-- .../common/dynamic/DynamicRealmObjectTests.kt | 160 ++++++++++- 14 files changed, 593 insertions(+), 143 deletions(-) diff --git a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt index 5d20a52733..5201ad2c4d 100644 --- a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt +++ b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt @@ -527,7 +527,6 @@ actual object RealmInterop { actual fun realm_list_get_dictionary(list: RealmListPointer, index: Long): RealmMapPointer = LongPointerWrapper(realmc.realm_list_get_dictionary(list.cptr(), index)) - actual fun realm_list_add(list: RealmListPointer, index: Long, transport: RealmValue) { realmc.realm_list_insert(list.cptr(), index, transport.value) } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/Converters.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/Converters.kt index e0c5a7eb27..80d7b930bf 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/Converters.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/Converters.kt @@ -20,9 +20,7 @@ import io.realm.kotlin.UpdatePolicy import io.realm.kotlin.dynamic.DynamicMutableRealmObject import io.realm.kotlin.dynamic.DynamicRealmObject import io.realm.kotlin.ext.asRealmObject -import io.realm.kotlin.internal.interop.CollectionType import io.realm.kotlin.internal.interop.MemTrackingAllocator -import io.realm.kotlin.internal.interop.RealmInterop import io.realm.kotlin.internal.interop.RealmObjectInterop import io.realm.kotlin.internal.interop.RealmQueryArgument import io.realm.kotlin.internal.interop.RealmQueryArgumentList @@ -351,7 +349,7 @@ internal object RealmValueArgumentConverter { publicToRealmValue(value) } } - // This is also hit from primary key + // This is also hit from primary key ?: throw IllegalArgumentException("Cannot use object '$value' of type '${value::class.simpleName}' as primary key argument") } } @@ -456,7 +454,7 @@ internal inline fun RealmValue.asPrimitiveRealmAnyOrElse( else -> elseBlock() } -@Suppress("OVERRIDE_BY_INLINE", "NestedBlockDepth") +@Suppress("OVERRIDE_BY_INLINE", "NestedBlockDepth", "LongParameterList") internal fun realmAnyConverter( mediator: Mediator, realmReference: RealmReference, @@ -520,8 +518,10 @@ internal fun realmAnyConverter( } /** + * // FIXME * Used for converting values to query arguments. */ +@Suppress("LongParameterList") internal inline fun MemTrackingAllocator.realmAnyToRealmValueWithImport( value: RealmAny?, mediator: Mediator, @@ -557,6 +557,7 @@ internal inline fun MemTrackingAllocator.realmAnyToRealmValue(value: RealmAny?): return when (value) { null -> nullTransport() else -> when (value.type) { + // We shouldn't be able to land here for primary key arguments! RealmAny.Type.OBJECT -> { val objRef = realmObjectToRealmReferenceOrError(value.asRealmObject()) realmObjectTransport(objRef) diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmAnyImpl.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmAnyImpl.kt index 24aed1b51b..ad881d814d 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmAnyImpl.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmAnyImpl.kt @@ -16,7 +16,6 @@ package io.realm.kotlin.internal -import io.realm.kotlin.Realm import io.realm.kotlin.UpdatePolicy import io.realm.kotlin.dynamic.DynamicMutableRealmObject import io.realm.kotlin.dynamic.DynamicRealmObject @@ -25,16 +24,11 @@ import io.realm.kotlin.internal.interop.CollectionType import io.realm.kotlin.internal.interop.MemTrackingAllocator import io.realm.kotlin.internal.interop.PropertyKey import io.realm.kotlin.internal.interop.RealmInterop -import io.realm.kotlin.internal.interop.RealmListPointer -import io.realm.kotlin.internal.interop.RealmMapPointer -import io.realm.kotlin.internal.interop.RealmPointer -import io.realm.kotlin.internal.interop.RealmSetPointer import io.realm.kotlin.types.BaseRealmObject import io.realm.kotlin.types.RealmAny import io.realm.kotlin.types.RealmDictionary import io.realm.kotlin.types.RealmInstant import io.realm.kotlin.types.RealmList -import io.realm.kotlin.types.RealmMap import io.realm.kotlin.types.RealmObject import io.realm.kotlin.types.RealmSet import io.realm.kotlin.types.RealmUUID @@ -48,9 +42,11 @@ internal sealed interface RealmAnyContainer { val mediator: Mediator val realm: RealmReference val obj: RealmObjectReference<*> - fun set(allocator: MemTrackingAllocator, value: RealmAny, - updatePolicy: UpdatePolicy = UpdatePolicy.ALL, - cache: UnmanagedToManagedObjectCache = mutableMapOf() + fun set( + allocator: MemTrackingAllocator, + value: RealmAny, + updatePolicy: UpdatePolicy = UpdatePolicy.ALL, + cache: UnmanagedToManagedObjectCache = mutableMapOf() ) { with(allocator) { when (value.type) { @@ -68,16 +64,29 @@ internal sealed interface RealmAnyContainer { setPrimitive(value) RealmAny.Type.SET -> { createCollection(CollectionType.RLM_COLLECTION_TYPE_SET) - (getSet() as ManagedRealmSet).operator.addAll(value.asSet(), updatePolicy, cache) + (getSet() as ManagedRealmSet).operator.addAll( + value.asSet(), + updatePolicy, + cache + ) } RealmAny.Type.LIST -> { createCollection(CollectionType.RLM_COLLECTION_TYPE_LIST) - (getList() as ManagedRealmList).operator.insertAll(0, value.asList(), updatePolicy, cache) + (getList() as ManagedRealmList).operator.insertAll( + 0, + value.asList(), + updatePolicy, + cache + ) } RealmAny.Type.DICTIONARY -> { createCollection(CollectionType.RLM_COLLECTION_TYPE_DICTIONARY) - (getDictionary() as ManagedRealmDictionary).operator.putAll(value.asDictionary(), updatePolicy, cache) + (getDictionary() as ManagedRealmDictionary).operator.putAll( + value.asDictionary(), + updatePolicy, + cache + ) } } } @@ -92,7 +101,9 @@ internal sealed interface RealmAnyContainer { internal class RealmAnyProperty( override val obj: RealmObjectReference<*>, - val key: PropertyKey + val key: PropertyKey, + val issueDynamicObject: Boolean, + val issueDynamicMutableObject: Boolean, ) : RealmAnyContainer { override val mediator = obj.mediator @@ -127,13 +138,19 @@ internal class RealmAnyProperty( override fun getSet(): RealmSet { val nativePointer = RealmInterop.realm_get_set(obj.objectPointer, key) - val operator = RealmAnySetOperator( mediator, realm, nativePointer ) + val operator = RealmAnySetOperator( + mediator, + realm, + nativePointer, + issueDynamicObject, + issueDynamicMutableObject + ) return ManagedRealmSet(obj, nativePointer, operator) } override fun getList(): RealmList { val nativePointer = RealmInterop.realm_get_list(obj.objectPointer, key) - val operator = RealmAnyListOperator( mediator, realm, nativePointer ) + val operator = RealmAnyListOperator(mediator, realm, nativePointer, issueDynamicObject = issueDynamicObject, issueDynamicMutableObject = issueDynamicMutableObject) val realmAnyList = ManagedRealmList(obj, nativePointer, operator) return realmAnyList } @@ -143,9 +160,10 @@ internal class RealmAnyProperty( val operator = RealmAnyMapOperator( mediator, realm, - realmAnyConverter(mediator, realm, false, false), // issueDynamicObject, issueDynamicMutableObject), + realmAnyConverter(mediator, realm, issueDynamicObject, issueDynamicMutableObject), converter(String::class, mediator, realm), - nativePointer + nativePointer, + issueDynamicObject, issueDynamicMutableObject ) val realmAnyDictionary = ManagedRealmDictionary(obj, nativePointer, operator) return realmAnyDictionary diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmListInternal.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmListInternal.kt index 2fd886c99b..f798c1bc7c 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmListInternal.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmListInternal.kt @@ -293,15 +293,18 @@ internal class PrimitiveListOperator( PrimitiveListOperator(mediator, realmReference, valueConverter, nativePointer) } +@Suppress("LongParameterList") internal class RealmAnyListOperator( override val mediator: Mediator, override val realmReference: RealmReference, override val nativePointer: RealmListPointer, - updatePolicy: UpdatePolicy = UpdatePolicy.ALL, - cache: UnmanagedToManagedObjectCache = mutableMapOf() + val updatePolicy: UpdatePolicy = UpdatePolicy.ALL, + val cache: UnmanagedToManagedObjectCache = mutableMapOf(), + val issueDynamicObject: Boolean, + val issueDynamicMutableObject: Boolean ) : ListOperator { - override val valueConverter: RealmValueConverter = realmAnyConverter(mediator, realmReference, false, false) + override val valueConverter: RealmValueConverter = realmAnyConverter(mediator, realmReference, issueDynamicObject, issueDynamicMutableObject) @Suppress("UNCHECKED_CAST") override fun get(index: Int): RealmAny? { @@ -310,20 +313,28 @@ internal class RealmAnyListOperator( val element: ValueType = transport.getType() if (element == ValueType.RLM_TYPE_SET) { val newNativePointer = RealmInterop.realm_list_get_set(nativePointer, index.toLong()) - val operator = RealmAnySetOperator( mediator, realmReference, newNativePointer ) + val operator = RealmAnySetOperator( + mediator, + realmReference, + newNativePointer, + issueDynamicObject, + issueDynamicMutableObject + ) val realmAnySet = ManagedRealmSet(null, newNativePointer, operator) RealmAny.Companion.create(realmAnySet) } else if (element == ValueType.RLM_TYPE_LIST) { val newNativePointer = RealmInterop.realm_list_get_list(nativePointer, index.toLong()) - val operator = RealmAnyListOperator( mediator, realmReference, newNativePointer ) + val operator = RealmAnyListOperator(mediator, realmReference, newNativePointer, updatePolicy, cache, issueDynamicObject, issueDynamicMutableObject) val realmAnyList = ManagedRealmList(null, newNativePointer, operator) RealmAny.Companion.create(realmAnyList) } else if (element == ValueType.RLM_TYPE_DICTIONARY) { val newNativePointer = RealmInterop.realm_list_get_dictionary(nativePointer, index.toLong()) - val operator = RealmAnyMapOperator( mediator, realmReference, - realmAnyConverter(mediator, realmReference, false, false), + val operator = RealmAnyMapOperator( + mediator, realmReference, + realmAnyConverter(mediator, realmReference, issueDynamicObject, issueDynamicMutableObject), converter(String::class, mediator, realmReference), - newNativePointer ) + newNativePointer, issueDynamicObject, issueDynamicMutableObject + ) val realmDict = ManagedRealmDictionary( null, newNativePointer, @@ -346,26 +357,34 @@ internal class RealmAnyListOperator( ) { inputScope { if (element != null && element.type in RealmAny.Type.COLLECTION_TYPES) { - when(element.type) { + when (element.type) { RealmAny.Type.SET -> { RealmInterop.realm_list_insert_collection(nativePointer, index.toLong(), CollectionType.RLM_COLLECTION_TYPE_SET) val newNativePointer = RealmInterop.realm_list_get_set(nativePointer, index.toLong()) - val operator = RealmAnySetOperator( mediator, realmReference, newNativePointer)// , updatePolicy, cache) + val operator = RealmAnySetOperator( + mediator, + realmReference, + newNativePointer, + issueDynamicObject, + issueDynamicMutableObject + ) // , updatePolicy, cache) operator.addAllInternal(element.asSet(), updatePolicy, cache) } RealmAny.Type.LIST -> { RealmInterop.realm_list_insert_collection(nativePointer, index.toLong(), CollectionType.RLM_COLLECTION_TYPE_LIST) val newNativePointer = RealmInterop.realm_list_get_list(nativePointer, index.toLong()) - val operator = RealmAnyListOperator( mediator, realmReference, newNativePointer, updatePolicy, cache) + val operator = RealmAnyListOperator(mediator, realmReference, newNativePointer, updatePolicy, cache, issueDynamicObject, issueDynamicMutableObject) operator.insertAll(0, element.asList(), updatePolicy, cache) } RealmAny.Type.DICTIONARY -> { RealmInterop.realm_list_insert_collection(nativePointer, index.toLong(), CollectionType.RLM_COLLECTION_TYPE_DICTIONARY) val newNativePointer = RealmInterop.realm_list_get_dictionary(nativePointer, index.toLong()) - val operator = RealmAnyMapOperator( mediator, realmReference, - realmAnyConverter(mediator, realmReference, false, false), - converter(String::class, mediator, realmReference), - newNativePointer ) + val operator = RealmAnyMapOperator( + mediator, realmReference, + realmAnyConverter(mediator, realmReference, issueDynamicObject, issueDynamicMutableObject), + converter(String::class, mediator, realmReference), + newNativePointer, issueDynamicObject, issueDynamicMutableObject + ) operator.putAll(element.asDictionary(), updatePolicy, cache) } else -> TODO() @@ -389,26 +408,34 @@ internal class RealmAnyListOperator( return get(index).also { inputScope { if (element != null && element.type in RealmAny.Type.COLLECTION_TYPES) { - when(element.type) { + when (element.type) { RealmAny.Type.SET -> { RealmInterop.realm_list_set_collection(nativePointer, index.toLong(), CollectionType.RLM_COLLECTION_TYPE_SET) val newNativePointer = RealmInterop.realm_list_get_set(nativePointer, index.toLong()) - val operator = RealmAnySetOperator( mediator, realmReference, newNativePointer)// , updatePolicy, cache) + val operator = RealmAnySetOperator( + mediator, + realmReference, + newNativePointer, + issueDynamicObject, + issueDynamicMutableObject + ) // , updatePolicy, cache) operator.addAllInternal(element.asSet(), updatePolicy, cache) } RealmAny.Type.LIST -> { RealmInterop.realm_list_set_collection(nativePointer, index.toLong(), CollectionType.RLM_COLLECTION_TYPE_LIST) val newNativePointer = RealmInterop.realm_list_get_list(nativePointer, index.toLong()) - val operator = RealmAnyListOperator( mediator, realmReference, newNativePointer, updatePolicy, cache) + val operator = RealmAnyListOperator(mediator, realmReference, newNativePointer, updatePolicy, cache, issueDynamicObject, issueDynamicMutableObject) operator.insertAll(0, element.asList(), updatePolicy, cache) } RealmAny.Type.DICTIONARY -> { RealmInterop.realm_list_set_collection(nativePointer, index.toLong(), CollectionType.RLM_COLLECTION_TYPE_DICTIONARY) val newNativePointer = RealmInterop.realm_list_get_dictionary(nativePointer, index.toLong()) - val operator = RealmAnyMapOperator( mediator, realmReference, - realmAnyConverter(mediator, realmReference, false, false), + val operator = RealmAnyMapOperator( + mediator, realmReference, + realmAnyConverter(mediator, realmReference, issueDynamicObject, issueDynamicMutableObject), converter(String::class, mediator, realmReference), - newNativePointer ) + newNativePointer, issueDynamicObject, issueDynamicMutableObject + ) operator.putAll(element.asDictionary(), updatePolicy, cache) } else -> TODO() @@ -427,7 +454,7 @@ internal class RealmAnyListOperator( realmReference: RealmReference, nativePointer: RealmListPointer ): ListOperator = - RealmAnyListOperator(mediator, realmReference, nativePointer) + RealmAnyListOperator(mediator, realmReference, nativePointer, issueDynamicObject = issueDynamicObject, issueDynamicMutableObject = issueDynamicMutableObject) } internal abstract class BaseRealmObjectListOperator( diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt index a665571fd9..023e09d1ac 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt @@ -28,11 +28,9 @@ import io.realm.kotlin.internal.interop.RealmChangesPointer import io.realm.kotlin.internal.interop.RealmInterop import io.realm.kotlin.internal.interop.RealmInterop.realm_dictionary_erase import io.realm.kotlin.internal.interop.RealmInterop.realm_dictionary_find -import io.realm.kotlin.internal.interop.RealmInterop.realm_dictionary_find_set import io.realm.kotlin.internal.interop.RealmInterop.realm_dictionary_get import io.realm.kotlin.internal.interop.RealmInterop.realm_dictionary_insert import io.realm.kotlin.internal.interop.RealmInterop.realm_dictionary_insert_embedded -import io.realm.kotlin.internal.interop.RealmInterop.realm_list_get import io.realm.kotlin.internal.interop.RealmInterop.realm_results_get import io.realm.kotlin.internal.interop.RealmMapPointer import io.realm.kotlin.internal.interop.RealmNotificationTokenPointer @@ -66,8 +64,7 @@ internal abstract class ManagedRealmMap constructor( internal val parent: RealmObjectReference<*>?, internal val nativePointer: RealmMapPointer, val operator: MapOperator -) : AbstractMutableMap(), RealmMap, - CoreNotifiable, MapChange>, Flowable> { +) : AbstractMutableMap(), RealmMap, CoreNotifiable, MapChange>, Flowable> { private val keysPointer by lazy { RealmInterop.realm_dictionary_get_keys(nativePointer) } private val valuesPointer by lazy { RealmInterop.realm_dictionary_to_results(nativePointer) } @@ -376,12 +373,15 @@ internal open class PrimitiveMapOperator constructor( PrimitiveMapOperator(mediator, realmReference, valueConverter, keyConverter, nativePointer) } +@Suppress("LongParameterList") internal class RealmAnyMapOperator constructor( mediator: Mediator, realmReference: RealmReference, valueConverter: RealmValueConverter, keyConverter: RealmValueConverter, - nativePointer: RealmMapPointer + nativePointer: RealmMapPointer, + val issueDynamicObject: Boolean, + val issueDynamicMutableObject: Boolean ) : PrimitiveMapOperator( mediator, realmReference, @@ -425,20 +425,31 @@ internal class RealmAnyMapOperator constructor( val element = valueTransport.getType() if (element == ValueType.RLM_TYPE_SET) { val newNativePointer = RealmInterop.realm_dictionary_find_set(nativePointer, keyTransport) - val operator = RealmAnySetOperator( mediator, realmReference, newNativePointer ) + val operator = RealmAnySetOperator( + mediator, + realmReference, + newNativePointer, + issueDynamicObject, + issueDynamicMutableObject + ) val realmAnySet = ManagedRealmSet(null, newNativePointer, operator) RealmAny.Companion.create(realmAnySet) } else if (element == ValueType.RLM_TYPE_LIST) { val newNativePointer = RealmInterop.realm_dictionary_find_list(nativePointer, keyTransport) - val operator = RealmAnyListOperator( mediator, realmReference, newNativePointer ) + val operator = RealmAnyListOperator(mediator, realmReference, newNativePointer, issueDynamicObject = issueDynamicObject, issueDynamicMutableObject = issueDynamicMutableObject) val realmAnyList = ManagedRealmList(null, newNativePointer, operator) RealmAny.Companion.create(realmAnyList) } else if (element == ValueType.RLM_TYPE_DICTIONARY) { val newNativePointer = RealmInterop.realm_dictionary_find_dictionary(nativePointer, keyTransport) - val operator = RealmAnyMapOperator( mediator, realmReference, - realmAnyConverter(mediator, realmReference, false, false), + val operator = RealmAnyMapOperator( + mediator, + realmReference, + realmAnyConverter(mediator, realmReference, issueDynamicObject, issueDynamicMutableObject), converter(String::class, mediator, realmReference), - newNativePointer ) + newNativePointer, + issueDynamicObject, + issueDynamicMutableObject + ) val realmDict = ManagedRealmDictionary( null, newNativePointer, @@ -462,11 +473,17 @@ internal class RealmAnyMapOperator constructor( return inputScope { val keyTransport = with(keyConverter) { publicToRealmValue(key) } if (value != null && value.type in RealmAny.Type.COLLECTION_TYPES) { - when(value.type) { + when (value.type) { RealmAny.Type.SET -> { RealmInterop.realm_dictionary_insert_collection(nativePointer, keyTransport, CollectionType.RLM_COLLECTION_TYPE_SET) val newNativePointer = RealmInterop.realm_dictionary_find_set(nativePointer, keyTransport) - val operator = RealmAnySetOperator( mediator, realmReference, newNativePointer)// , updatePolicy, cache) + val operator = RealmAnySetOperator( + mediator, + realmReference, + newNativePointer, + issueDynamicObject, + issueDynamicMutableObject + ) // , updatePolicy, cache) operator.addAllInternal(value.asSet(), updatePolicy, cache) // FIXME Return value for updates??!? RealmAny.create(ManagedRealmSet(null, newNativePointer, operator)) to true @@ -474,7 +491,7 @@ internal class RealmAnyMapOperator constructor( RealmAny.Type.LIST -> { RealmInterop.realm_dictionary_insert_collection(nativePointer, keyTransport, CollectionType.RLM_COLLECTION_TYPE_LIST) val newNativePointer = RealmInterop.realm_dictionary_find_list(nativePointer, keyTransport) - val operator = RealmAnyListOperator( mediator, realmReference, newNativePointer, updatePolicy, cache) + val operator = RealmAnyListOperator(mediator, realmReference, newNativePointer, updatePolicy, cache, issueDynamicObject, issueDynamicMutableObject) // FIXME Return value for updates??!? operator.insertAll(0, value.asList(), updatePolicy, cache) RealmAny.create(ManagedRealmList(null, newNativePointer, operator)) to true @@ -482,10 +499,13 @@ internal class RealmAnyMapOperator constructor( RealmAny.Type.DICTIONARY -> { RealmInterop.realm_dictionary_insert_collection(nativePointer, keyTransport, CollectionType.RLM_COLLECTION_TYPE_DICTIONARY) val newNativePointer = RealmInterop.realm_dictionary_find_dictionary(nativePointer, keyTransport) - val operator = RealmAnyMapOperator( mediator, realmReference, - realmAnyConverter(mediator, realmReference, false, false), + val operator = RealmAnyMapOperator( + mediator, realmReference, + realmAnyConverter(mediator, realmReference, issueDynamicObject, issueDynamicMutableObject), converter(String::class, mediator, realmReference), - newNativePointer ) + newNativePointer, + issueDynamicObject, issueDynamicMutableObject + ) operator.putAll(value.asDictionary(), updatePolicy, cache) // FIXME Return value for updates??!? RealmAny.create(ManagedRealmDictionary(null, nativePointer, operator)) to true @@ -749,7 +769,8 @@ internal class ManagedRealmDictionary constructor( parent: RealmObjectReference<*>?, nativePointer: RealmMapPointer, operator: MapOperator -) : ManagedRealmMap(parent, nativePointer, operator), RealmDictionary, +) : ManagedRealmMap(parent, nativePointer, operator), + RealmDictionary, Versioned by operator.realmReference { override fun freeze(frozenRealm: RealmReference): ManagedRealmDictionary? { diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt index a88160c30b..c9a46323bf 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt @@ -217,7 +217,7 @@ internal object RealmObjectHelper { ) is MutableRealmInt -> setValueTransportByKey(obj, key, longTransport(value.get())) is RealmAny -> { - RealmAnyProperty(obj, key).set(this, value) + RealmAnyProperty(obj, key, false, false).set(this, value) } else -> throw IllegalArgumentException("Unsupported value for transport: $value") } @@ -284,7 +284,7 @@ internal object RealmObjectHelper { ): RealmAny? = getterScope { val key = obj.propertyInfoOrThrow(propertyName).key getRealmValueFromKey(obj, key) - ?.let { realmValueToRealmAny(it, RealmAnyProperty(obj, key)) } + ?.let { realmValueToRealmAny(it, RealmAnyProperty(obj, key, false, false)) } } internal inline fun MemAllocator.getRealmValue( @@ -421,7 +421,9 @@ internal object RealmObjectHelper { CollectionOperatorType.REALM_ANY -> RealmAnyListOperator( mediator, realm, - listPtr + listPtr, + issueDynamicObject = issueDynamicObject, + issueDynamicMutableObject = issueDynamicMutableObject ) as ListOperator CollectionOperatorType.REALM_OBJECT -> { val classKey: ClassKey = realm.schemaMetadata.getOrThrow(propertyMetadata.linkTarget).classKey @@ -511,7 +513,9 @@ internal object RealmObjectHelper { CollectionOperatorType.REALM_ANY -> RealmAnySetOperator( mediator, realm, - setPtr + setPtr, + issueDynamicObject, + issueDynamicMutableObject ) as SetOperator CollectionOperatorType.REALM_OBJECT -> { val classKey: ClassKey = realm.schemaMetadata.getOrThrow(propertyMetadata.linkTarget).classKey @@ -598,7 +602,8 @@ internal object RealmObjectHelper { realm, realmAnyConverter(mediator, realm, issueDynamicObject, issueDynamicMutableObject), converter(String::class, mediator, realm), - dictionaryPtr + dictionaryPtr, + issueDynamicObject, issueDynamicMutableObject ) as MapOperator CollectionOperatorType.REALM_OBJECT -> { val classKey = realm.schemaMetadata.getOrThrow(propertyMetadata.linkTarget).classKey @@ -878,7 +883,7 @@ internal object RealmObjectHelper { ) RealmAny::class -> realmValueToRealmAny( transport, - RealmAnyProperty(obj, propertyInfo.key), + RealmAnyProperty(obj, propertyInfo.key, true, issueDynamicMutableObject), true, issueDynamicMutableObject ) @@ -1071,10 +1076,18 @@ internal object RealmObjectHelper { ) } else -> inputScope { - val transport = - // FIXME Lacks tests - realmAnyToRealmValueWithImport(value, obj.mediator, obj.owner, true, updatePolicy, cache) - setValueTransportByKey(obj, propertyMetadata.key, transport) + if (value == null) { + setValueTransportByKey(obj, propertyMetadata.key, nullTransport()) + } else { + val transport = + // FIXME Lacks tests + RealmAnyProperty( + obj, + propertyMetadata.key, + true, + true + ).set(this, value) + } } } } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmSetInternal.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmSetInternal.kt index 54b190bfd9..9fe7529db4 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmSetInternal.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmSetInternal.kt @@ -294,10 +294,12 @@ internal interface SetOperator : CollectionOperator { internal class RealmAnySetOperator( override val mediator: Mediator, override val realmReference: RealmReference, - override val nativePointer: RealmSetPointer + override val nativePointer: RealmSetPointer, + val issueDynamicObject: Boolean, + val issueDynamicMutableObject: Boolean ) : SetOperator { - override val valueConverter: RealmValueConverter = realmAnyConverter(mediator, realmReference, false, false) + override val valueConverter: RealmValueConverter = realmAnyConverter(mediator, realmReference, issueDynamicObject, issueDynamicMutableObject) override var modCount: Int = 0 @Suppress("UNCHECKED_CAST") @@ -341,7 +343,7 @@ internal class RealmAnySetOperator( realmReference: RealmReference, nativePointer: RealmSetPointer ): SetOperator = - PrimitiveSetOperator(mediator, realmReference, valueConverter, nativePointer) + RealmAnySetOperator(mediator, realmReference, nativePointer, issueDynamicObject, issueDynamicMutableObject) } internal class PrimitiveSetOperator( diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmUtils.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmUtils.kt index 09106489a1..c21bd44749 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmUtils.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmUtils.kt @@ -24,7 +24,6 @@ import io.realm.kotlin.ext.isManaged import io.realm.kotlin.ext.isValid import io.realm.kotlin.internal.RealmObjectHelper.assign import io.realm.kotlin.internal.RealmValueArgumentConverter.kAnyToPrimaryKeyRealmValue -import io.realm.kotlin.internal.RealmValueArgumentConverter.kAnyToRealmValue import io.realm.kotlin.internal.dynamic.DynamicUnmanagedRealmObject import io.realm.kotlin.internal.interop.ClassKey import io.realm.kotlin.internal.interop.ObjectKey diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/serializers/RealmKSerializers.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/serializers/RealmKSerializers.kt index d62a51752c..eb45bd1be8 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/serializers/RealmKSerializers.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/serializers/RealmKSerializers.kt @@ -356,6 +356,7 @@ public object RealmAnyKSerializer : KSerializer { private val serializer = SerializableRealmAny.serializer() override val descriptor: SerialDescriptor = serializer.descriptor + @Suppress("ComplexMethod") override fun deserialize(decoder: Decoder): RealmAny { return decoder.decodeSerializableValue(serializer).let { when (Type.valueOf(it.type)) { @@ -377,6 +378,7 @@ public object RealmAnyKSerializer : KSerializer { } } + @Suppress("ComplexMethod") override fun serialize(encoder: Encoder, value: RealmAny) { encoder.encodeSerializableValue( serializer, diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmAny.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmAny.kt index beb000bc72..f35e4890c5 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmAny.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmAny.kt @@ -120,8 +120,6 @@ public interface RealmAny { } } - - /** * Returns the [Type] of the `RealmAny` instance. */ diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt index 84498055a2..ace817d27e 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt @@ -115,7 +115,6 @@ class RealmAnyNestedCollectionTests { assertEquals(RealmAny.Type.SET, anyValue.type) } - @Test fun setInRealmAny_throwsOnNestedCollections_copyToRealm() = runBlocking { realm.write { @@ -144,9 +143,9 @@ class RealmAnyNestedCollectionTests { @Test fun setInRealmAny_throwsOnNestedCollections_add() = runBlocking { realm.write { - val instance = copyToRealm(JsonStyleRealmObject().apply { - value = RealmAny.create(realmSetOf()) - }) + val instance = copyToRealm( + JsonStyleRealmObject().apply { value = RealmAny.create(realmSetOf()) } + ) val set = instance.value!!.asSet() assertFailsWithMessage("Cannot add collections to RealmSets") { @@ -307,7 +306,6 @@ class RealmAnyNestedCollectionTests { ), ) } - } val anyList: RealmAny = realm.query().find().single().value!! anyList.asList().let { @@ -340,17 +338,19 @@ class RealmAnyNestedCollectionTests { realm.write { val sample = copyToRealm(Sample().apply { stringField = "SAMPLE" }) val instance = - copyToRealm(JsonStyleRealmObject().apply { - value = RealmAny.create( - realmListOf( - RealmAny.create(1), - RealmAny.create(1), - RealmAny.create(1), - RealmAny.create(1), + copyToRealm( + JsonStyleRealmObject().apply { + value = RealmAny.create( + realmListOf( + RealmAny.create(1), + RealmAny.create(1), + RealmAny.create(1), + RealmAny.create(1), + ) ) - ) - }) - instance!!.value!!.asList().run { + } + ) + instance.value!!.asList().run { // Embedded list set( 0, @@ -411,17 +411,12 @@ class RealmAnyNestedCollectionTests { @Test fun nestedCollectionsInList_set_invalidatesOldElement() = runBlocking { realm.write { - val instance = copyToRealm(JsonStyleRealmObject().apply { - value = RealmAny.create( - realmListOf( - RealmAny.create( - realmListOf( - RealmAny.create(5), - ) - ) + val instance = copyToRealm( + JsonStyleRealmObject().apply { + value = RealmAny.create( + realmListOf(RealmAny.create(realmListOf(RealmAny.create(5)))) ) - ) - } + } ) val nestedList = instance.value!!.asList()[0]!!.asList() assertEquals(5, nestedList[0]!!.asInt()) @@ -541,16 +536,17 @@ class RealmAnyNestedCollectionTests { val sample = Sample().apply { stringField = "SAMPLE" } // Import realm.write { - copyToRealm(JsonStyleRealmObject().apply { - // Assigning dictornary with nested lists and dictionaries - value = RealmAny.create( - realmDictionaryOf() - ) - }) + copyToRealm( + JsonStyleRealmObject().apply { + // Assigning dictionary with nested lists and dictionaries + value = RealmAny.create(realmDictionaryOf()) + } + ) query().find().single().value!!.asDictionary().run { put("keyInt", RealmAny.create(5)) put( - "keySet", RealmAny.create( + "keySet", + RealmAny.create( realmSetOf( RealmAny.create(5), RealmAny.create("Realm"), @@ -559,7 +555,8 @@ class RealmAnyNestedCollectionTests { ) ) put( - "keyList", RealmAny.create( + "keyList", + RealmAny.create( realmListOf( RealmAny.create(5), RealmAny.create("Realm"), @@ -620,17 +617,12 @@ class RealmAnyNestedCollectionTests { @Test fun nestedCollectionsInDictionary_put_invalidatesOldElement() = runBlocking { realm.write { - val instance = copyToRealm(JsonStyleRealmObject().apply { - value = RealmAny.create( - realmDictionaryOf( - "key" to RealmAny.create( - realmListOf( - RealmAny.create(5), - ) - ) + val instance = copyToRealm( + JsonStyleRealmObject().apply { + value = RealmAny.create( + realmDictionaryOf("key" to RealmAny.create(realmListOf(RealmAny.create(5)))) ) - ) - } + } ) val nestedList = instance.value!!.asDictionary()!!.get("key")!!.asList() assertEquals(5, nestedList[0]!!.asInt()) @@ -663,8 +655,7 @@ class RealmAnyNestedCollectionTests { } } - -class TabbedStringBuilder() { +class TabbedStringBuilder { private val builder = StringBuilder() internal var indentation = 0 internal fun append(s: String) = builder.append("\t".repeat(indentation) + s + "\n") diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyTests.kt index 508e15d206..654dc2bd44 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyTests.kt @@ -75,9 +75,9 @@ class RealmAnyTests { tmpDir = PlatformUtils.createTempDir() configBuilder = RealmConfiguration.Builder( embeddedSchema + - IndexedRealmAnyContainer::class + - RealmAnyContainer::class + - Sample::class + IndexedRealmAnyContainer::class + + RealmAnyContainer::class + + Sample::class ).directory(tmpDir) configuration = configBuilder.build() realm = Realm.open(configuration) diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/dynamic/DynamicMutableRealmObjectTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/dynamic/DynamicMutableRealmObjectTests.kt index 4cb07eec84..4566aa5bee 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/dynamic/DynamicMutableRealmObjectTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/dynamic/DynamicMutableRealmObjectTests.kt @@ -72,6 +72,7 @@ import kotlin.test.assertContentEquals import kotlin.test.assertEquals import kotlin.test.assertFailsWith import kotlin.test.assertFalse +import kotlin.test.assertIs import kotlin.test.assertNotNull import kotlin.test.assertNull import kotlin.test.assertTrue @@ -311,15 +312,24 @@ class DynamicMutableRealmObjectTests { dynamicSample.set(name, dynamicRealmAny) val expectedValue = dynamicMutableManagedObject.getValue("stringField") - val managedDynamicMutableObject = dynamicSample.getNullableValue(name) - ?.asRealmObject() - val actualValue = managedDynamicMutableObject?.getValue("stringField") + val managedDynamicMutableObject = + dynamicSample.getNullableValue(name) + ?.asRealmObject() + val actualValue = + managedDynamicMutableObject?.getValue("stringField") assertEquals(expectedValue, actualValue) // Check we did indeed get a dynamic mutable object managedDynamicMutableObject?.set("stringField", "NEW") - assertEquals("NEW", managedDynamicMutableObject?.getValue("stringField")) + assertEquals( + "NEW", + managedDynamicMutableObject?.getValue("stringField") + ) } + // Collections in RealmAny are tested in + // testSetsInRealmAny() + // testNestedCollectionsInListInRealmAny() + // testNestedCollectionsInDictionarytInRealmAny() } else -> error("Model contains untested properties: $property") } @@ -582,12 +592,16 @@ class DynamicMutableRealmObjectTests { assertEquals(1, actualList.size) val managedDynamicMutableObject = actualList[0] ?.asRealmObject() - val actualValue = managedDynamicMutableObject?.getValue("stringField") + val actualValue = + managedDynamicMutableObject?.getValue("stringField") assertEquals(expectedValue, actualValue) // Check we did indeed get a dynamic mutable object managedDynamicMutableObject?.set("stringField", "NEW") - assertEquals("NEW", managedDynamicMutableObject?.getValue("stringField")) + assertEquals( + "NEW", + managedDynamicMutableObject?.getValue("stringField") + ) } } else -> error("Model contains untested properties: $property") @@ -869,12 +883,16 @@ class DynamicMutableRealmObjectTests { assertEquals(1, actualSet.size) val managedDynamicMutableObject = actualSet.iterator().next() ?.asRealmObject() - val actualValue = managedDynamicMutableObject?.getValue("stringField") + val actualValue = + managedDynamicMutableObject?.getValue("stringField") assertEquals(expectedValue, actualValue) // Check we did indeed get a dynamic mutable object managedDynamicMutableObject?.set("stringField", "NEW") - assertEquals("NEW", managedDynamicMutableObject?.getValue("stringField")) + assertEquals( + "NEW", + managedDynamicMutableObject?.getValue("stringField") + ) } } else -> error("Model contains untested properties: $property") @@ -1062,9 +1080,13 @@ class DynamicMutableRealmObjectTests { val value = dynamicMutableRealm.copyToRealm( DynamicMutableRealmObject.create("Sample") ).set("stringField", "NEW_OBJECT") - dynamicSample.getNullableValueDictionary(property.name)["A"] = + dynamicSample.getNullableValueDictionary( + property.name + )["A"] = value - dynamicSample.getNullableValueDictionary(property.name)["B"] = + dynamicSample.getNullableValueDictionary( + property.name + )["B"] = null val nullableObjDictionary = @@ -1165,7 +1187,10 @@ class DynamicMutableRealmObjectTests { ).also { dynamicMutableUnmanagedObject -> val dynamicRealmAny = RealmAny.create(dynamicMutableUnmanagedObject) - dynamicSample.set(name, realmDictionaryOf("A" to dynamicRealmAny)) + dynamicSample.set( + name, + realmDictionaryOf("A" to dynamicRealmAny) + ) val expectedValue = dynamicMutableUnmanagedObject.getValue("stringField") val actualDictionary = @@ -1186,7 +1211,10 @@ class DynamicMutableRealmObjectTests { ).also { dynamicMutableManagedObject -> val dynamicRealmAny = RealmAny.create(dynamicMutableManagedObject) - dynamicSample.set(name, realmDictionaryOf("A" to dynamicRealmAny)) + dynamicSample.set( + name, + realmDictionaryOf("A" to dynamicRealmAny) + ) val expectedValue = dynamicMutableManagedObject.getValue("stringField") val actualDictionary = @@ -1194,12 +1222,16 @@ class DynamicMutableRealmObjectTests { assertEquals(1, actualDictionary.size) val managedDynamicMutableObject = actualDictionary["A"] ?.asRealmObject() - val actualValue = managedDynamicMutableObject?.getValue("stringField") + val actualValue = + managedDynamicMutableObject?.getValue("stringField") assertEquals(expectedValue, actualValue) // Check we did indeed get a dynamic mutable object managedDynamicMutableObject?.set("stringField", "NEW") - assertEquals("NEW", managedDynamicMutableObject?.getValue("stringField")) + assertEquals( + "NEW", + managedDynamicMutableObject?.getValue("stringField") + ) } } else -> error("Model contains untested properties: $property") @@ -1354,6 +1386,191 @@ class DynamicMutableRealmObjectTests { } } + @Test + fun testSetsInRealmAny() { + val dynamicSampleInner = dynamicMutableRealm.copyToRealm( + DynamicMutableRealmObject.create( + "Sample", + "stringField" to "INNER" + ) + ) + dynamicMutableRealm.copyToRealm( + DynamicMutableRealmObject.create( + "Sample", + "nullableRealmAnyField" to RealmAny.create( + realmSetOf( + RealmAny.create( + dynamicSampleInner + ) + ) + ) + ) + ).let { + val set = it.getNullableValue("nullableRealmAnyField")!!.asSet() + // Verify that we get mutable instances out of the set + val setObject = set.first()!!.asRealmObject() + assertIs(setObject) + assertEquals("INNER", setObject.getValue("stringField")) + setObject.set("stringField", "UPDATED_INNER") + // Verify that we can add elements to the set + set.add(RealmAny.Companion.create(dynamicSampleInner)) + // Verify that we cannot add nested collections + assertFailsWithMessage("Cannot add collections to RealmSets") { + set.add(RealmAny.Companion.create(realmSetOf())) + } + assertFailsWithMessage("Cannot add collections to RealmSets") { + set.add(RealmAny.Companion.create(realmListOf())) + } + assertFailsWithMessage("Cannot add collections to RealmSets") { + set.add(RealmAny.Companion.create(realmDictionaryOf())) + } + } + } + + @Test + fun testNestedCollectionsInListInRealmAny() { + val dynamicSampleInner = dynamicMutableRealm.copyToRealm( + DynamicMutableRealmObject.create("Sample", "stringField" to "INNER") + ) + dynamicMutableRealm.copyToRealm( + DynamicMutableRealmObject.create( + "Sample", + "nullableRealmAnyField" to RealmAny.create( + realmListOf( + RealmAny.create( + realmSetOf( + RealmAny.create( + DynamicMutableRealmObject.create( + "Sample", + "stringField" to "INNER_SET" + ) + ) + ) + ), + RealmAny.create( + realmListOf( + RealmAny.create( + DynamicMutableRealmObject.create( + "Sample", + "stringField" to "INNER_LIST" + ) + ) + ) + ), + RealmAny.create( + realmDictionaryOf( + "key" to RealmAny.create( + DynamicMutableRealmObject.create( + "Sample", + "stringField" to "INNER_DICT" + ) + ) + ) + ), + ) + ) + ) + ).let { + val list = it.getNullableValue("nullableRealmAnyField")!!.asList() + // Verify that we get mutable instances out of the set + list[0]!!.asSet().let { embeddedSet -> + val o = embeddedSet.first()!! + .asRealmObject() + assertIs(o) + assertEquals("INNER_SET", o.getValue("stringField")) + embeddedSet.add(RealmAny.Companion.create(dynamicSampleInner)) + } + list[1]!!.asList().let { embeddedList -> + val o = embeddedList.first()!! + .asRealmObject() + assertIs(o) + assertEquals("INNER_LIST", o.getValue("stringField")) + embeddedList.add(RealmAny.Companion.create(dynamicSampleInner)) + } + list[2]!!.asDictionary().let { embeddedDictionary -> + val o = embeddedDictionary["key"]!! + .asRealmObject() + assertIs(o) + assertEquals("INNER_DICT", o.getValue("stringField")) + embeddedDictionary.put("UPDATE", RealmAny.Companion.create(dynamicSampleInner)) + } + } + } + + @Test + fun testNestedCollectionsInDictionarytInRealmAny() { + val dynamicSampleInner = dynamicMutableRealm.copyToRealm( + DynamicMutableRealmObject.create( + "Sample", + "stringField" to "INNER" + ) + ) + // Collections in dictionary + dynamicMutableRealm.copyToRealm( + DynamicMutableRealmObject.create( + "Sample", + "nullableRealmAnyField" to RealmAny.create( + realmDictionaryOf( + "set" to RealmAny.create( + realmSetOf( + RealmAny.create( + DynamicMutableRealmObject.create( + "Sample", + "stringField" to "INNER_SET" + ) + ) + ) + ), + "list" to RealmAny.create( + realmListOf( + RealmAny.create( + DynamicMutableRealmObject.create( + "Sample", + "stringField" to "INNER_LIST" + ) + ) + ) + ), + "dict" to RealmAny.create( + realmDictionaryOf( + "key" to RealmAny.create( + DynamicMutableRealmObject.create( + "Sample", + "stringField" to "INNER_DICT" + ) + ) + ) + ), + ) + ) + ) + ).let { + val dict = it.getNullableValue("nullableRealmAnyField")!!.asDictionary() + // Verify that we get mutable instances out of the set + dict["set"]!!.asSet().let { embeddedSet -> + val o = embeddedSet.first()!! + .asRealmObject() + assertIs(o) + assertEquals("INNER_SET", o.getValue("stringField")) + embeddedSet.add(RealmAny.Companion.create(dynamicSampleInner)) + } + dict["list"]!!.asList().let { embeddedList -> + val o = embeddedList.first()!! + .asRealmObject() + assertIs(o) + assertEquals("INNER_LIST", o.getValue("stringField")) + embeddedList.add(RealmAny.Companion.create(dynamicSampleInner)) + } + dict["dict"]!!.asDictionary().let { embeddedDictionary -> + val o = embeddedDictionary["key"]!! + .asRealmObject() + assertIs(o) + assertEquals("INNER_DICT", o.getValue("stringField")) + embeddedDictionary.put("UPDATE", RealmAny.Companion.create(dynamicSampleInner)) + } + } + } + @Test fun set_embeddedRealmObject() { val parent = @@ -1735,7 +1952,10 @@ class DynamicMutableRealmObjectTests { "Sample", "stringField" to "intermediate", "nullableObject" to child2, - "nullableObjectDictionaryFieldNotNull" to realmDictionaryOf("A" to child2, "B" to child2) + "nullableObjectDictionaryFieldNotNull" to realmDictionaryOf( + "A" to child2, + "B" to child2 + ) ) val parent = dynamicMutableRealm.copyToRealm(DynamicMutableRealmObject.create("Sample")) parent.getObjectDictionary("nullableObjectDictionaryFieldNotNull").run { @@ -1761,7 +1981,10 @@ class DynamicMutableRealmObjectTests { "Sample", "stringField" to "intermediate", "nullableObject" to child2, - "nullableObjectDictionaryFieldNotNull" to realmDictionaryOf("A" to child2, "B" to child2) + "nullableObjectDictionaryFieldNotNull" to realmDictionaryOf( + "A" to child2, + "B" to child2 + ) ) val parent = dynamicMutableRealm.copyToRealm(DynamicMutableRealmObject.create("Sample")) parent.getObjectDictionary("nullableObjectDictionaryFieldNotNull").run { diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/dynamic/DynamicRealmObjectTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/dynamic/DynamicRealmObjectTests.kt index 4c3adbe111..695e431fa3 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/dynamic/DynamicRealmObjectTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/dynamic/DynamicRealmObjectTests.kt @@ -35,6 +35,7 @@ import io.realm.kotlin.ext.asRealmObject import io.realm.kotlin.ext.query import io.realm.kotlin.ext.realmDictionaryOf import io.realm.kotlin.ext.realmListOf +import io.realm.kotlin.ext.realmSetOf import io.realm.kotlin.ext.toRealmSet import io.realm.kotlin.internal.asDynamicRealm import io.realm.kotlin.query.RealmQuery @@ -61,6 +62,7 @@ import kotlin.test.Test import kotlin.test.assertContentEquals import kotlin.test.assertEquals import kotlin.test.assertFailsWith +import kotlin.test.assertIs import kotlin.test.assertNotNull import kotlin.test.assertNull import kotlin.test.assertTrue @@ -1541,6 +1543,55 @@ class DynamicRealmObjectTests { // This should be tested for DynamicMutableRealm instead. } + @Test + fun get_realmAny_nestedCollectionsInList() { + val unmanagedSample = Sample().apply { + nullableRealmAnyField = RealmAny.create( + realmListOf( + RealmAny.create( + realmListOf(RealmAny.create(Sample().apply { stringField = "INNER_LIST" })) + ), + RealmAny.create( + realmSetOf(RealmAny.create(Sample().apply { stringField = "INNER_SET" })) + ), + RealmAny.create( + realmDictionaryOf("key" to RealmAny.create(Sample().apply { stringField = "INNER_DICT" })) + ), + ) + ) + } + realm.writeBlocking { copyToRealm(unmanagedSample) } + realm.asDynamicRealm() + .also { dynamicRealm -> + val dynamicSample = dynamicRealm.query("Sample") + .find() + .first() + + val actualList = dynamicSample.getNullableValue( + Sample::nullableRealmAnyField.name, + RealmAny::class + )!!.asList() + + actualList[0]!!.let { innerList -> + val actualSample = innerList.asList()[0]!!.asRealmObject() + assertIs(actualSample) + assertEquals("INNER_LIST", actualSample.getValue("stringField")) + } + actualList[1]!!.let { innerSet -> + val actualSample = + innerSet.asSet()!!.first()!!.asRealmObject() + assertIs(actualSample) + assertEquals("INNER_SET", actualSample.getValue("stringField")) + } + actualList[2]!!.let { innerDictionary -> + val actualSample = + innerDictionary.asDictionary()!!["key"]!!.asRealmObject() + assertIs(actualSample) + assertEquals("INNER_DICT", actualSample.getValue("stringField")) + } + } + } + @Test fun get_realmAnySet() { val realmAnyValues = realmListOf( @@ -1550,7 +1601,7 @@ class DynamicRealmObjectTests { ) val unmanagedSample = Sample().apply { - nullableRealmAnySetField = realmAnyValues.toRealmSet() + nullableRealmAnyField = RealmAny.create(realmAnyValues.toRealmSet()) } realm.writeBlocking { copyToRealm(unmanagedSample) } realm.asDynamicRealm() @@ -1574,7 +1625,8 @@ class DynamicRealmObjectTests { if (value?.type == RealmAny.Type.OBJECT) { assertEquals( value.asRealmObject().stringField, - actual.asRealmObject().getValue("stringField") + actual.asRealmObject() + .getValue("stringField") ) assertionSucceeded = true return @@ -1602,6 +1654,61 @@ class DynamicRealmObjectTests { // This should be tested for DynamicMutableRealm instead. } + @Test + fun get_realmAnyNestedSet() { + val realmAnyValues = realmListOf( + null, + RealmAny.create("Hello"), + RealmAny.create(Sample().apply { stringField = "INNER" }), + ) + + val unmanagedSample = Sample().apply { + nullableRealmAnyField = RealmAny.create(realmAnyValues.toRealmSet()) + } + realm.writeBlocking { copyToRealm(unmanagedSample) } + realm.asDynamicRealm() + .also { dynamicRealm -> + val dynamicSample = dynamicRealm.query("Sample") + .find() + .first() + + val actualReifiedSet: RealmSet = + dynamicSample.getNullableValue( + Sample::nullableRealmAnyField.name + )!!.asSet() + + fun assertions(actual: RealmAny?) { + if (actual?.type == RealmAny.Type.OBJECT) { + var assertionSucceeded = false + for (value in realmAnyValues) { + if (value?.type == RealmAny.Type.OBJECT) { + assertEquals( + value.asRealmObject().stringField, + actual.asRealmObject() + .getValue("stringField") + ) + assertionSucceeded = true + return + } + } + assertTrue(assertionSucceeded) + } else { + assertTrue(realmAnyValues.contains(actual)) + } + } + + for (actual in actualReifiedSet) { + assertions(actual) + } + } + + // In case of testing sets we have to skip dynamic managed objects inside a + // RealmSet since the semantics prevent us from writing a DynamicRealmObject + // in this way, rightfully so. The reason for this is that we use the regular realm's + // accessors which go through the non-dynamic path so objects inside the set are expected + // to be non-dynamic - the 'issueDynamicObject' flag is always false following this path. + // This should be tested for DynamicMutableRealm instead. + } @Test fun get_realmAnyDictionary() { val realmAnyValues = realmDictionaryOf( @@ -1671,6 +1778,55 @@ class DynamicRealmObjectTests { // This should be tested for DynamicMutableRealm instead. } + @Test + fun get_realmAny_nestedCollectionsInDictionary() { + val unmanagedSample = Sample().apply { + nullableRealmAnyField = RealmAny.create( + realmDictionaryOf( + "list" to RealmAny.create( + realmListOf(RealmAny.create(Sample().apply { stringField = "INNER_LIST" })) + ), + "set" to RealmAny.create( + realmSetOf(RealmAny.create(Sample().apply { stringField = "INNER_SET" })) + ), + "dict" to RealmAny.create( + realmDictionaryOf("key" to RealmAny.create(Sample().apply { stringField = "INNER_DICT" })) + ), + ) + ) + } + realm.writeBlocking { copyToRealm(unmanagedSample) } + realm.asDynamicRealm() + .also { dynamicRealm -> + val dynamicSample = dynamicRealm.query("Sample") + .find() + .first() + + val actualDictionary = dynamicSample.getNullableValue( + Sample::nullableRealmAnyField.name, + RealmAny::class + )!!.asDictionary() + + actualDictionary["list"]!!.let { innerList -> + val actualSample = innerList.asList()[0]!!.asRealmObject() + assertIs(actualSample) + assertEquals("INNER_LIST", actualSample.getValue("stringField")) + } + actualDictionary["set"]!!.let { innerSet -> + val actualSample = + innerSet.asSet()!!.first()!!.asRealmObject() + assertIs(actualSample) + assertEquals("INNER_SET", actualSample.getValue("stringField")) + } + actualDictionary["dict"]!!.let { innerDictionary -> + val actualSample = + innerDictionary.asDictionary()!!["key"]!!.asRealmObject() + assertIs(actualSample) + assertEquals("INNER_DICT", actualSample.getValue("stringField")) + } + } + } + @Test fun get_throwsOnUnknownName() { realm.writeBlocking { From 14c44c895ee5271942d1d639a8f23a2c1e2187dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 25 Jul 2023 14:52:26 +0200 Subject: [PATCH 04/41] Add convenience methods to create set, list and dictionaries --- packages/external/core | 2 +- .../kotlin/io/realm/kotlin/ext/RealmAnyExt.kt | 46 +++++++++++++++++++ .../common/RealmAnyNestedCollectionTests.kt | 35 ++++---------- 3 files changed, 56 insertions(+), 27 deletions(-) diff --git a/packages/external/core b/packages/external/core index 26d37e2c9b..bf483aa969 160000 --- a/packages/external/core +++ b/packages/external/core @@ -1 +1 @@ -Subproject commit 26d37e2c9bd7f1db055255dbb79fc44ed4b3a93b +Subproject commit bf483aa9690622648517845add4da3342797b88d diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmAnyExt.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmAnyExt.kt index 67ef4a7827..23df9c92be 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmAnyExt.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmAnyExt.kt @@ -1,8 +1,13 @@ package io.realm.kotlin.ext +import io.realm.kotlin.dynamic.DynamicRealmObject import io.realm.kotlin.types.BaseRealmObject import io.realm.kotlin.types.RealmAny +import io.realm.kotlin.types.RealmInstant import io.realm.kotlin.types.RealmObject +import io.realm.kotlin.types.RealmUUID +import org.mongodb.kbson.Decimal128 +import org.mongodb.kbson.ObjectId /** * Creates an unmanaged `RealmAny` instance from a [BaseRealmObject] value. @@ -11,3 +16,44 @@ import io.realm.kotlin.types.RealmObject */ public inline fun RealmAny.asRealmObject(): T = asRealmObject(T::class) + +// FIXME Doc if this is public +public fun realmAnyOf(value: Any?): RealmAny? { + return when(value) { + (value == null) -> null + is Boolean -> RealmAny.create(value) + is Byte -> RealmAny.create(value) + is Char -> RealmAny.create(value) + is Short -> RealmAny.create(value) + is Int -> RealmAny.create(value) + is Long -> RealmAny.create(value) + is Float -> RealmAny.create(value) + is Double -> RealmAny.create(value) + is String -> RealmAny.create(value) + is Decimal128 -> RealmAny.create(value) + is ObjectId -> RealmAny.create(value) + is ByteArray -> RealmAny.create(value) + is RealmInstant -> RealmAny.create(value) + is RealmUUID -> RealmAny.create(value) + is RealmObject -> RealmAny.create(value) + is DynamicRealmObject -> RealmAny.create(value) + is Set<*> -> RealmAny.create(value.map { realmAnyOf(it) }.toRealmList()) + is List<*> -> RealmAny.create(value.map { realmAnyOf(it) }.toRealmList()) + is Map<*, *> -> RealmAny.create(value.map { (key, value) -> key as String to realmAnyOf(value) }.toRealmDictionary()) + is RealmAny -> value + else -> throw IllegalArgumentException("Cannot create RealmAny from '$value'") + } +} + +// FIXME Doc +public fun realmAnySetOf(vararg values: Any?): RealmAny = + RealmAny.create(values.map { realmAnyOf(it) }.toRealmSet()) + +// FIXME Doc +public fun realmAnyListOf(vararg values: Any?): RealmAny = + RealmAny.create(values.map { realmAnyOf(it) }.toRealmList()) + +// FIXME Doc +public fun realmAnyDictionaryOf(vararg values: Pair): RealmAny = + RealmAny.create(values.map { (key, value) -> key to realmAnyOf(value) } + .toRealmDictionary()) diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt index ace817d27e..dff3dd2217 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt @@ -21,6 +21,9 @@ import io.realm.kotlin.RealmConfiguration import io.realm.kotlin.entities.Sample import io.realm.kotlin.ext.asRealmObject import io.realm.kotlin.ext.query +import io.realm.kotlin.ext.realmAnyDictionaryOf +import io.realm.kotlin.ext.realmAnyListOf +import io.realm.kotlin.ext.realmAnySetOf import io.realm.kotlin.ext.realmDictionaryOf import io.realm.kotlin.ext.realmListOf import io.realm.kotlin.ext.realmSetOf @@ -544,34 +547,14 @@ class RealmAnyNestedCollectionTests { ) query().find().single().value!!.asDictionary().run { put("keyInt", RealmAny.create(5)) - put( - "keySet", - RealmAny.create( - realmSetOf( - RealmAny.create(5), - RealmAny.create("Realm"), - RealmAny.create(sample), - ) - ) - ) - put( - "keyList", - RealmAny.create( - realmListOf( - RealmAny.create(5), - RealmAny.create("Realm"), - RealmAny.create(sample) - ) - ) - ) + put("keySet", realmAnySetOf(5, "Realm", sample)) + put("keyList", realmAnyListOf(5, "Realm", sample)) put( "keyDictionary", - RealmAny.create( - realmDictionaryOf( - "keyInt" to RealmAny.create(5), - "keyString" to RealmAny.create("Realm"), - "keyObject" to RealmAny.create(sample) - ) + realmAnyDictionaryOf( + "keyInt" to 5, + "keyString" to "Realm", + "keyObject" to sample, ), ) } From ddc590fe5126ae328e32e2b852bcc9aee487e878 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Wed, 26 Jul 2023 15:46:48 +0200 Subject: [PATCH 05/41] Query and object notification tests --- .../kotlin/io/realm/kotlin/ext/RealmAnyExt.kt | 2 +- .../kotlin/internal/RealmListInternal.kt | 6 + .../common/RealmAnyNestedCollectionTests.kt | 110 +++++++++++++++--- ...ealmAnyNestedCollectionNotificationTest.kt | 96 +++++++++++++++ 4 files changed, 199 insertions(+), 15 deletions(-) create mode 100644 packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedCollectionNotificationTest.kt diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmAnyExt.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmAnyExt.kt index 23df9c92be..ba204ef0e0 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmAnyExt.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmAnyExt.kt @@ -37,7 +37,7 @@ public fun realmAnyOf(value: Any?): RealmAny? { is RealmUUID -> RealmAny.create(value) is RealmObject -> RealmAny.create(value) is DynamicRealmObject -> RealmAny.create(value) - is Set<*> -> RealmAny.create(value.map { realmAnyOf(it) }.toRealmList()) + is Set<*> -> RealmAny.create(value.map { realmAnyOf(it) }.toRealmSet()) is List<*> -> RealmAny.create(value.map { realmAnyOf(it) }.toRealmList()) is Map<*, *> -> RealmAny.create(value.map { (key, value) -> key as String to realmAnyOf(value) }.toRealmDictionary()) is RealmAny -> value diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmListInternal.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmListInternal.kt index f798c1bc7c..99bfd0307c 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmListInternal.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmListInternal.kt @@ -419,12 +419,16 @@ internal class RealmAnyListOperator( issueDynamicObject, issueDynamicMutableObject ) // , updatePolicy, cache) + // FIXME + operator.clear() operator.addAllInternal(element.asSet(), updatePolicy, cache) } RealmAny.Type.LIST -> { RealmInterop.realm_list_set_collection(nativePointer, index.toLong(), CollectionType.RLM_COLLECTION_TYPE_LIST) val newNativePointer = RealmInterop.realm_list_get_list(nativePointer, index.toLong()) val operator = RealmAnyListOperator(mediator, realmReference, newNativePointer, updatePolicy, cache, issueDynamicObject, issueDynamicMutableObject) + // FIXME + RealmInterop.realm_list_clear(newNativePointer) operator.insertAll(0, element.asList(), updatePolicy, cache) } RealmAny.Type.DICTIONARY -> { @@ -436,6 +440,8 @@ internal class RealmAnyListOperator( converter(String::class, mediator, realmReference), newNativePointer, issueDynamicObject, issueDynamicMutableObject ) + // FIXME + operator.clear() operator.putAll(element.asDictionary(), updatePolicy, cache) } else -> TODO() diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt index dff3dd2217..c1b82aae29 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt @@ -78,7 +78,7 @@ class RealmAnyNestedCollectionTests { @Test fun setInRealmAny_copyToRealm() = runBlocking { val sample = Sample().apply { stringField = "SAMPLE" } - val o = realm.write { + realm.write { val instance = JsonStyleRealmObject().apply { // Assigning set // - How to prevent adding a set containing non-any elements? @@ -99,8 +99,8 @@ class RealmAnyNestedCollectionTests { @Test fun setInRealmAny_assignment() = runBlocking { - val o = realm.write { - val sample = copyToRealm(Sample().apply { stringField = "SAMPLE" }) + realm.write { + copyToRealm(Sample().apply { stringField = "SAMPLE" }) val instance = copyToRealm(JsonStyleRealmObject()) // Assigning set // - How to prevent adding a set containing non-any elements? @@ -198,7 +198,7 @@ class RealmAnyNestedCollectionTests { @Test fun nestedCollectionsInList_copyToRealm() = runBlocking { val sample = Sample().apply { stringField = "SAMPLE" } - val o = realm.write { + realm.write { JsonStyleRealmObject().apply { value = RealmAny.create( realmListOf( @@ -462,7 +462,7 @@ class RealmAnyNestedCollectionTests { fun dictionaryInRealmAny_copyToRealm() = runBlocking { val sample = Sample().apply { stringField = "SAMPLE" } // Import - val o = realm.write { + realm.write { // Normal realm link/object reference JsonStyleRealmObject().apply { // Assigning dictornary with nested lists and dictionaries @@ -503,9 +503,7 @@ class RealmAnyNestedCollectionTests { assertEquals(RealmAny.Type.DICTIONARY, anyValue.type) anyValue.asDictionary().run { assertEquals(4, size) - get("keyInt")!!.let { - assertEquals(5, it.asInt()) - } + assertEquals(5, get("keyInt")!!.asInt()) get("keySet")!!.asSet().toMutableSet().let { embeddedSet -> assertEquals(3, embeddedSet.size) assertTrue { embeddedSet.remove(RealmAny.create(5)) } @@ -566,9 +564,7 @@ class RealmAnyNestedCollectionTests { assertEquals(RealmAny.Type.DICTIONARY, anyValue.type) anyValue.asDictionary().run { assertEquals(4, size) - get("keyInt")!!.let { - assertEquals(5, it.asInt()) - } + assertEquals(5, get("keyInt")!!.asInt()) get("keySet")!!.asSet().toMutableSet().let { embeddedSet -> assertEquals(3, embeddedSet.size) assertTrue { embeddedSet.remove(RealmAny.create(5)) } @@ -607,14 +603,14 @@ class RealmAnyNestedCollectionTests { ) } ) - val nestedList = instance.value!!.asDictionary()!!.get("key")!!.asList() + val nestedList = instance.value!!.asDictionary()["key"]!!.asList() assertEquals(5, nestedList[0]!!.asInt()) // Overwrite nested list element with new list - instance.value!!.asDictionary()!!["key"] = RealmAny.create(realmListOf(RealmAny.create(7))) + instance.value!!.asDictionary()["key"] = RealmAny.create(realmListOf(RealmAny.create(7))) // FIXME This shouldn't be true. We shouldn't have overwrite the old list // assertEquals(5, nestedList[0]!!.asInt()) // Overwrite nested list element with new collection of different type - instance.value!!.asDictionary()!!["key"] = RealmAny.create(realmSetOf(RealmAny.create(8))) + instance.value!!.asDictionary()["key"] = RealmAny.create(realmSetOf(RealmAny.create(8))) // Access the old list // FIXME Seems like we don't throw a nice error when accessing a delete collection // Overwriting with different collection type seems to ruin original item without @@ -636,6 +632,92 @@ class RealmAnyNestedCollectionTests { realm.query("value == $0", RealmAny.create(realmDictionaryOf())) } } + + @Test + fun query() = runBlocking { + realm.write { + copyToRealm(JsonStyleRealmObject().apply { + id = "SET" +// value = RealmAny.create(realmSetOf(RealmAny.create(1), RealmAny.create(2), RealmAny.create(3))) + value = realmAnySetOf(1, 2, 3) + }) + copyToRealm(JsonStyleRealmObject().apply { + id = "LIST" + value = realmAnyListOf(4, 5, 6) + }) + copyToRealm(JsonStyleRealmObject().apply { + id = "DICT" + value = realmAnyDictionaryOf( + "key1" to 7, + "key2" to 8, + "key3" to 9, + ) + }) + copyToRealm(JsonStyleRealmObject().apply { + id = "EMBEDDED" + value = realmAnyListOf( + setOf(1, 2, 3), + listOf(4, 5, 6), + mapOf( + "key1" to 7, + "key2" to 8, + "key3" to listOf(9), + ) + ) + }) + } + + assertEquals(4, realm.query().find().size) + + // Matching sets +// realm.query("value[0] == 1").find().single().run { +// assertEquals("SET", id) +// } +// realm.query("value[*] == 1").find().single().run { +// assertEquals("SET", id) +// } + // Size + // [RLM_ERR_INVALID_QUERY]: Operation '@size' is not supported on property of type 'mixed' + // assertEquals(1, realm.query("value[*].@size == 3").find().size) + + // Matching lists + realm.query("value[0] == 4").find().single().run { + assertEquals("LIST", id) + } + realm.query("value[*] == 4").find().single().run { + assertEquals("LIST", id) + } + // Size + // [RLM_ERR_INVALID_QUERY]: Operation '@size' is not supported on property of type 'mixed' + // assertEquals(1, realm.query("value[1].@size == 3").find().size) + + // Matching dictionaries + assertEquals(1, realm.query("value.key1 == 7").find().size) + assertEquals(1, realm.query("value['key1'] == 7").find().size) + assertEquals(0, realm.query("value.unknown == 3").find().size) + assertEquals(1, realm.query("value.@keys == 'key1'").find().size) + assertEquals(0, realm.query("value.@keys == 'unknown'").find().size) + + // None + assertTrue { realm.query("value[*] == 10").find().isEmpty() } + + // Matching across all elements and in nested structures +// realm.query("value[*][*] == 1").find().single().run { +// assertEquals("EMBEDDED", id) +// } + realm.query("value[*][*] == 4").find().single().run { + assertEquals("EMBEDDED", id) + } + realm.query("value[*][*] == 7").find().single().run { + assertEquals("EMBEDDED", id) + } + realm.query("value[*].@keys == 'key1'").find().single().run { + assertEquals("EMBEDDED", id) + } + realm.query("value[*].key3[0] == 9").find().single().run { + assertEquals("EMBEDDED", id) + } + } } class TabbedStringBuilder { diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedCollectionNotificationTest.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedCollectionNotificationTest.kt new file mode 100644 index 0000000000..96dc2fdfa0 --- /dev/null +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedCollectionNotificationTest.kt @@ -0,0 +1,96 @@ +/* + * Copyright 2023 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.realm.kotlin.test.common.notifications + +import io.realm.kotlin.Realm +import io.realm.kotlin.RealmConfiguration +import io.realm.kotlin.ext.asFlow +import io.realm.kotlin.ext.realmAnyListOf +import io.realm.kotlin.internal.platform.runBlocking +import io.realm.kotlin.notifications.InitialObject +import io.realm.kotlin.notifications.ObjectChange +import io.realm.kotlin.notifications.UpdatedObject +import io.realm.kotlin.test.common.JsonStyleRealmObject +import io.realm.kotlin.test.platform.PlatformUtils +import io.realm.kotlin.types.RealmAny +import kotlinx.coroutines.async +import kotlinx.coroutines.channels.Channel +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertIs + +class RealmAnyNestedCollectionNotificationTest { + + lateinit var tmpDir: String + lateinit var configuration: RealmConfiguration + lateinit var realm: Realm + + private val keys = listOf("11", "22", "33") + + @BeforeTest + fun setup() { + tmpDir = PlatformUtils.createTempDir() + configuration = RealmConfiguration.Builder( + schema = setOf(JsonStyleRealmObject::class) + ).directory(tmpDir) + .build() + realm = Realm.open(configuration) + } + + @AfterTest + fun tearDown() { + if (this::realm.isInitialized && !realm.isClosed()) { + realm.close() + } + PlatformUtils.deleteTempDir(tmpDir) + } + + @Test + fun objectNotificationsOnNestedCollections() = runBlocking { + val channel = Channel>() + + val o: JsonStyleRealmObject = realm.write { + copyToRealm(JsonStyleRealmObject().apply { + id = "SET" + value = realmAnyListOf(realmAnyListOf(1, 2, 3)) + }) + } + val listener = async { + o.asFlow().collect { + channel.send(it) + } + } + + assertIs>(channel.receive()) + + realm.write { + findLatest(o)!!.value!!.asList()[0]!!.asList()[1] = RealmAny.create(4) + } + + val objectUpdate = channel.receive() + assertIs>(objectUpdate) + objectUpdate.run { + val realmAny = obj.value + val nestedList = realmAny!!.asList().first()!!.asList() + assertEquals(listOf(1, 4, 3), nestedList.map { it!!.asInt() }) + } + listener.cancel() + channel.close() + } +} From 61ccde731c9f783ffe94525a493ca640a2376e4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Thu, 3 Aug 2023 16:10:10 +0200 Subject: [PATCH 06/41] Update test --- .../common/RealmAnyNestedCollectionTests.kt | 42 ++++++++++--------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt index c1b82aae29..b053eb9f56 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt @@ -412,29 +412,31 @@ class RealmAnyNestedCollectionTests { } @Test - fun nestedCollectionsInList_set_invalidatesOldElement() = runBlocking { + fun nestedCollectionsInList_set_invalidatesOldElement() = runBlocking { realm.write { - val instance = copyToRealm( - JsonStyleRealmObject().apply { - value = RealmAny.create( - realmListOf(RealmAny.create(realmListOf(RealmAny.create(5)))) - ) - } - ) - val nestedList = instance.value!!.asList()[0]!!.asList() + val instance = copyToRealm(JsonStyleRealmObject()) + instance.value = RealmAny.create(realmListOf(RealmAny.create(5))) + + val nestedList = instance.value!!.asList() assertEquals(5, nestedList[0]!!.asInt()) - // Overwrite nested list element with new list - instance.value!!.asList()[0] = RealmAny.create(realmListOf(RealmAny.create(7))) - // FIXME This should be true. We shouldn't have overwrite the old list -// assertEquals(5, nestedList[0]!!.asInt()) + + // FIXME Overwrite nested list element with new list just updates existing list + instance.value = realmAnyListOf(7) + assertFailsWithMessage("This is an ex-list") { + assertEquals(5, nestedList[0]!!.asInt()) + } + // Overwrite nested list element with new collection of different type - instance.value!!.asList()[0] = RealmAny.create(realmSetOf(RealmAny.create(8))) - // Access the old list - // FIXME Seems like we don't throw a nice error when accessing a delete collection - // Overwriting with different collection type seems to ruin original item without - // throwing proper fix -// val realmAny = nestedList[0] -// assertEquals(7, realmAny!!.asInt()) + instance.value = realmAnySetOf(8) + + // Update old list + assertFailsWithMessage("This is an ex-list") { + nestedList.add(RealmAny.create(1)) + } + // FIXME Throws RLM_ERR_INDEX_OUT_OF_BOUNDS instead of RLM_ERR_ILLEGAL_OPERATION + assertFailsWithMessage("This is an ex-list") { + val realmAny = nestedList[0] + } } } From 8f422a659fb9cc071d33e0abdf2638d31c98fafa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Thu, 3 Aug 2023 17:44:35 +0200 Subject: [PATCH 07/41] Notification test for set --- .../BacklinksNotificationsTests.kt | 2 +- ...ealmAnyNestedCollectionNotificationTest.kt | 214 +++++++++++++++++- .../RealmDictionaryNotificationsTests.kt | 2 +- .../RealmListNotificationsTests.kt | 2 +- .../RealmObjectNotificationsTests.kt | 2 +- .../RealmSetNotificationsTests.kt | 2 +- .../utils/RealmEntityNotificationTests.kt | 2 +- 7 files changed, 218 insertions(+), 8 deletions(-) diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/BacklinksNotificationsTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/BacklinksNotificationsTests.kt index e8d8d8a1c6..7bab7cd6c2 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/BacklinksNotificationsTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/BacklinksNotificationsTests.kt @@ -341,7 +341,7 @@ class BacklinksNotificationsTests : RealmEntityNotificationTests { } @Test - override fun asFlowOnDeleteEntity() { + override fun asFlowOnDeletedEntity() { runBlocking { val sample = realm.write { copyToRealm(Sample()) } val mutex = Mutex(true) diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedCollectionNotificationTest.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedCollectionNotificationTest.kt index 96dc2fdfa0..510132405e 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedCollectionNotificationTest.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedCollectionNotificationTest.kt @@ -18,24 +18,45 @@ package io.realm.kotlin.test.common.notifications import io.realm.kotlin.Realm import io.realm.kotlin.RealmConfiguration +import io.realm.kotlin.entities.list.RealmListContainer import io.realm.kotlin.ext.asFlow import io.realm.kotlin.ext.realmAnyListOf +import io.realm.kotlin.ext.realmAnySetOf +import io.realm.kotlin.ext.realmListOf import io.realm.kotlin.internal.platform.runBlocking +import io.realm.kotlin.notifications.DeletedList +import io.realm.kotlin.notifications.DeletedObject +import io.realm.kotlin.notifications.DeletedSet import io.realm.kotlin.notifications.InitialObject +import io.realm.kotlin.notifications.InitialSet +import io.realm.kotlin.notifications.ListChange import io.realm.kotlin.notifications.ObjectChange +import io.realm.kotlin.notifications.SetChange import io.realm.kotlin.notifications.UpdatedObject import io.realm.kotlin.test.common.JsonStyleRealmObject +import io.realm.kotlin.test.common.OBJECT_VALUES +import io.realm.kotlin.test.common.utils.RealmEntityNotificationTests import io.realm.kotlin.test.platform.PlatformUtils +import io.realm.kotlin.test.util.receiveOrFail import io.realm.kotlin.types.RealmAny +import io.realm.kotlin.types.RealmUUID import kotlinx.coroutines.async import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.takeWhile +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.withTimeout import kotlin.test.AfterTest import kotlin.test.BeforeTest +import kotlin.test.Ignore import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertIs +import kotlin.test.assertTrue +import kotlin.time.Duration.Companion.seconds + +class RealmAnyNestedCollectionNotificationTest : RealmEntityNotificationTests { -class RealmAnyNestedCollectionNotificationTest { lateinit var tmpDir: String lateinit var configuration: RealmConfiguration @@ -62,7 +83,7 @@ class RealmAnyNestedCollectionNotificationTest { } @Test - fun objectNotificationsOnNestedCollections() = runBlocking { + fun objectNotificationsOnNestedCollections() = runBlocking { val channel = Channel>() val o: JsonStyleRealmObject = realm.write { @@ -93,4 +114,193 @@ class RealmAnyNestedCollectionNotificationTest { listener.cancel() channel.close() } + + @Test + @Ignore // Initial element events are verified as part of the asFlow tests + override fun initialElement() {} + + @Test + override fun asFlow() = runBlocking { + val channel = Channel>() + + val o: JsonStyleRealmObject = realm.write { + copyToRealm(JsonStyleRealmObject().apply { + id = "SET" + value = realmAnySetOf(1, 2, 3) + }) + } + + val set = o.value!!.asSet() + assertEquals(3, set.size) + val listener = async { + set.asFlow().collect { + channel.send(it) + } + } + + channel.receive().run { + assertIs>(this) + val expectedSet = mutableSetOf(1, 2, 3) + this.set.forEach { expectedSet.remove(it!!.asInt()) } + assertTrue { expectedSet.isEmpty() } + } + + realm.write { + val liveSet = findLatest(o)!!.value!!.asSet() + liveSet.add(RealmAny.create(4)) + } + + channel.receive().run { + TODO("Missing test of updates and deletion events") + +// channel.receive().run { +// assertIs>(this) +// val expectedSet = mutableSetOf(1, 2, 3) +// this.set.forEach { expectedSet.remove(it!!.asInt()) } +// assertTrue { expectedSet.isEmpty() } +// } +// while (!listener.isCompleted) { +// realm.write { +// val x = findLatest(o) +// if (x != null) { +// val value = x.value!! +// if (value.type == RealmAny.Type.SET) { +// val asSet = value.asSet() +// if (asSet.size < 10) { +// asSet.add(RealmAny.create(RealmUUID.random())) +// } else { +// delete(x) +//// x.value = realmAnyListOf() +// } +// } +// } +// } +// } + } + listener.cancel() + channel.close() + } + + @Test + override fun cancelAsFlow() { + kotlinx.coroutines.runBlocking { + val container = realm.write { + copyToRealm(JsonStyleRealmObject().apply { value = realmAnySetOf()}) + } + val channel1 = Channel>(1) + val channel2 = Channel>(1) + val observedSet = container.value!!.asSet() + val observer1 = async { + observedSet.asFlow() + .collect { change -> + channel1.trySend(change) + } + } + val observer2 = async { + observedSet.asFlow() + .collect { change -> + channel2.trySend(change) + } + } + + // Ignore first emission with empty sets + channel1.receiveOrFail() + channel2.receiveOrFail() + + // Trigger an update + realm.write { + val queriedContainer = findLatest(container) + queriedContainer!!.value!!.asSet().add(RealmAny.Companion.create(1)) + } + assertEquals(1, channel1.receiveOrFail().set.size) + assertEquals(1, channel2.receiveOrFail().set.size) + + // Cancel observer 1 + observer1.cancel() + + // Trigger another update + realm.write { + val queriedContainer = findLatest(container) + queriedContainer!!.value!!.asSet().add(RealmAny.create(2)) + } + + // Check channel 1 didn't receive the update + assertTrue(channel1.isEmpty) + // But channel 2 did + assertEquals(2, channel2.receiveOrFail().set.size) + + observer2.cancel() + channel1.close() + channel2.close() + } + } + + // FIXME Create core issue for missing events on deletion of collections? + @Test + override fun deleteEntity() = runBlocking { + val container = + realm.write { copyToRealm(JsonStyleRealmObject().apply { value = realmAnySetOf() }) } + val mutex = Mutex(true) + val flow = async { + container.value!!.asSet().asFlow().first { + mutex.unlock() + it is DeletedSet<*> + } + } + + // Await that flow is actually running + mutex.lock() + // Update mixed value to overwrite and delete set + realm.write { + findLatest(container)!!.value = realmAnyListOf() + } + + // Await that notifier has signalled the deletion so we are certain that the entity + // has been deleted + withTimeout(10.seconds) { + flow.await() + } + } + + @Test + override fun asFlowOnDeletedEntity() = runBlocking { + val container = + realm.write { copyToRealm(JsonStyleRealmObject().apply { value = realmAnyListOf(realmAnySetOf()) }) } + val mutex = Mutex(true) + val flow = async { + container.value!!.asList()[0]!!.asSet().asFlow().first { + mutex.unlock() + it is DeletedSet<*> + } + } + + // Await that flow is actually running + mutex.lock() + // And delete containing entity + realm.write { delete(findLatest(container)!!) } + + // Await that notifier has signalled the deletion so we are certain that the entity + // has been deleted + withTimeout(10.seconds) { + flow.await() + } + + // Verify that a flow on the deleted entity will signal a deletion and complete gracefully + withTimeout(10.seconds) { + container.value!!.asSet().asFlow().collect { + assertIs>(it) + } + } + } + + @Test + @Ignore + override fun closingRealmDoesNotCancelFlows() { + TODO("Not yet implemented") + } + + @Ignore + override fun closeRealmInsideFlowThrows() { + TODO("Not yet implemented") + } } diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmDictionaryNotificationsTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmDictionaryNotificationsTests.kt index 05cee44aad..ffe28e6800 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmDictionaryNotificationsTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmDictionaryNotificationsTests.kt @@ -125,7 +125,7 @@ class RealmDictionaryNotificationsTests : RealmEntityNotificationTests { } } - override fun asFlowOnDeleteEntity() { + override fun asFlowOnDeletedEntity() { runBlocking { val container = realm.write { copyToRealm(RealmDictionaryContainer()) } val mutex = Mutex(true) diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmListNotificationsTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmListNotificationsTests.kt index 1121d93221..a25c77df5a 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmListNotificationsTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmListNotificationsTests.kt @@ -408,7 +408,7 @@ class RealmListNotificationsTests : RealmEntityNotificationTests { } @Test - override fun asFlowOnDeleteEntity() { + override fun asFlowOnDeletedEntity() { runBlocking { val container = realm.write { copyToRealm(RealmListContainer()) } val mutex = Mutex(true) diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmObjectNotificationsTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmObjectNotificationsTests.kt index 6a49bd0806..f9be48a8e4 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmObjectNotificationsTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmObjectNotificationsTests.kt @@ -241,7 +241,7 @@ class RealmObjectNotificationsTests : RealmEntityNotificationTests { } @Test - override fun asFlowOnDeleteEntity() { + override fun asFlowOnDeletedEntity() { runBlocking { val sample = realm.write { copyToRealm(Sample()) } val mutex = Mutex(true) diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmSetNotificationsTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmSetNotificationsTests.kt index f4dd164bea..f765eb882d 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmSetNotificationsTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmSetNotificationsTests.kt @@ -282,7 +282,7 @@ class RealmSetNotificationsTests : RealmEntityNotificationTests { } @Test - override fun asFlowOnDeleteEntity() { + override fun asFlowOnDeletedEntity() { runBlocking { val container = realm.write { copyToRealm(RealmSetContainer()) } val mutex = Mutex(true) diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/utils/RealmEntityNotificationTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/utils/RealmEntityNotificationTests.kt index 76a769d501..5f9b5bfa4c 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/utils/RealmEntityNotificationTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/utils/RealmEntityNotificationTests.kt @@ -29,5 +29,5 @@ interface RealmEntityNotificationTests : FlowableTests { // Verify that we emit deletion events and close the flow when registering for notifications on // an outdated entity. - fun asFlowOnDeleteEntity() + fun asFlowOnDeletedEntity() } From ee019460f772e693fe00a2f629be212571230984 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Mon, 7 Aug 2023 13:01:41 +0200 Subject: [PATCH 08/41] Minor adjustments --- .../kotlin/io/realm/kotlin/types/RealmAny.kt | 24 ++++++++++++++----- ...ealmAnyNestedCollectionNotificationTest.kt | 18 +++++--------- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmAny.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmAny.kt index f35e4890c5..449989db16 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmAny.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmAny.kt @@ -356,18 +356,30 @@ public interface RealmAny { public fun create(realmObject: DynamicRealmObject): RealmAny = RealmAnyImpl(Type.OBJECT, DynamicRealmObject::class, realmObject) - // FIXME Docs - // Directly from Set? + /** + * Creates an unmanaged `RealmAny` instance from a [RealmSet] of [RealmAny] values. + * + * To create a [RealmAny] containing a [RealmSet] of arbitrary values wrapped in [RealmAny]s + * use the [io.realm.kotlin.ext.realmAnySetOf]. + */ public fun create(value: RealmSet): RealmAny = RealmAnyImpl(Type.SET, RealmAny::class, value) - // FIXME Docs - // Directly from Collection? + /** + * Creates an unmanaged `RealmAny` instance from a [RealmList] of [RealmAny] values. + * + * To create a [RealmAny] containing a [RealmList] of arbitrary values wrapped in [RealmAny]s + * use the [io.realm.kotlin.ext.realmAnyListOf]. + */ public fun create(value: RealmList): RealmAny = RealmAnyImpl(Type.LIST, RealmAny::class, value) - // FIXME Docs - // Directly from map? + /** + * Creates an unmanaged `RealmAny` instance from a [RealmDictionary] of [RealmAny] values. + * + * To create a [RealmAny] containing a [RealmDictionary] of arbitrary values wrapped in + * [RealmAny]s use the [io.realm.kotlin.ext.realmAnyDictionaryOf]. + */ public fun create(value: RealmDictionary): RealmAny = RealmAnyImpl(Type.DICTIONARY, RealmAny::class, value) } diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedCollectionNotificationTest.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedCollectionNotificationTest.kt index 510132405e..95b01dc396 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedCollectionNotificationTest.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedCollectionNotificationTest.kt @@ -18,32 +18,24 @@ package io.realm.kotlin.test.common.notifications import io.realm.kotlin.Realm import io.realm.kotlin.RealmConfiguration -import io.realm.kotlin.entities.list.RealmListContainer import io.realm.kotlin.ext.asFlow import io.realm.kotlin.ext.realmAnyListOf import io.realm.kotlin.ext.realmAnySetOf -import io.realm.kotlin.ext.realmListOf import io.realm.kotlin.internal.platform.runBlocking -import io.realm.kotlin.notifications.DeletedList -import io.realm.kotlin.notifications.DeletedObject import io.realm.kotlin.notifications.DeletedSet import io.realm.kotlin.notifications.InitialObject import io.realm.kotlin.notifications.InitialSet -import io.realm.kotlin.notifications.ListChange import io.realm.kotlin.notifications.ObjectChange import io.realm.kotlin.notifications.SetChange import io.realm.kotlin.notifications.UpdatedObject import io.realm.kotlin.test.common.JsonStyleRealmObject -import io.realm.kotlin.test.common.OBJECT_VALUES import io.realm.kotlin.test.common.utils.RealmEntityNotificationTests import io.realm.kotlin.test.platform.PlatformUtils import io.realm.kotlin.test.util.receiveOrFail import io.realm.kotlin.types.RealmAny -import io.realm.kotlin.types.RealmUUID import kotlinx.coroutines.async import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.takeWhile import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.withTimeout import kotlin.test.AfterTest @@ -92,9 +84,10 @@ class RealmAnyNestedCollectionNotificationTest : RealmEntityNotificationTests { value = realmAnyListOf(realmAnyListOf(1, 2, 3)) }) } + val listener = async { - o.asFlow().collect { - channel.send(it) + o.asFlow().collect { change -> + channel.send(change) } } @@ -107,8 +100,9 @@ class RealmAnyNestedCollectionNotificationTest : RealmEntityNotificationTests { val objectUpdate = channel.receive() assertIs>(objectUpdate) objectUpdate.run { - val realmAny = obj.value - val nestedList = realmAny!!.asList().first()!!.asList() + assertEquals(1, changedFields.size) + assertTrue(changedFields.contains("value")) + val nestedList = obj.value!!.asList().first()!!.asList() assertEquals(listOf(1, 4, 3), nestedList.map { it!!.asInt() }) } listener.cancel() From 84c93a1f64c1b7990a81bdbeb7ef0843ad6e7171 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Mon, 7 Aug 2023 14:40:31 +0200 Subject: [PATCH 09/41] Additional tests --- .../common/RealmAnyNestedCollectionTests.kt | 37 ++++++------ ...ealmAnyNestedCollectionNotificationTest.kt | 58 ++++++++----------- .../test/mongodb/common/SyncedRealmTests.kt | 29 ++++++++++ 3 files changed, 70 insertions(+), 54 deletions(-) diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt index b053eb9f56..bad882a9f7 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt @@ -18,6 +18,7 @@ package io.realm.kotlin.test.common import io.realm.kotlin.Realm import io.realm.kotlin.RealmConfiguration +import io.realm.kotlin.entities.JsonStyleRealmObject import io.realm.kotlin.entities.Sample import io.realm.kotlin.ext.asRealmObject import io.realm.kotlin.ext.query @@ -31,18 +32,13 @@ import io.realm.kotlin.internal.platform.runBlocking import io.realm.kotlin.test.common.utils.assertFailsWithMessage import io.realm.kotlin.test.platform.PlatformUtils import io.realm.kotlin.types.RealmAny -import io.realm.kotlin.types.RealmObject +import org.mongodb.kbson.ObjectId import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue -internal class JsonStyleRealmObject : RealmObject { - var id: String = "JsonStyleRealmObject" - var value: RealmAny? = null -} - class RealmAnyNestedCollectionTests { private lateinit var configBuilder: RealmConfiguration.Builder @@ -121,7 +117,7 @@ class RealmAnyNestedCollectionTests { @Test fun setInRealmAny_throwsOnNestedCollections_copyToRealm() = runBlocking { realm.write { - JsonStyleRealmObject().apply { + JsonStyleRealmObject(ObjectId().toString()).apply { value = RealmAny.create(realmSetOf(RealmAny.create(realmListOf(RealmAny.create(5))))) }.let { @@ -129,7 +125,7 @@ class RealmAnyNestedCollectionTests { copyToRealm(it) } } - JsonStyleRealmObject().apply { + JsonStyleRealmObject(ObjectId().toString()).apply { value = RealmAny.create( realmSetOf( RealmAny.create(realmDictionaryOf("key" to RealmAny.create(5))) @@ -617,8 +613,9 @@ class RealmAnyNestedCollectionTests { // FIXME Seems like we don't throw a nice error when accessing a delete collection // Overwriting with different collection type seems to ruin original item without // throwing proper fix -// val realmAny = nestedList[0] -// assertEquals(7, realmAny!!.asInt()) + nestedList[0] = RealmAny.create(5) + val realmAny = nestedList[0] + assertEquals(7, realmAny!!.asInt()) } } @@ -639,16 +636,16 @@ class RealmAnyNestedCollectionTests { fun query() = runBlocking { realm.write { copyToRealm(JsonStyleRealmObject().apply { - id = "SET" + _id = "SET" // value = RealmAny.create(realmSetOf(RealmAny.create(1), RealmAny.create(2), RealmAny.create(3))) value = realmAnySetOf(1, 2, 3) }) copyToRealm(JsonStyleRealmObject().apply { - id = "LIST" + _id = "LIST" value = realmAnyListOf(4, 5, 6) }) copyToRealm(JsonStyleRealmObject().apply { - id = "DICT" + _id = "DICT" value = realmAnyDictionaryOf( "key1" to 7, "key2" to 8, @@ -656,7 +653,7 @@ class RealmAnyNestedCollectionTests { ) }) copyToRealm(JsonStyleRealmObject().apply { - id = "EMBEDDED" + _id = "EMBEDDED" value = realmAnyListOf( setOf(1, 2, 3), listOf(4, 5, 6), @@ -684,10 +681,10 @@ class RealmAnyNestedCollectionTests { // Matching lists realm.query("value[0] == 4").find().single().run { - assertEquals("LIST", id) + assertEquals("LIST", _id) } realm.query("value[*] == 4").find().single().run { - assertEquals("LIST", id) + assertEquals("LIST", _id) } // Size // [RLM_ERR_INVALID_QUERY]: Operation '@size' is not supported on property of type 'mixed' @@ -708,16 +705,16 @@ class RealmAnyNestedCollectionTests { // assertEquals("EMBEDDED", id) // } realm.query("value[*][*] == 4").find().single().run { - assertEquals("EMBEDDED", id) + assertEquals("EMBEDDED", _id) } realm.query("value[*][*] == 7").find().single().run { - assertEquals("EMBEDDED", id) + assertEquals("EMBEDDED", _id) } realm.query("value[*].@keys == 'key1'").find().single().run { - assertEquals("EMBEDDED", id) + assertEquals("EMBEDDED", _id) } realm.query("value[*].key3[0] == 9").find().single().run { - assertEquals("EMBEDDED", id) + assertEquals("EMBEDDED", _id) } } } diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedCollectionNotificationTest.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedCollectionNotificationTest.kt index 95b01dc396..313a10bfeb 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedCollectionNotificationTest.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedCollectionNotificationTest.kt @@ -18,8 +18,10 @@ package io.realm.kotlin.test.common.notifications import io.realm.kotlin.Realm import io.realm.kotlin.RealmConfiguration +import io.realm.kotlin.entities.JsonStyleRealmObject import io.realm.kotlin.ext.asFlow import io.realm.kotlin.ext.realmAnyListOf +import io.realm.kotlin.ext.realmAnyOf import io.realm.kotlin.ext.realmAnySetOf import io.realm.kotlin.internal.platform.runBlocking import io.realm.kotlin.notifications.DeletedSet @@ -28,7 +30,7 @@ import io.realm.kotlin.notifications.InitialSet import io.realm.kotlin.notifications.ObjectChange import io.realm.kotlin.notifications.SetChange import io.realm.kotlin.notifications.UpdatedObject -import io.realm.kotlin.test.common.JsonStyleRealmObject +import io.realm.kotlin.notifications.UpdatedSet import io.realm.kotlin.test.common.utils.RealmEntityNotificationTests import io.realm.kotlin.test.platform.PlatformUtils import io.realm.kotlin.test.util.receiveOrFail @@ -80,7 +82,7 @@ class RealmAnyNestedCollectionNotificationTest : RealmEntityNotificationTests { val o: JsonStyleRealmObject = realm.write { copyToRealm(JsonStyleRealmObject().apply { - id = "SET" + _id = "SET" value = realmAnyListOf(realmAnyListOf(1, 2, 3)) }) } @@ -119,7 +121,7 @@ class RealmAnyNestedCollectionNotificationTest : RealmEntityNotificationTests { val o: JsonStyleRealmObject = realm.write { copyToRealm(JsonStyleRealmObject().apply { - id = "SET" + _id = "SET" value = realmAnySetOf(1, 2, 3) }) } @@ -132,7 +134,7 @@ class RealmAnyNestedCollectionNotificationTest : RealmEntityNotificationTests { } } - channel.receive().run { + channel.receiveOrFail(1.seconds).run { assertIs>(this) val expectedSet = mutableSetOf(1, 2, 3) this.set.forEach { expectedSet.remove(it!!.asInt()) } @@ -144,35 +146,23 @@ class RealmAnyNestedCollectionNotificationTest : RealmEntityNotificationTests { liveSet.add(RealmAny.create(4)) } - channel.receive().run { - TODO("Missing test of updates and deletion events") - -// channel.receive().run { -// assertIs>(this) -// val expectedSet = mutableSetOf(1, 2, 3) -// this.set.forEach { expectedSet.remove(it!!.asInt()) } -// assertTrue { expectedSet.isEmpty() } -// } -// while (!listener.isCompleted) { -// realm.write { -// val x = findLatest(o) -// if (x != null) { -// val value = x.value!! -// if (value.type == RealmAny.Type.SET) { -// val asSet = value.asSet() -// if (asSet.size < 10) { -// asSet.add(RealmAny.create(RealmUUID.random())) -// } else { -// delete(x) -//// x.value = realmAnyListOf() -// } -// } -// } -// } -// } + channel.receiveOrFail(1.seconds).run { + assertIs>(this) + val expectedSet = mutableSetOf(1, 2, 3, 4) + this.set.forEach { expectedSet.remove(it!!.asInt()) } + assertTrue { expectedSet.isEmpty() } + } + + realm.write { + findLatest(o)!!.value = realmAnyOf(5) } - listener.cancel() - channel.close() + + // Fails due to missing deletion events + channel.receiveOrFail(1.seconds).run { + assertIs>(this) + } + listener.cancel() + channel.close() } @Test @@ -229,8 +219,8 @@ class RealmAnyNestedCollectionNotificationTest : RealmEntityNotificationTests { } } - // FIXME Create core issue for missing events on deletion of collections? @Test + @Ignore // Awaiting callbacks on deletion https://github.com/realm/realm-core/issues/6857 override fun deleteEntity() = runBlocking { val container = realm.write { copyToRealm(JsonStyleRealmObject().apply { value = realmAnySetOf() }) } @@ -281,7 +271,7 @@ class RealmAnyNestedCollectionNotificationTest : RealmEntityNotificationTests { // Verify that a flow on the deleted entity will signal a deletion and complete gracefully withTimeout(10.seconds) { - container.value!!.asSet().asFlow().collect { + container.value!!.asList()[0]!!.asSet().asFlow().collect { assertIs>(it) } } diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt index 38e0fa85b4..16ac683781 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt @@ -20,6 +20,7 @@ import io.realm.kotlin.LogConfiguration import io.realm.kotlin.Realm import io.realm.kotlin.RealmConfiguration import io.realm.kotlin.VersionId +import io.realm.kotlin.entities.JsonStyleRealmObject import io.realm.kotlin.entities.sync.BinaryObject import io.realm.kotlin.entities.sync.ChildPk import io.realm.kotlin.entities.sync.ParentPk @@ -28,6 +29,7 @@ import io.realm.kotlin.entities.sync.flx.FlexChildObject import io.realm.kotlin.entities.sync.flx.FlexEmbeddedObject import io.realm.kotlin.entities.sync.flx.FlexParentObject import io.realm.kotlin.ext.query +import io.realm.kotlin.ext.realmAnyListOf import io.realm.kotlin.internal.platform.fileExists import io.realm.kotlin.internal.platform.runBlocking import io.realm.kotlin.log.LogLevel @@ -1553,6 +1555,33 @@ class SyncedRealmTests { } } + @Test + fun cannotSyncCollectionsInMixed() = runBlocking { + val flexApp = TestApp( + logLevel = LogLevel.ALL, + appName = io.realm.kotlin.test.mongodb.TEST_APP_FLEX, + builder = { + it.syncRootDirectory(PlatformUtils.createTempDir("flx-sync-")) + } + ) + val (email, password) = randomEmail() to "password1234" + val user = flexApp.createUserAndLogIn(email, password) + val local = createFlexibleSyncConfig(user = user, name = "local", schema = setOf(JsonStyleRealmObject::class)) { + initialSubscriptions { + this.add(it.query()) + } + } + Realm.open(local).use { + it.write { + val obj = copyToRealm(JsonStyleRealmObject()) + assertFailsWith { + obj.value = realmAnyListOf() + } + } + } + flexApp.close() + } + // @Test // fun initialVersion() { // assertEquals(INITIAL_VERSION, realm.version()) From 88b8c58c245e3778110a9b5db5b3da5f9fb9778a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Wed, 16 Aug 2023 16:08:02 +0200 Subject: [PATCH 10/41] Notification tests for nested lists and dictionaries --- .../interop/CollectionChangeSetBuilder.kt | 3 + .../kotlin/internal/interop/RealmInterop.kt | 11 +- .../kotlin/internal/interop/RealmInterop.kt | 41 ++- .../kotlin/internal/interop/RealmInterop.kt | 4 +- packages/external/core | 2 +- packages/jni-swig-stub/realm.i | 2 +- .../io/realm/kotlin/internal/Notifiable.kt | 2 + .../kotlin/internal/RealmListInternal.kt | 20 +- .../realm/kotlin/internal/RealmMapInternal.kt | 15 +- .../kotlin/internal/RealmObjectReference.kt | 2 +- .../realm/kotlin/internal/RealmResultsImpl.kt | 2 +- .../realm/kotlin/internal/RealmSetInternal.kt | 4 +- .../kotlin/internal/SuspendableNotifier.kt | 11 +- .../kotlin/mongodb/internal/BsonEncoder.kt | 1 + .../common/RealmAnyNestedCollectionTests.kt | 71 ++++- .../realm/kotlin/test/common/RealmSetTests.kt | 9 - ...ealmAnyNestedCollectionNotificationTest.kt | 182 +----------- ...ealmAnyNestedDictionaryNotificationTest.kt | 257 +++++++++++++++++ .../RealmAnyNestedListNotificationTest.kt | 259 ++++++++++++++++++ .../RealmAnyNestedSetNotificationTest.kt | 253 +++++++++++++++++ .../test/mongodb/common/SyncedRealmTests.kt | 11 +- 21 files changed, 917 insertions(+), 245 deletions(-) create mode 100644 packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedDictionaryNotificationTest.kt create mode 100644 packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedListNotificationTest.kt create mode 100644 packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedSetNotificationTest.kt diff --git a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/CollectionChangeSetBuilder.kt b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/CollectionChangeSetBuilder.kt index ece9fdd70b..3558aa166c 100644 --- a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/CollectionChangeSetBuilder.kt +++ b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/CollectionChangeSetBuilder.kt @@ -32,6 +32,9 @@ abstract class CollectionChangeSetBuilder { var movesCount: Int = 0 + var isCleared: Boolean = false + var isDeleted: Boolean = false + fun isEmpty(): Boolean = insertionIndices.isEmpty() && modificationIndices.isEmpty() && deletionIndices.isEmpty() && diff --git a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt index 203db73188..d59edce63e 100644 --- a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt +++ b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt @@ -312,7 +312,12 @@ expect object RealmInterop { fun realm_list_insert_embedded(list: RealmListPointer, index: Long): RealmObjectPointer // Returns the element previously at the specified position fun realm_list_set(list: RealmListPointer, index: Long, inputTransport: RealmValue) - fun realm_list_insert_collection(list: RealmListPointer, index: Long, collectionType: CollectionType) + fun realm_list_insert_set(list: RealmListPointer, index: Long): RealmSetPointer + fun realm_list_insert_list(list: RealmListPointer, index: Long): RealmListPointer + fun realm_list_insert_dictionary(list: RealmListPointer, index: Long): RealmMapPointer +// fun realm_list_set_set(list: RealmListPointer, index: Long): RealmSetPointer +// fun realm_list_set_list(list: RealmListPointer, index: Long): RealmListPointer +// fun realm_list_set_dictionary(list: RealmListPointer, index: Long): RealmMapPointer fun realm_list_set_collection(list: RealmListPointer, index: Long, collectionType: CollectionType) // Returns the newly inserted element as the previous embedded element is automatically delete @@ -383,7 +388,9 @@ expect object RealmInterop { dictionary: RealmMapPointer, mapKey: RealmValue ): RealmValue - fun realm_dictionary_insert_collection(dictionary: RealmMapPointer, mapKey: RealmValue, collectionType: CollectionType) + fun realm_dictionary_insert_set(dictionary: RealmMapPointer, mapKey: RealmValue): RealmSetPointer + fun realm_dictionary_insert_list(dictionary: RealmMapPointer, mapKey: RealmValue): RealmListPointer + fun realm_dictionary_insert_dictionary(dictionary: RealmMapPointer, mapKey: RealmValue): RealmMapPointer fun realm_dictionary_get_keys(dictionary: RealmMapPointer): RealmResultsPointer fun realm_dictionary_resolve_in( dictionary: RealmMapPointer, diff --git a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt index 7771fd0779..0658f1ca22 100644 --- a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt +++ b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt @@ -538,9 +538,24 @@ actual object RealmInterop { actual fun realm_list_insert_embedded(list: RealmListPointer, index: Long): RealmObjectPointer { return LongPointerWrapper(realmc.realm_list_insert_embedded(list.cptr(), index)) } - actual fun realm_list_insert_collection(list: RealmListPointer, index: Long, collectionType: CollectionType) { - realmc.realm_list_insert_collection(list.cptr(), index, collectionType.nativeValue) - } + actual fun realm_list_insert_set(list: RealmListPointer, index: Long): RealmSetPointer { + return LongPointerWrapper(realmc.realm_list_insert_set(list.cptr(), index)) + } + actual fun realm_list_insert_list(list: RealmListPointer, index: Long): RealmListPointer { + return LongPointerWrapper(realmc.realm_list_insert_list(list.cptr(), index)) + } + actual fun realm_list_insert_dictionary(list: RealmListPointer, index: Long): RealmMapPointer { + return LongPointerWrapper(realmc.realm_list_insert_dictionary(list.cptr(), index)) + } +// actual fun realm_list_set_set(list: RealmListPointer, index: Long): RealmSetPointer { +// return LongPointerWrapper(realmc.realm_list_set_collection(list.cptr(), index)) +// } +// actual fun realm_list_set_list(list: RealmListPointer, index: Long): RealmListPointer { +// return LongPointerWrapper(realmc.realm_list_set_list(list.cptr(), index)) +// } +// actual fun realm_list_set_dictionary(list: RealmListPointer, index: Long): RealmMapPointer { +// return LongPointerWrapper(realmc.realm_list_set_dictionary(list.cptr(), index)) +// } actual fun realm_list_set_collection(list: RealmListPointer, index: Long, collectionType: CollectionType) { realmc.realm_list_set_collection(list.cptr(), index, collectionType.nativeValue) } @@ -787,8 +802,16 @@ actual object RealmInterop { } ) } - actual fun realm_dictionary_insert_collection(dictionary: RealmMapPointer, mapKey: RealmValue, collectionType: CollectionType) { - realmc.realm_dictionary_insert_collection(dictionary.cptr(), mapKey.value, collectionType.nativeValue) + actual fun realm_dictionary_insert_set(dictionary: RealmMapPointer, mapKey: RealmValue): RealmSetPointer { + return LongPointerWrapper(realmc.realm_dictionary_insert_set(dictionary.cptr(), mapKey.value)) + } + + actual fun realm_dictionary_insert_list(dictionary: RealmMapPointer, mapKey: RealmValue): RealmListPointer { + return LongPointerWrapper(realmc.realm_dictionary_insert_list(dictionary.cptr(), mapKey.value)) + } + + actual fun realm_dictionary_insert_dictionary(dictionary: RealmMapPointer, mapKey: RealmValue): RealmMapPointer { + return LongPointerWrapper(realmc.realm_dictionary_insert_dictionary(dictionary.cptr(), mapKey.value)) } actual fun realm_dictionary_get_keys(dictionary: RealmMapPointer): RealmResultsPointer { @@ -926,6 +949,7 @@ actual object RealmInterop { val modificationCount = LongArray(1) val movesCount = LongArray(1) val collectionWasCleared = BooleanArray(1) + val collectionWasDeleted = BooleanArray(1) realmc.realm_collection_changes_get_num_changes( change.cptr(), @@ -933,7 +957,8 @@ actual object RealmInterop { insertionCount, modificationCount, movesCount, - collectionWasCleared + collectionWasCleared, + collectionWasDeleted, ) val insertionIndices: LongArray = initIndicesArray(insertionCount) @@ -1017,11 +1042,13 @@ actual object RealmInterop { val deletions = longArrayOf(0) val insertions = longArrayOf(0) val modifications = longArrayOf(0) + val collectionWasDeleted = BooleanArray(1) realmc.realm_dictionary_get_changes( change.cptr(), deletions, insertions, - modifications + modifications, + collectionWasDeleted, ) val deletionStructs = realmc.new_valueArray(deletions[0].toInt()) diff --git a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt b/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt index 96a5d86e00..dd4b2a469b 100644 --- a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt +++ b/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt @@ -1771,6 +1771,7 @@ actual object RealmInterop { val modificationCount = allocArray(1) val movesCount = allocArray(1) val collectionWasErased = alloc() + val collectionWasDeleted = alloc() realm_wrapper.realm_collection_changes_get_num_changes( change.cptr(), @@ -1778,7 +1779,8 @@ actual object RealmInterop { insertionCount, modificationCount, movesCount, - collectionWasErased.ptr + collectionWasErased.ptr, + collectionWasDeleted.ptr, ) val deletionIndices = initArray(deletionCount) diff --git a/packages/external/core b/packages/external/core index 05125c3e99..23dcc34376 160000 --- a/packages/external/core +++ b/packages/external/core @@ -1 +1 @@ -Subproject commit 05125c3e99151d75dd3d0b8e4f74dcc0ba7d6b01 +Subproject commit 23dcc34376b160234e30332baf2ef7652087d132 diff --git a/packages/jni-swig-stub/realm.i b/packages/jni-swig-stub/realm.i index f88e093eef..ecd6356347 100644 --- a/packages/jni-swig-stub/realm.i +++ b/packages/jni-swig-stub/realm.i @@ -357,7 +357,7 @@ bool realm_object_is_valid(const realm_object_t*); %apply bool* OUTPUT { bool* out_found, bool* did_create, bool* did_delete_realm, bool* out_inserted, bool* erased, bool* out_erased, bool* did_refresh, bool* did_run, bool* found, bool* out_collection_was_cleared, bool* did_compact, - bool* collection_was_cleared}; + bool* collection_was_cleared, bool* out_collection_was_deleted, bool* out_was_deleted}; // uint64_t output parameter for realm_get_num_versions %apply int64_t* OUTPUT { uint64_t* out_versions_count }; diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/Notifiable.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/Notifiable.kt index 8ce71fd03c..ba8813bc8f 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/Notifiable.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/Notifiable.kt @@ -135,4 +135,6 @@ internal interface CoreNotifiable : Notifiable, Observable, Ve // Default implementation as all Observables are just thawing themselves. override fun notifiable(): Notifiable = this override fun coreObservable(liveRealm: LiveRealm): CoreNotifiable? = thaw(liveRealm.realmReference) + + fun isValid(): Boolean } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmListInternal.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmListInternal.kt index b98fbb6146..1ac6fdc28c 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmListInternal.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmListInternal.kt @@ -25,6 +25,7 @@ import io.realm.kotlin.internal.interop.CollectionType import io.realm.kotlin.internal.interop.RealmChangesPointer import io.realm.kotlin.internal.interop.RealmInterop import io.realm.kotlin.internal.interop.RealmInterop.realm_list_get +import io.realm.kotlin.internal.interop.RealmInterop.realm_list_is_valid import io.realm.kotlin.internal.interop.RealmInterop.realm_list_set_embedded import io.realm.kotlin.internal.interop.RealmListPointer import io.realm.kotlin.internal.interop.RealmNotificationTokenPointer @@ -143,7 +144,7 @@ internal class ManagedRealmList( RealmListChangeFlow(scope) // TODO from LifeCycle interface - internal fun isValid(): Boolean = + override fun isValid(): Boolean = !nativePointer.isReleased() && RealmInterop.realm_list_is_valid(nativePointer) override fun delete() = RealmInterop.realm_list_remove_all(nativePointer) @@ -182,7 +183,7 @@ internal fun ManagedRealmList.query( throw IllegalArgumentException(e.message, e.cause) } } - if (parent == null) TODO() + if (parent == null) error("Cannot perform subqueries on non-object lists") return ObjectBoundQuery( parent, ObjectQuery( @@ -367,8 +368,7 @@ internal class RealmAnyListOperator( if (element != null && element.type in RealmAny.Type.COLLECTION_TYPES) { when (element.type) { RealmAny.Type.SET -> { - RealmInterop.realm_list_insert_collection(nativePointer, index.toLong(), CollectionType.RLM_COLLECTION_TYPE_SET) - val newNativePointer = RealmInterop.realm_list_get_set(nativePointer, index.toLong()) + val newNativePointer = RealmInterop.realm_list_insert_set(nativePointer, index.toLong()) val operator = RealmAnySetOperator( mediator, realmReference, @@ -379,14 +379,12 @@ internal class RealmAnyListOperator( operator.addAllInternal(element.asSet(), updatePolicy, cache) } RealmAny.Type.LIST -> { - RealmInterop.realm_list_insert_collection(nativePointer, index.toLong(), CollectionType.RLM_COLLECTION_TYPE_LIST) - val newNativePointer = RealmInterop.realm_list_get_list(nativePointer, index.toLong()) + val newNativePointer = RealmInterop.realm_list_insert_list(nativePointer, index.toLong()) val operator = RealmAnyListOperator(mediator, realmReference, newNativePointer, updatePolicy, cache, issueDynamicObject, issueDynamicMutableObject) operator.insertAll(0, element.asList(), updatePolicy, cache) } RealmAny.Type.DICTIONARY -> { - RealmInterop.realm_list_insert_collection(nativePointer, index.toLong(), CollectionType.RLM_COLLECTION_TYPE_DICTIONARY) - val newNativePointer = RealmInterop.realm_list_get_dictionary(nativePointer, index.toLong()) + val newNativePointer = RealmInterop.realm_list_insert_dictionary(nativePointer, index.toLong()) val operator = RealmAnyMapOperator( mediator, realmReference, realmAnyConverter(mediator, realmReference, issueDynamicObject, issueDynamicMutableObject), @@ -427,16 +425,12 @@ internal class RealmAnyListOperator( issueDynamicObject, issueDynamicMutableObject ) // , updatePolicy, cache) - // FIXME - operator.clear() operator.addAllInternal(element.asSet(), updatePolicy, cache) } RealmAny.Type.LIST -> { RealmInterop.realm_list_set_collection(nativePointer, index.toLong(), CollectionType.RLM_COLLECTION_TYPE_LIST) val newNativePointer = RealmInterop.realm_list_get_list(nativePointer, index.toLong()) val operator = RealmAnyListOperator(mediator, realmReference, newNativePointer, updatePolicy, cache, issueDynamicObject, issueDynamicMutableObject) - // FIXME - RealmInterop.realm_list_clear(newNativePointer) operator.insertAll(0, element.asList(), updatePolicy, cache) } RealmAny.Type.DICTIONARY -> { @@ -448,8 +442,6 @@ internal class RealmAnyListOperator( converter(String::class, mediator, realmReference), newNativePointer, issueDynamicObject, issueDynamicMutableObject ) - // FIXME - operator.clear() operator.putAll(element.asDictionary(), updatePolicy, cache) } else -> TODO() diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt index 023e09d1ac..9b7538ca17 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt @@ -112,7 +112,7 @@ internal abstract class ManagedRealmMap constructor( ): RealmNotificationTokenPointer = RealmInterop.realm_dictionary_add_notification_callback(nativePointer, callback) - internal fun isValid(): Boolean = + override fun isValid(): Boolean = !nativePointer.isReleased() && RealmInterop.realm_dictionary_is_valid(nativePointer) // TODO add equals and hashCode and tests for those. Observe this constrain @@ -130,7 +130,7 @@ internal fun ManagedRealmMap.query( val mapValues = values as RealmMapValues<*, *> RealmInterop.realm_query_parse_for_results(mapValues.resultsPointer, query, queryArgs) } - if (parent == null) TODO() + if (parent == null) error("Cannot perform subqueries on non-object dictionaries") return ObjectBoundQuery( parent, ObjectQuery( @@ -475,8 +475,7 @@ internal class RealmAnyMapOperator constructor( if (value != null && value.type in RealmAny.Type.COLLECTION_TYPES) { when (value.type) { RealmAny.Type.SET -> { - RealmInterop.realm_dictionary_insert_collection(nativePointer, keyTransport, CollectionType.RLM_COLLECTION_TYPE_SET) - val newNativePointer = RealmInterop.realm_dictionary_find_set(nativePointer, keyTransport) + val newNativePointer = RealmInterop.realm_dictionary_insert_set(nativePointer, keyTransport) val operator = RealmAnySetOperator( mediator, realmReference, @@ -489,16 +488,14 @@ internal class RealmAnyMapOperator constructor( RealmAny.create(ManagedRealmSet(null, newNativePointer, operator)) to true } RealmAny.Type.LIST -> { - RealmInterop.realm_dictionary_insert_collection(nativePointer, keyTransport, CollectionType.RLM_COLLECTION_TYPE_LIST) - val newNativePointer = RealmInterop.realm_dictionary_find_list(nativePointer, keyTransport) + val newNativePointer = RealmInterop.realm_dictionary_insert_list(nativePointer, keyTransport) val operator = RealmAnyListOperator(mediator, realmReference, newNativePointer, updatePolicy, cache, issueDynamicObject, issueDynamicMutableObject) // FIXME Return value for updates??!? operator.insertAll(0, value.asList(), updatePolicy, cache) RealmAny.create(ManagedRealmList(null, newNativePointer, operator)) to true } RealmAny.Type.DICTIONARY -> { - RealmInterop.realm_dictionary_insert_collection(nativePointer, keyTransport, CollectionType.RLM_COLLECTION_TYPE_DICTIONARY) - val newNativePointer = RealmInterop.realm_dictionary_find_dictionary(nativePointer, keyTransport) + val newNativePointer = RealmInterop.realm_dictionary_insert_dictionary(nativePointer, keyTransport) val operator = RealmAnyMapOperator( mediator, realmReference, realmAnyConverter(mediator, realmReference, issueDynamicObject, issueDynamicMutableObject), @@ -797,7 +794,7 @@ internal class ManagedRealmDictionary constructor( owner.version().version, RealmInterop.realm_object_get_key(parent.objectPointer).key ) - } ?: TODO() + } ?: Triple("null", "null", -1) return "RealmDictionary{size=$size,owner=$owner,objKey=$objKey,version=$version}" } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectReference.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectReference.kt index e282065338..938b575d91 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectReference.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectReference.kt @@ -141,7 +141,7 @@ public class RealmObjectReference( objectPointer.let { RealmInterop.realm_object_delete(it) } } - internal fun isValid(): Boolean { + override fun isValid(): Boolean { val ptr = objectPointer return if (ptr != null) { !ptr.isReleased() && RealmInterop.realm_object_is_valid(ptr) diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmResultsImpl.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmResultsImpl.kt index 87dab43f86..fe82812f62 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmResultsImpl.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmResultsImpl.kt @@ -143,7 +143,7 @@ internal class RealmResultsImpl constructor( override fun realmState(): RealmState = realm - internal fun isValid(): Boolean { + override fun isValid(): Boolean { return !nativePointer.isReleased() && !realm.isClosed() } } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmSetInternal.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmSetInternal.kt index 86a2c0f8ae..cf1647cfc9 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmSetInternal.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmSetInternal.kt @@ -190,7 +190,7 @@ internal class ManagedRealmSet constructor( RealmInterop.realm_set_remove_all(nativePointer) } - internal fun isValid(): Boolean { + override fun isValid(): Boolean { return !nativePointer.isReleased() && RealmInterop.realm_set_is_valid(nativePointer) } } @@ -208,7 +208,7 @@ internal fun ManagedRealmSet.query( queryArgs ) } - if (parent == null) TODO() + if (parent == null) error("Cannot perform subqueries on non-object sets") return ObjectBoundQuery( parent, ObjectQuery( diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/SuspendableNotifier.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/SuspendableNotifier.kt index dbb91ad499..50d22c807a 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/SuspendableNotifier.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/SuspendableNotifier.kt @@ -111,7 +111,16 @@ internal class SuspendableNotifier( override fun onChange(change: RealmChangesPointer) { // Notifications need to be delivered with the version they where created on, otherwise // the fine-grained notification data might be out of sync. - val frozenObservable = lifeRef.freeze(realm.gcTrackedSnapshot()) + // TODO Currently verifying that lifeRef is still valid to indicate + // if it was actually deleted. This is only a problem for + // collections as they seemed to be freezable from a delete + // reference (contrary to other objects that returns null from + // freeze). An `out_collection_was_deleted` flag was added to the + // change object, which would probably be the way to go, but + // requires rework of our change set build infrastructure. + val frozenObservable: T? = if (lifeRef.isValid()) + lifeRef.freeze(realm.gcTrackedSnapshot()) + else null changeFlow.emit(frozenObservable, change) } } diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/BsonEncoder.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/BsonEncoder.kt index f30dc83ecb..c676fbbbcf 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/BsonEncoder.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/BsonEncoder.kt @@ -246,6 +246,7 @@ internal object BsonEncoder { RealmAny.Type.OBJECT_ID -> asObjectId() RealmAny.Type.UUID -> asRealmUUID() RealmAny.Type.OBJECT -> asRealmObject() + else -> TODO("Unsupported type $type") } ) diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt index bad882a9f7..c929b7d333 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt @@ -413,26 +413,67 @@ class RealmAnyNestedCollectionTests { val instance = copyToRealm(JsonStyleRealmObject()) instance.value = RealmAny.create(realmListOf(RealmAny.create(5))) + // Store local reference to existing list val nestedList = instance.value!!.asList() - assertEquals(5, nestedList[0]!!.asInt()) + // Accessing returns excepted value 5 + nestedList[0]!!.asInt() - // FIXME Overwrite nested list element with new list just updates existing list + // Overwriting with new list instance.value = realmAnyListOf(7) - assertFailsWithMessage("This is an ex-list") { - assertEquals(5, nestedList[0]!!.asInt()) - } + // Accessing original orphaned list return 7 from the new instance, but expected ILLEGAL_STATE_EXCEPTION["List is no longer valid"] + nestedList[0]!!.asInt() - // Overwrite nested list element with new collection of different type - instance.value = realmAnySetOf(8) + // Overwriting with null value + instance.value = null + // Throws excepted ILLEGAL_STATE_EXCEPTION["List is no longer valid"] + nestedList[0]!!.asInt() - // Update old list - assertFailsWithMessage("This is an ex-list") { - nestedList.add(RealmAny.create(1)) - } - // FIXME Throws RLM_ERR_INDEX_OUT_OF_BOUNDS instead of RLM_ERR_ILLEGAL_OPERATION - assertFailsWithMessage("This is an ex-list") { - val realmAny = nestedList[0] - } + // Updating to a new list + instance.value = realmAnyListOf(7) + // Accessing original orphaned list return 7 from the new instance again, but expected ILLEGAL_STATE_EXCEPTION["List is no longer valid"] + nestedList[0]!!.asInt() + +// assertFailsWithMessage("List is no longer valid") { +// nestedList[0]!!.asInt() +// } + + +// assertFailsWithMessage("List is no longer valid") { +// nestedList[0]!!.asInt() +// +// } +// +// // Overwrite with null value +// instance.value = null +// assertFailsWithMessage("List is no longer valid") { +// nestedList[0]!!.asInt() +// } +// +// // Overwrite with another list suddently revives the old list +// instance.value = realmAnyListOf(5) +// assertFailsWithMessage("List is no longer valid") { +// nestedList[0]!!.asInt() +// } +// +// +// instance.value = realmAnyListOf() +// println("Updated empty") +// assertFailsWithMessage("List is no longer valid") { +// println("Assert empty") +// nestedList[0]!!.asInt() +// } +// +// // Overwrite nested list element with new collection of different type +// instance.value = realmAnySetOf(8) +// +// // Update old list +// assertFailsWithMessage("List is no longer valid") { +// nestedList.add(RealmAny.create(1)) +// } +// // FIXME Throws RLM_ERR_INDEX_OUT_OF_BOUNDS instead of RLM_ERR_ILLEGAL_OPERATION +// assertFailsWithMessage("List is no longer valid") { +// val realmAny = nestedList[0] +// } } } diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmSetTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmSetTests.kt index 437ff45ebc..dd5fb2aebf 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmSetTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmSetTests.kt @@ -800,9 +800,6 @@ internal abstract class ManagedSetTester( } override fun removeAll() { - // TODO https://github.com/realm/realm-kotlin/issues/1097 - // Ignore RealmObject: structural equality cannot be assessed for this type when removing - // elements from the set if (classifier != RealmObject::class) { val dataSet = typeSafetyManager.dataSetToLoad @@ -812,9 +809,6 @@ internal abstract class ManagedSetTester( set.addAll(dataSet) assertTrue(set.removeAll(dataSet)) - // TODO https://github.com/realm/realm-kotlin/issues/1097 - // If the RealmAny instance contains an object it will NOT be removed until - // the issue above is fixed if (classifier == RealmAny::class) { assertEquals(1, set.size) } else { @@ -826,9 +820,6 @@ internal abstract class ManagedSetTester( assertContainerAndCleanup { container -> val set = typeSafetyManager.getCollection(container) - // TODO https://github.com/realm/realm-kotlin/issues/1097 - // If the RealmAny instance contains an object it will NOT be removed until - // the issue above is fixed if (classifier == RealmAny::class) { assertEquals(1, set.size) } else { diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedCollectionNotificationTest.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedCollectionNotificationTest.kt index 313a10bfeb..90862094ac 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedCollectionNotificationTest.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedCollectionNotificationTest.kt @@ -49,15 +49,13 @@ import kotlin.test.assertIs import kotlin.test.assertTrue import kotlin.time.Duration.Companion.seconds -class RealmAnyNestedCollectionNotificationTest : RealmEntityNotificationTests { +class RealmAnyNestedCollectionNotificationTest { lateinit var tmpDir: String lateinit var configuration: RealmConfiguration lateinit var realm: Realm - private val keys = listOf("11", "22", "33") - @BeforeTest fun setup() { tmpDir = PlatformUtils.createTempDir() @@ -110,181 +108,5 @@ class RealmAnyNestedCollectionNotificationTest : RealmEntityNotificationTests { listener.cancel() channel.close() } - - @Test - @Ignore // Initial element events are verified as part of the asFlow tests - override fun initialElement() {} - - @Test - override fun asFlow() = runBlocking { - val channel = Channel>() - - val o: JsonStyleRealmObject = realm.write { - copyToRealm(JsonStyleRealmObject().apply { - _id = "SET" - value = realmAnySetOf(1, 2, 3) - }) - } - - val set = o.value!!.asSet() - assertEquals(3, set.size) - val listener = async { - set.asFlow().collect { - channel.send(it) - } - } - - channel.receiveOrFail(1.seconds).run { - assertIs>(this) - val expectedSet = mutableSetOf(1, 2, 3) - this.set.forEach { expectedSet.remove(it!!.asInt()) } - assertTrue { expectedSet.isEmpty() } - } - - realm.write { - val liveSet = findLatest(o)!!.value!!.asSet() - liveSet.add(RealmAny.create(4)) - } - - channel.receiveOrFail(1.seconds).run { - assertIs>(this) - val expectedSet = mutableSetOf(1, 2, 3, 4) - this.set.forEach { expectedSet.remove(it!!.asInt()) } - assertTrue { expectedSet.isEmpty() } - } - - realm.write { - findLatest(o)!!.value = realmAnyOf(5) - } - - // Fails due to missing deletion events - channel.receiveOrFail(1.seconds).run { - assertIs>(this) - } - listener.cancel() - channel.close() - } - - @Test - override fun cancelAsFlow() { - kotlinx.coroutines.runBlocking { - val container = realm.write { - copyToRealm(JsonStyleRealmObject().apply { value = realmAnySetOf()}) - } - val channel1 = Channel>(1) - val channel2 = Channel>(1) - val observedSet = container.value!!.asSet() - val observer1 = async { - observedSet.asFlow() - .collect { change -> - channel1.trySend(change) - } - } - val observer2 = async { - observedSet.asFlow() - .collect { change -> - channel2.trySend(change) - } - } - - // Ignore first emission with empty sets - channel1.receiveOrFail() - channel2.receiveOrFail() - - // Trigger an update - realm.write { - val queriedContainer = findLatest(container) - queriedContainer!!.value!!.asSet().add(RealmAny.Companion.create(1)) - } - assertEquals(1, channel1.receiveOrFail().set.size) - assertEquals(1, channel2.receiveOrFail().set.size) - - // Cancel observer 1 - observer1.cancel() - - // Trigger another update - realm.write { - val queriedContainer = findLatest(container) - queriedContainer!!.value!!.asSet().add(RealmAny.create(2)) - } - - // Check channel 1 didn't receive the update - assertTrue(channel1.isEmpty) - // But channel 2 did - assertEquals(2, channel2.receiveOrFail().set.size) - - observer2.cancel() - channel1.close() - channel2.close() - } - } - - @Test - @Ignore // Awaiting callbacks on deletion https://github.com/realm/realm-core/issues/6857 - override fun deleteEntity() = runBlocking { - val container = - realm.write { copyToRealm(JsonStyleRealmObject().apply { value = realmAnySetOf() }) } - val mutex = Mutex(true) - val flow = async { - container.value!!.asSet().asFlow().first { - mutex.unlock() - it is DeletedSet<*> - } - } - - // Await that flow is actually running - mutex.lock() - // Update mixed value to overwrite and delete set - realm.write { - findLatest(container)!!.value = realmAnyListOf() - } - - // Await that notifier has signalled the deletion so we are certain that the entity - // has been deleted - withTimeout(10.seconds) { - flow.await() - } - } - - @Test - override fun asFlowOnDeletedEntity() = runBlocking { - val container = - realm.write { copyToRealm(JsonStyleRealmObject().apply { value = realmAnyListOf(realmAnySetOf()) }) } - val mutex = Mutex(true) - val flow = async { - container.value!!.asList()[0]!!.asSet().asFlow().first { - mutex.unlock() - it is DeletedSet<*> - } - } - - // Await that flow is actually running - mutex.lock() - // And delete containing entity - realm.write { delete(findLatest(container)!!) } - - // Await that notifier has signalled the deletion so we are certain that the entity - // has been deleted - withTimeout(10.seconds) { - flow.await() - } - - // Verify that a flow on the deleted entity will signal a deletion and complete gracefully - withTimeout(10.seconds) { - container.value!!.asList()[0]!!.asSet().asFlow().collect { - assertIs>(it) - } - } - } - - @Test - @Ignore - override fun closingRealmDoesNotCancelFlows() { - TODO("Not yet implemented") - } - - @Ignore - override fun closeRealmInsideFlowThrows() { - TODO("Not yet implemented") - } } + diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedDictionaryNotificationTest.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedDictionaryNotificationTest.kt new file mode 100644 index 0000000000..80c4add335 --- /dev/null +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedDictionaryNotificationTest.kt @@ -0,0 +1,257 @@ +/* + * Copyright 2023 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.realm.kotlin.test.common.notifications + +import io.realm.kotlin.Realm +import io.realm.kotlin.RealmConfiguration +import io.realm.kotlin.entities.JsonStyleRealmObject +import io.realm.kotlin.ext.realmAnyDictionaryOf +import io.realm.kotlin.ext.realmAnyListOf +import io.realm.kotlin.ext.realmAnyOf +import io.realm.kotlin.ext.realmAnySetOf +import io.realm.kotlin.internal.platform.runBlocking +import io.realm.kotlin.notifications.DeletedList +import io.realm.kotlin.notifications.DeletedMap +import io.realm.kotlin.notifications.InitialMap +import io.realm.kotlin.notifications.MapChange +import io.realm.kotlin.notifications.UpdatedMap +import io.realm.kotlin.test.common.utils.RealmEntityNotificationTests +import io.realm.kotlin.test.platform.PlatformUtils +import io.realm.kotlin.test.util.receiveOrFail +import io.realm.kotlin.types.RealmAny +import kotlinx.coroutines.async +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.withTimeout +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Ignore +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertIs +import kotlin.test.assertTrue +import kotlin.time.Duration.Companion.seconds + +class RealmAnyNestedDictionaryNotificationTest : RealmEntityNotificationTests { + + lateinit var tmpDir: String + lateinit var configuration: RealmConfiguration + lateinit var realm: Realm + + @BeforeTest + fun setup() { + tmpDir = PlatformUtils.createTempDir() + configuration = RealmConfiguration.Builder( + schema = setOf(JsonStyleRealmObject::class) + ).directory(tmpDir) + .build() + realm = Realm.open(configuration) + } + + @AfterTest + fun tearDown() { + if (this::realm.isInitialized && !realm.isClosed()) { + realm.close() + } + PlatformUtils.deleteTempDir(tmpDir) + } + + @Test + @Ignore // Initial element events are verified as part of the asFlow tests + override fun initialElement() {} + + @Test + override fun asFlow() = runBlocking { + val channel = Channel>() + + val o: JsonStyleRealmObject = realm.write { + copyToRealm(JsonStyleRealmObject().apply { + _id = "DICTIONARY" + value = realmAnyDictionaryOf("root" to realmAnyDictionaryOf("key1" to 1, "key2" to 2, "key3" to 3)) + }) + } + + val dict = o.value!!.asDictionary()["root"]!!.asDictionary() + assertEquals(3, dict.size) + val listener = async { + dict.asFlow().collect { + channel.send(it) + } + } + + channel.receiveOrFail(1.seconds).run { + assertIs>(this) + assertEquals(mapOf("key1" to 1,"key2" to 2,"key3" to 3), this.map.mapValues{ it.value!!.asInt()}) + } + + realm.write { + val liveList = findLatest(o)!!.value!!.asDictionary()["root"]!!.asDictionary() + liveList.put("key4", RealmAny.create(4)) + } + + channel.receiveOrFail(1.seconds).run { + assertIs>(this) + assertEquals(mapOf("key1" to 1,"key2" to 2,"key3" to 3, "key4" to 4), this.map.mapValues{ it.value!!.asInt()}) + } + + realm.write { + findLatest(o)!!.value = realmAnyOf(5) + } + + // Fails due to missing deletion events + channel.receiveOrFail(1.seconds).run { + assertIs>(this) + } + listener.cancel() + channel.close() + } + + @Test + override fun cancelAsFlow() { + kotlinx.coroutines.runBlocking { + val container = realm.write { + copyToRealm(JsonStyleRealmObject().apply { value = realmAnyDictionaryOf("root" to + realmAnyDictionaryOf() + ) + }) + } + val channel1 = Channel>(1) + val channel2 = Channel>(1) + val observedDict = container.value!!.asDictionary()["root"]!!.asDictionary() + val observer1 = async { + observedDict.asFlow() + .collect { change -> + channel1.trySend(change) + } + } + val observer2 = async { + observedDict.asFlow() + .collect { change -> + channel2.trySend(change) + } + } + + // Ignore first emission with empty sets + channel1.receiveOrFail() + channel2.receiveOrFail() + + // Trigger an update + realm.write { + val queriedContainer = findLatest(container) + // FIXME Suspendable notifier listens to root list instead of nested list +// queriedContainer!!.value!!.asList()[0]!!.asDictionary().put("key1", RealmAny.create(1)) + queriedContainer!!.value!!.asDictionary()["root"]!!.asDictionary().put("key1", RealmAny.create(1)) + } + assertEquals(2, channel1.receiveOrFail().map.size) + assertEquals(2, channel2.receiveOrFail().map.size) + + // Cancel observer 1 + observer1.cancel() + + // Trigger another update + realm.write { + val queriedContainer = findLatest(container) + queriedContainer!!.value!!.asDictionary()["root"]!!.asDictionary().put("key2", RealmAny.create(2)) + } + + // Check channel 1 didn't receive the update + assertTrue(channel1.isEmpty) + // But channel 2 did + assertEquals(3, channel2.receiveOrFail().map.size) + + observer2.cancel() + channel1.close() + channel2.close() + } + } + + @Test + override fun deleteEntity() = runBlocking { + val container = + realm.write { copyToRealm(JsonStyleRealmObject().apply { value = realmAnyDictionaryOf("root" to + realmAnyDictionaryOf() + ) }) } + val mutex = Mutex(true) + val flow = async { + container.value!!.asDictionary()["root"]!!.asDictionary().asFlow().first { + mutex.unlock() + it is DeletedMap + } + } + + // Await that flow is actually running + mutex.lock() + // Update mixed value to overwrite and delete set + realm.write { + // FIMXE Overwriting with similar container type doesn't emit a deletion event + // https://github.com/realm/realm-core/issues/6895 +// findLatest(container)!!.value = realmAnyListOf() + findLatest(container)!!.value = realmAnySetOf() + } + + // Await that notifier has signalled the deletion so we are certain that the entity + // has been deleted + withTimeout(10.seconds) { + flow.await() + } + } + + @Test + override fun asFlowOnDeletedEntity() = runBlocking { + val container = + realm.write { copyToRealm(JsonStyleRealmObject().apply { value = realmAnyDictionaryOf("root" to + realmAnyDictionaryOf() + ) }) } + val mutex = Mutex(true) + val flow = async { + container.value!!.asDictionary()["root"]!!.asDictionary().asFlow().first { + mutex.unlock() + it is DeletedMap + } + } + + // Await that flow is actually running + mutex.lock() + // And delete containing entity + realm.write { delete(findLatest(container)!!) } + + // Await that notifier has signalled the deletion so we are certain that the entity + // has been deleted + withTimeout(10.seconds) { + flow.await() + } + + // Verify that a flow on the deleted entity will signal a deletion and complete gracefully + withTimeout(10.seconds) { + container.value!!.asDictionary()["root"]!!.asDictionary().asFlow().collect { + assertIs>(it) + } + } + } + + @Test + @Ignore + override fun closingRealmDoesNotCancelFlows() { + TODO("Not yet implemented") + } + + @Ignore + override fun closeRealmInsideFlowThrows() { + TODO("Not yet implemented") + } +} diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedListNotificationTest.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedListNotificationTest.kt new file mode 100644 index 0000000000..1a6c3f7ff9 --- /dev/null +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedListNotificationTest.kt @@ -0,0 +1,259 @@ +/* + * Copyright 2023 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.realm.kotlin.test.common.notifications + +import io.realm.kotlin.Realm +import io.realm.kotlin.RealmConfiguration +import io.realm.kotlin.entities.JsonStyleRealmObject +import io.realm.kotlin.ext.realmAnyListOf +import io.realm.kotlin.ext.realmAnyOf +import io.realm.kotlin.ext.realmAnySetOf +import io.realm.kotlin.internal.platform.runBlocking +import io.realm.kotlin.notifications.DeletedList +import io.realm.kotlin.notifications.DeletedSet +import io.realm.kotlin.notifications.InitialList +import io.realm.kotlin.notifications.ListChange +import io.realm.kotlin.notifications.SetChange +import io.realm.kotlin.notifications.UpdatedSet +import io.realm.kotlin.test.common.utils.RealmEntityNotificationTests +import io.realm.kotlin.test.platform.PlatformUtils +import io.realm.kotlin.test.util.receiveOrFail +import io.realm.kotlin.types.RealmAny +import kotlinx.coroutines.async +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.withTimeout +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Ignore +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertIs +import kotlin.test.assertTrue +import kotlin.time.Duration.Companion.seconds + +class RealmAnyNestedListNotificationTest : RealmEntityNotificationTests { + + lateinit var tmpDir: String + lateinit var configuration: RealmConfiguration + lateinit var realm: Realm + + @BeforeTest + fun setup() { + tmpDir = PlatformUtils.createTempDir() + configuration = RealmConfiguration.Builder( + schema = setOf(JsonStyleRealmObject::class) + ).directory(tmpDir) + .build() + realm = Realm.open(configuration) + } + + @AfterTest + fun tearDown() { + if (this::realm.isInitialized && !realm.isClosed()) { + realm.close() + } + PlatformUtils.deleteTempDir(tmpDir) + } + + @Test + @Ignore // Initial element events are verified as part of the asFlow tests + override fun initialElement() {} + + @Test + override fun asFlow() = runBlocking { + val channel = Channel>() + + val o: JsonStyleRealmObject = realm.write { + copyToRealm(JsonStyleRealmObject().apply { + _id = "LIST" + value = realmAnyListOf(realmAnyListOf(realmAnyListOf(1, 2, 3))) + }) + } + + val list = o.value!!.asList()[0]!!.asList()[0]!!.asList() + assertEquals(3, list.size) + val listener = async { + println("LISTENING to $list") + list.asFlow().collect { + channel.send(it) + } + } + + channel.receiveOrFail(1.seconds).run { + assertIs>(this) + println(this) + println(this.list) + println(this.list[0]) + assertEquals(listOf(1,2,3), this.list.map{ it!!.asInt()}) + } + + realm.write { + val liveList = findLatest(o)!!.value!!.asList()[0]!!.asList() + liveList.add(RealmAny.create(4)) + } + + channel.receiveOrFail(1.seconds).run { + assertIs>(this) + assertEquals(listOf(1,2,3, 4), this.list.map{ it!!.asInt()}) + } + + realm.write { + findLatest(o)!!.value = realmAnyOf(5) + } + + // Fails due to missing deletion events + channel.receiveOrFail(1.seconds).run { + assertIs>(this) + } + listener.cancel() + channel.close() + } + + @Test + override fun cancelAsFlow() { + kotlinx.coroutines.runBlocking { + val container = realm.write { + copyToRealm(JsonStyleRealmObject().apply { value = realmAnyListOf(realmAnyListOf())}) + } + val channel1 = Channel>(1) + val channel2 = Channel>(1) + val observedSet = container.value!!.asList()[0]!!.asList() + val observer1 = async { + observedSet.asFlow() + .collect { change -> + channel1.trySend(change) + } + } + val observer2 = async { + observedSet.asFlow() + .collect { change -> + channel2.trySend(change) + } + } + + // Ignore first emission with empty sets + channel1.receiveOrFail() + channel2.receiveOrFail() + + // Trigger an update + realm.write { + val queriedContainer = findLatest(container) + // FIXME Suspendable notifier listens to root list instead of nested list +// queriedContainer!!.value!!.asList()[0]!!.asList().add(RealmAny.create(1)) + queriedContainer!!.value!!.asList().add(RealmAny.create(1)) + } + assertEquals(1, channel1.receiveOrFail().list.size) + assertEquals(1, channel2.receiveOrFail().list.size) + + // Cancel observer 1 + observer1.cancel() + + // Trigger another update + realm.write { + val queriedContainer = findLatest(container) + queriedContainer!!.value!!.asList()[0]!!.asList().add(RealmAny.create(2)) + } + + // Check channel 1 didn't receive the update + assertTrue(channel1.isEmpty) + // But channel 2 did + assertEquals(2, channel2.receiveOrFail().list.size) + + observer2.cancel() + channel1.close() + channel2.close() + } + } + + @Test + override fun deleteEntity() = runBlocking { + val container = + realm.write { copyToRealm(JsonStyleRealmObject().apply { value = realmAnyListOf( + realmAnyListOf() + ) }) } + val mutex = Mutex(true) + val flow = async { + container.value!!.asList()[0]!!.asList().asFlow().first { + mutex.unlock() + it is DeletedList<*> + } + } + + // Await that flow is actually running + mutex.lock() + // Update mixed value to overwrite and delete set + realm.write { + // FIMXE Overwriting with similar container type doesn't emit a deletion event + // https://github.com/realm/realm-core/issues/6895 +// findLatest(container)!!.value = realmAnyListOf() + findLatest(container)!!.value = realmAnySetOf() + } + + // Await that notifier has signalled the deletion so we are certain that the entity + // has been deleted + withTimeout(10.seconds) { + flow.await() + } + } + + @Test + override fun asFlowOnDeletedEntity() = runBlocking { + val container = + realm.write { copyToRealm(JsonStyleRealmObject().apply { value = realmAnyListOf( + realmAnyListOf() + ) }) } + val mutex = Mutex(true) + val flow = async { + container.value!!.asList()[0]!!.asList().asFlow().first { + mutex.unlock() + it is DeletedList<*> + } + } + + // Await that flow is actually running + mutex.lock() + // And delete containing entity + realm.write { delete(findLatest(container)!!) } + + // Await that notifier has signalled the deletion so we are certain that the entity + // has been deleted + withTimeout(10.seconds) { + flow.await() + } + + // Verify that a flow on the deleted entity will signal a deletion and complete gracefully + withTimeout(10.seconds) { + container.value!!.asList()[0]!!.asList().asFlow().collect { + assertIs>(it) + } + } + } + + @Test + @Ignore + override fun closingRealmDoesNotCancelFlows() { + TODO("Not yet implemented") + } + + @Ignore + override fun closeRealmInsideFlowThrows() { + TODO("Not yet implemented") + } +} + diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedSetNotificationTest.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedSetNotificationTest.kt new file mode 100644 index 0000000000..d71b677908 --- /dev/null +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedSetNotificationTest.kt @@ -0,0 +1,253 @@ +/* + * Copyright 2023 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.realm.kotlin.test.common.notifications + +import io.realm.kotlin.Realm +import io.realm.kotlin.RealmConfiguration +import io.realm.kotlin.entities.JsonStyleRealmObject +import io.realm.kotlin.ext.asFlow +import io.realm.kotlin.ext.realmAnyListOf +import io.realm.kotlin.ext.realmAnyOf +import io.realm.kotlin.ext.realmAnySetOf +import io.realm.kotlin.internal.platform.runBlocking +import io.realm.kotlin.notifications.DeletedSet +import io.realm.kotlin.notifications.InitialObject +import io.realm.kotlin.notifications.InitialSet +import io.realm.kotlin.notifications.ObjectChange +import io.realm.kotlin.notifications.SetChange +import io.realm.kotlin.notifications.UpdatedObject +import io.realm.kotlin.notifications.UpdatedSet +import io.realm.kotlin.test.common.utils.RealmEntityNotificationTests +import io.realm.kotlin.test.platform.PlatformUtils +import io.realm.kotlin.test.util.receiveOrFail +import io.realm.kotlin.types.RealmAny +import kotlinx.coroutines.async +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.withTimeout +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Ignore +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertIs +import kotlin.test.assertTrue +import kotlin.time.Duration.Companion.seconds + +class RealmAnyNestedSetNotificationTest : RealmEntityNotificationTests { + + + lateinit var tmpDir: String + lateinit var configuration: RealmConfiguration + lateinit var realm: Realm + + @BeforeTest + fun setup() { + tmpDir = PlatformUtils.createTempDir() + configuration = RealmConfiguration.Builder( + schema = setOf(JsonStyleRealmObject::class) + ).directory(tmpDir) + .build() + realm = Realm.open(configuration) + } + + @AfterTest + fun tearDown() { + if (this::realm.isInitialized && !realm.isClosed()) { + realm.close() + } + PlatformUtils.deleteTempDir(tmpDir) + } + + @Test + @Ignore // Initial element events are verified as part of the asFlow tests + override fun initialElement() {} + + @Test + override fun asFlow() = runBlocking { + val channel = Channel>() + + val o: JsonStyleRealmObject = realm.write { + copyToRealm(JsonStyleRealmObject().apply { + _id = "SET" + value = realmAnySetOf(1, 2, 3) + }) + } + + val set = o.value!!.asSet() + assertEquals(3, set.size) + val listener = async { + set.asFlow().collect { + channel.send(it) + } + } + + channel.receiveOrFail(1.seconds).run { + assertIs>(this) + val expectedSet = mutableSetOf(1, 2, 3) + this.set.forEach { expectedSet.remove(it!!.asInt()) } + assertTrue { expectedSet.isEmpty() } + } + + realm.write { + val liveSet = findLatest(o)!!.value!!.asSet() + liveSet.add(RealmAny.create(4)) + } + + channel.receiveOrFail(1.seconds).run { + assertIs>(this) + val expectedSet = mutableSetOf(1, 2, 3, 4) + this.set.forEach { expectedSet.remove(it!!.asInt()) } + assertTrue { expectedSet.isEmpty() } + } + + realm.write { + findLatest(o)!!.value = realmAnyOf(5) + } + + // Fails due to missing deletion events + channel.receiveOrFail(1.seconds).run { + assertIs>(this) + } + listener.cancel() + channel.close() + } + + @Test + override fun cancelAsFlow() { + kotlinx.coroutines.runBlocking { + val container = realm.write { + copyToRealm(JsonStyleRealmObject().apply { value = realmAnySetOf()}) + } + val channel1 = Channel>(1) + val channel2 = Channel>(1) + val observedSet = container.value!!.asSet() + val observer1 = async { + observedSet.asFlow() + .collect { change -> + channel1.trySend(change) + } + } + val observer2 = async { + observedSet.asFlow() + .collect { change -> + channel2.trySend(change) + } + } + + // Ignore first emission with empty sets + channel1.receiveOrFail() + channel2.receiveOrFail() + + // Trigger an update + realm.write { + val queriedContainer = findLatest(container) + queriedContainer!!.value!!.asSet().add(RealmAny.Companion.create(1)) + } + assertEquals(1, channel1.receiveOrFail().set.size) + assertEquals(1, channel2.receiveOrFail().set.size) + + // Cancel observer 1 + observer1.cancel() + + // Trigger another update + realm.write { + val queriedContainer = findLatest(container) + queriedContainer!!.value!!.asSet().add(RealmAny.create(2)) + } + + // Check channel 1 didn't receive the update + assertTrue(channel1.isEmpty) + // But channel 2 did + assertEquals(2, channel2.receiveOrFail().set.size) + + observer2.cancel() + channel1.close() + channel2.close() + } + } + + @Test + override fun deleteEntity() = runBlocking { + val container = + realm.write { copyToRealm(JsonStyleRealmObject().apply { value = realmAnySetOf() }) } + val mutex = Mutex(true) + val flow = async { + container.value!!.asSet().asFlow().first { + mutex.unlock() + it is DeletedSet<*> + } + } + + // Await that flow is actually running + mutex.lock() + // Update mixed value to overwrite and delete set + realm.write { + findLatest(container)!!.value = realmAnyListOf() + } + + // Await that notifier has signalled the deletion so we are certain that the entity + // has been deleted + withTimeout(10.seconds) { + flow.await() + } + } + + @Test + override fun asFlowOnDeletedEntity() = runBlocking { + val container = + realm.write { copyToRealm(JsonStyleRealmObject().apply { value = realmAnyListOf(realmAnySetOf()) }) } + val mutex = Mutex(true) + val flow = async { + container.value!!.asList()[0]!!.asSet().asFlow().first { + mutex.unlock() + it is DeletedSet<*> + } + } + + // Await that flow is actually running + mutex.lock() + // And delete containing entity + realm.write { delete(findLatest(container)!!) } + + // Await that notifier has signalled the deletion so we are certain that the entity + // has been deleted + withTimeout(10.seconds) { + flow.await() + } + + // Verify that a flow on the deleted entity will signal a deletion and complete gracefully + withTimeout(10.seconds) { + container.value!!.asList()[0]!!.asSet().asFlow().collect { + assertIs>(it) + } + } + } + + @Test + @Ignore + override fun closingRealmDoesNotCancelFlows() { + TODO("Not yet implemented") + } + + @Ignore + override fun closeRealmInsideFlowThrows() { + TODO("Not yet implemented") + } +} + diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt index 16ac683781..9a8ef77d9a 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt @@ -29,7 +29,9 @@ import io.realm.kotlin.entities.sync.flx.FlexChildObject import io.realm.kotlin.entities.sync.flx.FlexEmbeddedObject import io.realm.kotlin.entities.sync.flx.FlexParentObject import io.realm.kotlin.ext.query +import io.realm.kotlin.ext.realmAnyDictionaryOf import io.realm.kotlin.ext.realmAnyListOf +import io.realm.kotlin.ext.realmAnySetOf import io.realm.kotlin.internal.platform.fileExists import io.realm.kotlin.internal.platform.runBlocking import io.realm.kotlin.log.LogLevel @@ -1541,6 +1543,7 @@ class SyncedRealmTests { @Test fun flexibleSync_throwsWithLocalInitialRealmFile() { + val (email, password) = randomEmail() to "password1234" val user = runBlocking { app.createUserAndLogIn(email, password) @@ -1574,9 +1577,15 @@ class SyncedRealmTests { Realm.open(local).use { it.write { val obj = copyToRealm(JsonStyleRealmObject()) - assertFailsWith { + assertFailsWithMessage("Cannot sync nested set") { + obj.value = realmAnySetOf() + } + assertFailsWithMessage("Cannot sync nested list") { obj.value = realmAnyListOf() } + assertFailsWithMessage("Cannot sync nested dictionary") { + obj.value = realmAnyDictionaryOf() + } } } flexApp.close() From 3b4212f864911fd925f5a72175296f530972bcbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 22 Aug 2023 13:12:33 +0200 Subject: [PATCH 11/41] Fixed notification and overwrite tests --- .../kotlin/internal/interop/RealmInterop.kt | 8 +- .../kotlin/internal/interop/RealmInterop.kt | 24 +++++- packages/external/core | 2 +- .../io/realm/kotlin/internal/RealmAnyImpl.kt | 19 ++++- .../kotlin/internal/RealmListInternal.kt | 9 ++- .../realm/kotlin/internal/RealmMapInternal.kt | 3 + .../common/RealmAnyNestedCollectionTests.kt | 80 ++++++++++++++----- ...ealmAnyNestedDictionaryNotificationTest.kt | 4 +- .../RealmAnyNestedListNotificationTest.kt | 27 ++----- 9 files changed, 123 insertions(+), 53 deletions(-) diff --git a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt index d59edce63e..2154b3542d 100644 --- a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt +++ b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt @@ -293,7 +293,9 @@ expect object RealmInterop { isDefault: Boolean ) fun realm_set_embedded(obj: RealmObjectPointer, key: PropertyKey): RealmObjectPointer - fun realm_set_collection(obj: RealmObjectPointer, key: PropertyKey, collectionType: CollectionType) + fun realm_set_set(obj: RealmObjectPointer, key: PropertyKey) : RealmSetPointer + fun realm_set_list(obj: RealmObjectPointer, key: PropertyKey) : RealmListPointer + fun realm_set_dictionary(obj: RealmObjectPointer, key: PropertyKey): RealmMapPointer fun realm_object_add_int(obj: RealmObjectPointer, key: PropertyKey, value: Long) fun realm_object_get_parent( obj: RealmObjectPointer, @@ -318,7 +320,9 @@ expect object RealmInterop { // fun realm_list_set_set(list: RealmListPointer, index: Long): RealmSetPointer // fun realm_list_set_list(list: RealmListPointer, index: Long): RealmListPointer // fun realm_list_set_dictionary(list: RealmListPointer, index: Long): RealmMapPointer - fun realm_list_set_collection(list: RealmListPointer, index: Long, collectionType: CollectionType) + fun realm_list_set_set(list: RealmListPointer, index: Long): RealmSetPointer + fun realm_list_set_list(list: RealmListPointer, index: Long): RealmListPointer + fun realm_list_set_dictionary(list: RealmListPointer, index: Long): RealmMapPointer // Returns the newly inserted element as the previous embedded element is automatically delete // by this operation diff --git a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt index 0658f1ca22..95cff9016c 100644 --- a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt +++ b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt @@ -463,8 +463,17 @@ actual object RealmInterop { return LongPointerWrapper(realmc.realm_set_embedded(obj.cptr(), key.key)) } - actual fun realm_set_collection(obj: RealmObjectPointer, key: PropertyKey, collectionType: CollectionType) { - realmc.realm_set_collection(obj.cptr(), key.key, collectionType.nativeValue) + actual fun realm_set_set(obj: RealmObjectPointer, key: PropertyKey) : RealmSetPointer { + realmc.realm_set_set(obj.cptr(), key.key) + return realm_get_set(obj, key) + } + actual fun realm_set_list(obj: RealmObjectPointer, key: PropertyKey) : RealmListPointer { + realmc.realm_set_list(obj.cptr(), key.key) + return realm_get_list(obj, key) + } + actual fun realm_set_dictionary(obj: RealmObjectPointer, key: PropertyKey) : RealmMapPointer { + realmc.realm_set_dictionary(obj.cptr(), key.key) + return realm_get_dictionary(obj, key) } actual fun realm_object_add_int(obj: RealmObjectPointer, key: PropertyKey, value: Long) { @@ -556,8 +565,15 @@ actual object RealmInterop { // actual fun realm_list_set_dictionary(list: RealmListPointer, index: Long): RealmMapPointer { // return LongPointerWrapper(realmc.realm_list_set_dictionary(list.cptr(), index)) // } - actual fun realm_list_set_collection(list: RealmListPointer, index: Long, collectionType: CollectionType) { - realmc.realm_list_set_collection(list.cptr(), index, collectionType.nativeValue) + + actual fun realm_list_set_set(list: RealmListPointer, index: Long): RealmSetPointer { + return LongPointerWrapper(realmc.realm_list_set_set(list.cptr(), index)) + } + actual fun realm_list_set_list(list: RealmListPointer, index: Long): RealmListPointer { + return LongPointerWrapper(realmc.realm_list_set_list(list.cptr(), index)) + } + actual fun realm_list_set_dictionary(list: RealmListPointer, index: Long): RealmMapPointer { + return LongPointerWrapper(realmc.realm_list_set_dictionary(list.cptr(), index)) } actual fun realm_list_set( diff --git a/packages/external/core b/packages/external/core index 23dcc34376..3d11e01b8a 160000 --- a/packages/external/core +++ b/packages/external/core @@ -1 +1 @@ -Subproject commit 23dcc34376b160234e30332baf2ef7652087d132 +Subproject commit 3d11e01b8af665e45dcad8b20cadd36f77659777 diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmAnyImpl.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmAnyImpl.kt index ad881d814d..16389bed8c 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmAnyImpl.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmAnyImpl.kt @@ -93,7 +93,7 @@ internal sealed interface RealmAnyContainer { } fun MemTrackingAllocator.setPrimitive(value: RealmAny) - fun createCollection(collectionType: CollectionType) + fun MemTrackingAllocator.createCollection(collectionType: CollectionType) fun getSet(): RealmSet fun getList(): RealmList fun getDictionary(): RealmDictionary @@ -109,8 +109,21 @@ internal class RealmAnyProperty( override val mediator = obj.mediator override val realm: RealmReference = obj.owner - override fun createCollection(collectionType: CollectionType) { - RealmInterop.realm_set_collection(obj.objectPointer, key, collectionType) + override fun MemTrackingAllocator.createCollection(collectionType: CollectionType) { + RealmInterop.realm_set_value(obj.objectPointer, key, nullTransport(), false) + when(collectionType) { + CollectionType.RLM_COLLECTION_TYPE_NONE -> TODO() + CollectionType.RLM_COLLECTION_TYPE_LIST -> { + RealmInterop.realm_set_list(obj.objectPointer, key) + } + CollectionType.RLM_COLLECTION_TYPE_SET -> { + RealmInterop.realm_set_set(obj.objectPointer, key) + } + CollectionType.RLM_COLLECTION_TYPE_DICTIONARY -> { + RealmInterop.realm_set_dictionary(obj.objectPointer, key) + } + else -> TODO() + } } override fun MemTrackingAllocator.setPrimitive(value: RealmAny) { diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmListInternal.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmListInternal.kt index 1ac6fdc28c..429050538b 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmListInternal.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmListInternal.kt @@ -414,9 +414,12 @@ internal class RealmAnyListOperator( return get(index).also { inputScope { if (element != null && element.type in RealmAny.Type.COLLECTION_TYPES) { + // Core will not detect updates if resetting with similar containertype, so + // force detection of new reference by clearing the value first. + RealmInterop.realm_list_set(nativePointer, index.toLong(), nullTransport()) when (element.type) { RealmAny.Type.SET -> { - RealmInterop.realm_list_set_collection(nativePointer, index.toLong(), CollectionType.RLM_COLLECTION_TYPE_SET) + RealmInterop.realm_list_set_set(nativePointer, index.toLong()) val newNativePointer = RealmInterop.realm_list_get_set(nativePointer, index.toLong()) val operator = RealmAnySetOperator( mediator, @@ -428,13 +431,13 @@ internal class RealmAnyListOperator( operator.addAllInternal(element.asSet(), updatePolicy, cache) } RealmAny.Type.LIST -> { - RealmInterop.realm_list_set_collection(nativePointer, index.toLong(), CollectionType.RLM_COLLECTION_TYPE_LIST) + RealmInterop.realm_list_set_list(nativePointer, index.toLong()) val newNativePointer = RealmInterop.realm_list_get_list(nativePointer, index.toLong()) val operator = RealmAnyListOperator(mediator, realmReference, newNativePointer, updatePolicy, cache, issueDynamicObject, issueDynamicMutableObject) operator.insertAll(0, element.asList(), updatePolicy, cache) } RealmAny.Type.DICTIONARY -> { - RealmInterop.realm_list_set_collection(nativePointer, index.toLong(), CollectionType.RLM_COLLECTION_TYPE_DICTIONARY) + RealmInterop.realm_list_set_dictionary(nativePointer, index.toLong()) val newNativePointer = RealmInterop.realm_list_get_dictionary(nativePointer, index.toLong()) val operator = RealmAnyMapOperator( mediator, realmReference, diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt index 9b7538ca17..5f37d0db6e 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt @@ -473,6 +473,9 @@ internal class RealmAnyMapOperator constructor( return inputScope { val keyTransport = with(keyConverter) { publicToRealmValue(key) } if (value != null && value.type in RealmAny.Type.COLLECTION_TYPES) { + // Core will not detect updates if resetting with similar containertype, so + // force detection of new reference by clearing the value first. + realm_dictionary_insert(nativePointer, keyTransport, nullTransport()) when (value.type) { RealmAny.Type.SET -> { val newNativePointer = RealmInterop.realm_dictionary_insert_set(nativePointer, keyTransport) diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt index c929b7d333..920f740c96 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt @@ -33,6 +33,7 @@ import io.realm.kotlin.test.common.utils.assertFailsWithMessage import io.realm.kotlin.test.platform.PlatformUtils import io.realm.kotlin.types.RealmAny import org.mongodb.kbson.ObjectId +import java.lang.IllegalStateException import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test @@ -411,27 +412,27 @@ class RealmAnyNestedCollectionTests { fun nestedCollectionsInList_set_invalidatesOldElement() = runBlocking { realm.write { val instance = copyToRealm(JsonStyleRealmObject()) - instance.value = RealmAny.create(realmListOf(RealmAny.create(5))) + instance.value = realmAnyListOf(realmAnyListOf(5)) // Store local reference to existing list - val nestedList = instance.value!!.asList() + var nestedList = instance.value!!.asList()[0]!!.asList() // Accessing returns excepted value 5 - nestedList[0]!!.asInt() + assertEquals(5, nestedList[0]!!.asInt()) - // Overwriting with new list - instance.value = realmAnyListOf(7) - // Accessing original orphaned list return 7 from the new instance, but expected ILLEGAL_STATE_EXCEPTION["List is no longer valid"] - nestedList[0]!!.asInt() + // Overwriting exact list with new list + instance.value!!.asList()[0] = realmAnyListOf(7) + assertFailsWithMessage("List is no longer valid") { + nestedList[0]!!.asInt() + } - // Overwriting with null value - instance.value = null - // Throws excepted ILLEGAL_STATE_EXCEPTION["List is no longer valid"] - nestedList[0]!!.asInt() + nestedList = instance.value!!.asList()[0]!!.asList() + assertEquals(7, nestedList[0]!!.asInt()) - // Updating to a new list - instance.value = realmAnyListOf(7) - // Accessing original orphaned list return 7 from the new instance again, but expected ILLEGAL_STATE_EXCEPTION["List is no longer valid"] - nestedList[0]!!.asInt() + // Overwriting root entry + instance.value = null + assertFailsWithMessage("List is no longer valid") { + nestedList[0]!!.asInt() + } // assertFailsWithMessage("List is no longer valid") { // nestedList[0]!!.asInt() @@ -474,6 +475,11 @@ class RealmAnyNestedCollectionTests { // assertFailsWithMessage("List is no longer valid") { // val realmAny = nestedList[0] // } + // Recreating list doesn't bring things back to shape + instance.value = realmAnyListOf(realmAnyListOf(8)) + assertFailsWithMessage("List is no longer valid") { + nestedList[0]!!.asInt() + } } } @@ -645,9 +651,12 @@ class RealmAnyNestedCollectionTests { val nestedList = instance.value!!.asDictionary()["key"]!!.asList() assertEquals(5, nestedList[0]!!.asInt()) // Overwrite nested list element with new list - instance.value!!.asDictionary()["key"] = RealmAny.create(realmListOf(RealmAny.create(7))) - // FIXME This shouldn't be true. We shouldn't have overwrite the old list -// assertEquals(5, nestedList[0]!!.asInt()) + instance.value!!.asDictionary()["key"] = null + + assertFailsWithMessage("List is no longer valid") { + nestedList[0]!!.asInt() + } + // Overwrite nested list element with new collection of different type instance.value!!.asDictionary()["key"] = RealmAny.create(realmSetOf(RealmAny.create(8))) // Access the old list @@ -660,6 +669,41 @@ class RealmAnyNestedCollectionTests { } } + @Test + fun updateMixed_invalidatesOldElement() = runBlocking { + realm.write { + val instance = copyToRealm(JsonStyleRealmObject()) + instance.value = RealmAny.create(realmListOf(RealmAny.create(5))) + + // Store local reference to existing list + val nestedList = instance.value!!.asList() + // Accessing returns excepted value 5 + nestedList[0]!!.asInt() + + // Overwriting with new list + instance.value = realmAnyListOf(7) + // Accessing original orphaned list return 7 from the new instance, but expected ILLEGAL_STATE_EXCEPTION["List is no longer valid"] + assertFailsWithMessage("List is no longer valid") { + nestedList[0]!!.asInt() + } + + // Overwriting with null value + instance.value = null + // Throws excepted ILLEGAL_STATE_EXCEPTION["List is no longer valid"] + assertFailsWithMessage("List is no longer valid") { + nestedList[0]!!.asInt() + } + + // Updating to a new list + instance.value = realmAnyListOf(7) + // Accessing original orphaned list return 7 from the new instance again, but expected ILLEGAL_STATE_EXCEPTION["List is no longer valid"] + assertFailsWithMessage("List is no longer valid") { + nestedList[0]!!.asInt() + } + + } + } + @Test fun query_ThrowsOnNestedCollectionArguments() { assertFailsWithMessage("Cannot use nested collections as primary keys or query arguments") { diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedDictionaryNotificationTest.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedDictionaryNotificationTest.kt index 80c4add335..3675c6b18f 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedDictionaryNotificationTest.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedDictionaryNotificationTest.kt @@ -153,9 +153,7 @@ class RealmAnyNestedDictionaryNotificationTest : RealmEntityNotificationTests { // Trigger an update realm.write { val queriedContainer = findLatest(container) - // FIXME Suspendable notifier listens to root list instead of nested list -// queriedContainer!!.value!!.asList()[0]!!.asDictionary().put("key1", RealmAny.create(1)) - queriedContainer!!.value!!.asDictionary()["root"]!!.asDictionary().put("key1", RealmAny.create(1)) + queriedContainer!!.value!!.asList()[0]!!.asDictionary().put("key1", RealmAny.create(1)) } assertEquals(2, channel1.receiveOrFail().map.size) assertEquals(2, channel2.receiveOrFail().map.size) diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedListNotificationTest.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedListNotificationTest.kt index 1a6c3f7ff9..db7c2baa82 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedListNotificationTest.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedListNotificationTest.kt @@ -24,11 +24,9 @@ import io.realm.kotlin.ext.realmAnyOf import io.realm.kotlin.ext.realmAnySetOf import io.realm.kotlin.internal.platform.runBlocking import io.realm.kotlin.notifications.DeletedList -import io.realm.kotlin.notifications.DeletedSet import io.realm.kotlin.notifications.InitialList import io.realm.kotlin.notifications.ListChange -import io.realm.kotlin.notifications.SetChange -import io.realm.kotlin.notifications.UpdatedSet +import io.realm.kotlin.notifications.UpdatedList import io.realm.kotlin.test.common.utils.RealmEntityNotificationTests import io.realm.kotlin.test.platform.PlatformUtils import io.realm.kotlin.test.util.receiveOrFail @@ -82,14 +80,13 @@ class RealmAnyNestedListNotificationTest : RealmEntityNotificationTests { val o: JsonStyleRealmObject = realm.write { copyToRealm(JsonStyleRealmObject().apply { _id = "LIST" - value = realmAnyListOf(realmAnyListOf(realmAnyListOf(1, 2, 3))) + value = realmAnyListOf(realmAnyListOf(1, 2, 3)) }) } - val list = o.value!!.asList()[0]!!.asList()[0]!!.asList() + val list = o.value!!.asList()[0]!!.asList() assertEquals(3, list.size) val listener = async { - println("LISTENING to $list") list.asFlow().collect { channel.send(it) } @@ -97,19 +94,16 @@ class RealmAnyNestedListNotificationTest : RealmEntityNotificationTests { channel.receiveOrFail(1.seconds).run { assertIs>(this) - println(this) - println(this.list) - println(this.list[0]) assertEquals(listOf(1,2,3), this.list.map{ it!!.asInt()}) } realm.write { - val liveList = findLatest(o)!!.value!!.asList()[0]!!.asList() - liveList.add(RealmAny.create(4)) + val liveNestedList = findLatest(o)!!.value!!.asList()[0]!!.asList() + liveNestedList.add(RealmAny.create(4)) } channel.receiveOrFail(1.seconds).run { - assertIs>(this) + assertIs>(this) assertEquals(listOf(1,2,3, 4), this.list.map{ it!!.asInt()}) } @@ -154,9 +148,7 @@ class RealmAnyNestedListNotificationTest : RealmEntityNotificationTests { // Trigger an update realm.write { val queriedContainer = findLatest(container) - // FIXME Suspendable notifier listens to root list instead of nested list -// queriedContainer!!.value!!.asList()[0]!!.asList().add(RealmAny.create(1)) - queriedContainer!!.value!!.asList().add(RealmAny.create(1)) + queriedContainer!!.value!!.asList()[0]!!.asList().add(RealmAny.create(1)) } assertEquals(1, channel1.receiveOrFail().list.size) assertEquals(1, channel2.receiveOrFail().list.size) @@ -199,10 +191,7 @@ class RealmAnyNestedListNotificationTest : RealmEntityNotificationTests { mutex.lock() // Update mixed value to overwrite and delete set realm.write { - // FIMXE Overwriting with similar container type doesn't emit a deletion event - // https://github.com/realm/realm-core/issues/6895 -// findLatest(container)!!.value = realmAnyListOf() - findLatest(container)!!.value = realmAnySetOf() + findLatest(container)!!.value = realmAnyListOf() } // Await that notifier has signalled the deletion so we are certain that the entity From 5345436f88c1ecd09e1588f70a8d484b71371a73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Wed, 23 Aug 2023 12:49:12 +0200 Subject: [PATCH 12/41] Add tests for collection operations with unmanaged entities --- .../realm/kotlin/internal/RealmSetInternal.kt | 26 +++++++++++++++ .../test/common/RealmDictionaryTests.kt | 16 ++++++++++ .../kotlin/test/common/RealmListTests.kt | 32 +++++++++++++++++++ .../realm/kotlin/test/common/RealmSetTests.kt | 28 ++++++++++++++++ 4 files changed, 102 insertions(+) diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmSetInternal.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmSetInternal.kt index cf1647cfc9..08e028c5df 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmSetInternal.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmSetInternal.kt @@ -18,6 +18,8 @@ package io.realm.kotlin.internal import io.realm.kotlin.UpdatePolicy import io.realm.kotlin.Versioned +import io.realm.kotlin.ext.asRealmObject +import io.realm.kotlin.ext.isManaged import io.realm.kotlin.internal.RealmValueArgumentConverter.convertToQueryArgs import io.realm.kotlin.internal.interop.Callback import io.realm.kotlin.internal.interop.ClassKey @@ -338,7 +340,19 @@ internal class RealmAnySetOperator( } } + override fun remove(element: RealmAny?): Boolean { + // Unmanaged objects are never found in a managed dictionary + if (element?.type == RealmAny.Type.OBJECT) { + if (!element.asRealmObject().isManaged()) return false + } + return super.remove(element) + } + override fun contains(element: RealmAny?): Boolean { + // Unmanaged objects are never found in a managed dictionary + if (element?.type == RealmAny.Type.OBJECT) { + if (!element.asRealmObject().isManaged()) return false + } return inputScope { with(valueConverter) { val transport = publicToRealmValue(element) @@ -446,7 +460,19 @@ internal class RealmObjectSetOperator constructor( } } + override fun remove(element: E): Boolean { + // Unmanaged objects are never found in a managed set + element?.also { + if (!(it as RealmObjectInternal).isManaged()) return false + } + return super.remove(element) + } + override fun contains(element: E): Boolean { + // Unmanaged objects are never found in a managed set + element?.also { + if (!(it as RealmObjectInternal).isManaged()) return false + } return inputScope { val objRef = realmObjectToRealmReferenceWithImport( element as BaseRealmObject?, diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmDictionaryTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmDictionaryTests.kt index 50c5b9347f..e89334f023 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmDictionaryTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmDictionaryTests.kt @@ -21,6 +21,7 @@ import io.realm.kotlin.Realm import io.realm.kotlin.RealmConfiguration import io.realm.kotlin.entities.dictionary.DictionaryEmbeddedLevel1 import io.realm.kotlin.entities.dictionary.RealmDictionaryContainer +import io.realm.kotlin.entities.list.RealmListContainer import io.realm.kotlin.ext.asRealmObject import io.realm.kotlin.ext.query import io.realm.kotlin.ext.realmDictionaryEntryOf @@ -1323,6 +1324,21 @@ class RealmDictionaryTests : EmbeddedObjectCollectionQueryTests { Unit } + @Test + fun contains_unmanagedArgs() = runBlocking { + val frozenObject = realm.write { + val liveObject = copyToRealm(RealmDictionaryContainer()) + assertEquals(1, query().find().size) + assertFalse(liveObject.nullableObjectDictionaryField.containsValue(RealmDictionaryContainer())) + assertFalse(liveObject.nullableRealmAnyDictionaryField.containsValue(RealmAny.create(RealmDictionaryContainer()))) + assertEquals(1, query().find().size) + liveObject + } + // Verify that we can also call this on frozen instances + assertFalse(frozenObject.nullableObjectDictionaryField.containsValue(RealmDictionaryContainer())) + assertFalse(frozenObject.nullableRealmAnyDictionaryField.containsValue(RealmAny.create(RealmDictionaryContainer()))) + } + private fun getCloseableRealm(): Realm = RealmConfiguration.Builder(schema = dictionarySchema) .directory(tmpDir) diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmListTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmListTests.kt index 4dae18cd07..dfd745a5ca 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmListTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmListTests.kt @@ -27,6 +27,7 @@ import io.realm.kotlin.entities.list.Level2 import io.realm.kotlin.entities.list.Level3 import io.realm.kotlin.entities.list.RealmListContainer import io.realm.kotlin.entities.list.listTestSchema +import io.realm.kotlin.entities.set.RealmSetContainer import io.realm.kotlin.ext.asRealmObject import io.realm.kotlin.ext.query import io.realm.kotlin.ext.realmListOf @@ -632,6 +633,37 @@ class RealmListTests : EmbeddedObjectCollectionQueryTests { Unit } + @Test + fun contains_unmanagedArgs() = runBlocking { + val frozenObject = realm.write { + val liveObject = copyToRealm(RealmListContainer()) + assertEquals(1, query().find().size) + assertFalse(liveObject.objectListField.contains(RealmListContainer())) + assertFalse(liveObject.nullableRealmAnyListField.contains(RealmAny.create(RealmListContainer()))) + assertEquals(1, query().find().size) + liveObject + } + // Verify that we can also call this on frozen instances + assertFalse(frozenObject.objectListField.contains(RealmListContainer())) + assertFalse(frozenObject.nullableRealmAnyListField.contains(RealmAny.create(RealmListContainer()))) + } + + @Test + fun remove_unmanagedArgs() = runBlocking { + val frozenObject = realm.write { + val liveObject = copyToRealm(RealmListContainer()) + assertEquals(1, query().find().size) + assertFalse(liveObject.objectListField.remove(RealmListContainer())) + assertFalse(liveObject.nullableRealmAnyListField.remove(RealmAny.create(RealmListContainer()))) + assertEquals(1, query().find().size) + liveObject + } + assertFalse(frozenObject.objectListField.contains(RealmListContainer())) + assertFalse(frozenObject.nullableRealmAnyListField.contains(RealmAny.create(RealmListContainer()))) + } + + + private fun getCloseableRealm(): Realm = RealmConfiguration.Builder(schema = listTestSchema) .directory(tmpDir) diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmSetTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmSetTests.kt index dd5fb2aebf..d42e3ebcae 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmSetTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmSetTests.kt @@ -637,6 +637,34 @@ class RealmSetTests : CollectionQueryTests { Unit } + @Test + fun contains_unmanagedArgs() = runBlocking { + val frozenObject = realm.write { + val liveObject = copyToRealm(RealmSetContainer()) + assertEquals(1, query().find().size) + assertFalse(liveObject.objectSetField.contains(RealmSetContainer())) + assertFalse(liveObject.nullableRealmAnySetField.contains(RealmAny.create(RealmSetContainer()))) + assertEquals(1, query().find().size) + liveObject + } + assertFalse(frozenObject.objectSetField.contains(RealmSetContainer())) + assertFalse(frozenObject.nullableRealmAnySetField.contains(RealmAny.create(RealmSetContainer()))) + } + + @Test + fun remove_unmanagedArgs() = runBlocking { + val frozenObject = realm.write { + val liveObject = copyToRealm(RealmSetContainer()) + assertEquals(1, query().find().size) + assertFalse(liveObject.objectSetField.remove(RealmSetContainer())) + assertFalse(liveObject.nullableRealmAnySetField.remove(RealmAny.create(RealmSetContainer()))) + assertEquals(1, query().find().size) + liveObject + } + assertFalse(frozenObject.objectSetField.contains(RealmSetContainer())) + assertFalse(frozenObject.nullableRealmAnySetField.contains(RealmAny.create(RealmSetContainer()))) + } + private fun getCloseableRealm(): Realm = RealmConfiguration.Builder(schema = setOf(RealmSetContainer::class)) .directory(tmpDir) From d88c0e0aea15f0d9452ea935a9c8bcdb3d9a876a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Fri, 25 Aug 2023 21:38:14 +0200 Subject: [PATCH 13/41] Remove non-primitive converters and use explicit operators instead --- CHANGELOG.md | 1 + .../kotlin/internal/interop/RealmInterop.kt | 3 - .../kotlin/internal/interop/RealmInterop.kt | 10 - .../kotlin/internal/CollectionOperator.kt | 1 - .../io/realm/kotlin/internal/Converters.kt | 387 ++++++++++-------- .../io/realm/kotlin/internal/RealmAnyImpl.kt | 154 ------- .../kotlin/internal/RealmListInternal.kt | 264 ++++++------ .../realm/kotlin/internal/RealmMapInternal.kt | 297 +++++++------- .../kotlin/internal/RealmObjectHelper.kt | 236 +++++++---- .../realm/kotlin/internal/RealmResultsImpl.kt | 18 +- .../realm/kotlin/internal/RealmSetInternal.kt | 149 ++++--- .../kotlin/internal/query/ScalarQuery.kt | 168 ++++---- 12 files changed, 811 insertions(+), 877 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0380bda7c..ef872bd63b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ childA == childC if the content is the same. Custom implementations of these methods will be respected if they are present. (Issue [#1097](https://github.com/realm/realm-kotlin/issues/1097)) * Support for performing geospatial queries using the new classes: `GeoPoint`, `GeoCircle`, `GeoBox`, and `GeoPolygon`. See `GeoPoint` documentation on how to persist locations. (Issue [#1403](https://github.com/realm/realm-kotlin/pull/1403)) * Support for automatic resolution of embedded object constraints during migration through `RealmConfiguration.Builder.migration(migration: AutomaticSchemaMigration, resolveEmbeddedObjectConstraints: Boolean)`. (Issue [#1464](https://github.com/realm/realm-kotlin/issues/1464) +* Support for collections in `RealmAny`. (Issue [#1434](https://github.com/realm/realm-kotlin/issues/1434)) * [Sync] Add support for customizing authorization headers and adding additional custom headers to all Atlas App service requests with `AppConfiguration.Builder.authorizationHeaderName()` and `AppConfiguration.Builder.addCustomRequestHeader(...)`. (Issue [#1453](https://github.com/realm/realm-kotlin/pull/1453)) ### Fixed diff --git a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt index 2154b3542d..066471642f 100644 --- a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt +++ b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt @@ -317,9 +317,6 @@ expect object RealmInterop { fun realm_list_insert_set(list: RealmListPointer, index: Long): RealmSetPointer fun realm_list_insert_list(list: RealmListPointer, index: Long): RealmListPointer fun realm_list_insert_dictionary(list: RealmListPointer, index: Long): RealmMapPointer -// fun realm_list_set_set(list: RealmListPointer, index: Long): RealmSetPointer -// fun realm_list_set_list(list: RealmListPointer, index: Long): RealmListPointer -// fun realm_list_set_dictionary(list: RealmListPointer, index: Long): RealmMapPointer fun realm_list_set_set(list: RealmListPointer, index: Long): RealmSetPointer fun realm_list_set_list(list: RealmListPointer, index: Long): RealmListPointer fun realm_list_set_dictionary(list: RealmListPointer, index: Long): RealmMapPointer diff --git a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt index 95cff9016c..1b6cdef65e 100644 --- a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt +++ b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt @@ -556,16 +556,6 @@ actual object RealmInterop { actual fun realm_list_insert_dictionary(list: RealmListPointer, index: Long): RealmMapPointer { return LongPointerWrapper(realmc.realm_list_insert_dictionary(list.cptr(), index)) } -// actual fun realm_list_set_set(list: RealmListPointer, index: Long): RealmSetPointer { -// return LongPointerWrapper(realmc.realm_list_set_collection(list.cptr(), index)) -// } -// actual fun realm_list_set_list(list: RealmListPointer, index: Long): RealmListPointer { -// return LongPointerWrapper(realmc.realm_list_set_list(list.cptr(), index)) -// } -// actual fun realm_list_set_dictionary(list: RealmListPointer, index: Long): RealmMapPointer { -// return LongPointerWrapper(realmc.realm_list_set_dictionary(list.cptr(), index)) -// } - actual fun realm_list_set_set(list: RealmListPointer, index: Long): RealmSetPointer { return LongPointerWrapper(realmc.realm_list_set_set(list.cptr(), index)) } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/CollectionOperator.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/CollectionOperator.kt index 286847e1af..01a55091f4 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/CollectionOperator.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/CollectionOperator.kt @@ -22,7 +22,6 @@ import io.realm.kotlin.internal.interop.NativePointer internal interface CollectionOperator { val mediator: Mediator val realmReference: RealmReference - val valueConverter: RealmValueConverter val nativePointer: NativePointer } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/Converters.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/Converters.kt index 80d7b930bf..e59fda625d 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/Converters.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/Converters.kt @@ -21,11 +21,14 @@ import io.realm.kotlin.dynamic.DynamicMutableRealmObject import io.realm.kotlin.dynamic.DynamicRealmObject import io.realm.kotlin.ext.asRealmObject import io.realm.kotlin.internal.interop.MemTrackingAllocator +import io.realm.kotlin.internal.interop.RealmListPointer +import io.realm.kotlin.internal.interop.RealmMapPointer import io.realm.kotlin.internal.interop.RealmObjectInterop import io.realm.kotlin.internal.interop.RealmQueryArgument import io.realm.kotlin.internal.interop.RealmQueryArgumentList import io.realm.kotlin.internal.interop.RealmQueryListArgument import io.realm.kotlin.internal.interop.RealmQuerySingleArgument +import io.realm.kotlin.internal.interop.RealmSetPointer import io.realm.kotlin.internal.interop.RealmValue import io.realm.kotlin.internal.interop.Timestamp import io.realm.kotlin.internal.interop.ValueType @@ -101,63 +104,146 @@ public inline fun realmValueToRealmUUID(transport: RealmValue): RealmUUID = Real public inline fun realmValueToDecimal128(transport: RealmValue): Decimal128 = transport.getDecimal128Array().let { Decimal128.fromIEEE754BIDEncoding(it[1], it[0]) } -internal inline fun realmValueToRealmAny( - transport: RealmValue, - container: RealmAnyContainer, - issueDynamicObject: Boolean = false -): RealmAny? { - return realmValueToRealmAny(transport, container, issueDynamicObject, false) -} - @Suppress("ComplexMethod", "NestedBlockDepth") internal inline fun realmValueToRealmAny( - transport: RealmValue, - container: RealmAnyContainer, - issueDynamicObject: Boolean, + realmValue: RealmValue, + parent: RealmObjectReference<*>?, + mediator: Mediator, + owner: RealmReference, + issueDynamicObject: Boolean , issueDynamicMutableObject: Boolean, + set: () -> RealmSetPointer = { error("Cannot handled embedded sets") }, + list: () -> RealmListPointer = { error("Cannot handled embedded lists") }, + dictionary: () -> RealmMapPointer = { error("Cannot handled embedded dictionaries") }, ): RealmAny? { - return when (transport.isNull()) { + return when (realmValue.isNull()) { true -> null - false -> when (val type = transport.getType()) { + false -> when (val type = realmValue.getType()) { ValueType.RLM_TYPE_NULL -> null - ValueType.RLM_TYPE_INT -> RealmAny.create(transport.getLong()) - ValueType.RLM_TYPE_BOOL -> RealmAny.create(transport.getBoolean()) - ValueType.RLM_TYPE_STRING -> RealmAny.create(transport.getString()) - ValueType.RLM_TYPE_BINARY -> RealmAny.create(transport.getByteArray()) - ValueType.RLM_TYPE_TIMESTAMP -> RealmAny.create(RealmInstantImpl(transport.getTimestamp())) - ValueType.RLM_TYPE_FLOAT -> RealmAny.create(transport.getFloat()) - ValueType.RLM_TYPE_DOUBLE -> RealmAny.create(transport.getDouble()) - ValueType.RLM_TYPE_DECIMAL128 -> RealmAny.create(realmValueToDecimal128(transport)) + ValueType.RLM_TYPE_INT -> RealmAny.create(realmValue.getLong()) + ValueType.RLM_TYPE_BOOL -> RealmAny.create(realmValue.getBoolean()) + ValueType.RLM_TYPE_STRING -> RealmAny.create(realmValue.getString()) + ValueType.RLM_TYPE_BINARY -> RealmAny.create(realmValue.getByteArray()) + ValueType.RLM_TYPE_TIMESTAMP -> RealmAny.create(RealmInstantImpl(realmValue.getTimestamp())) + ValueType.RLM_TYPE_FLOAT -> RealmAny.create(realmValue.getFloat()) + ValueType.RLM_TYPE_DOUBLE -> RealmAny.create(realmValue.getDouble()) + ValueType.RLM_TYPE_DECIMAL128 -> RealmAny.create(realmValueToDecimal128(realmValue)) ValueType.RLM_TYPE_OBJECT_ID -> - RealmAny.create(BsonObjectId(transport.getObjectIdBytes())) - ValueType.RLM_TYPE_UUID -> RealmAny.create(RealmUUIDImpl(transport.getUUIDBytes())) + RealmAny.create(BsonObjectId(realmValue.getObjectIdBytes())) + ValueType.RLM_TYPE_UUID -> RealmAny.create(RealmUUIDImpl(realmValue.getUUIDBytes())) ValueType.RLM_TYPE_LINK -> { - val mediator = container.mediator - val owner = container.realm if (issueDynamicObject) { val clazz = when (issueDynamicMutableObject) { true -> DynamicMutableRealmObject::class false -> DynamicRealmObject::class } - val realmObject = realmValueToRealmObject(transport, clazz, mediator, owner) + val realmObject = realmValueToRealmObject(realmValue, clazz, mediator, owner) RealmAny.create(realmObject!!) } else { val clazz = owner.schemaMetadata - .get(transport.getLink().classKey) + .get(realmValue.getLink().classKey) ?.clazz ?: throw IllegalArgumentException("The object class is not present in the current schema - are you using an outdated schema version?") - val realmObject = realmValueToRealmObject(transport, clazz, mediator, owner) + val realmObject = realmValueToRealmObject(realmValue, clazz, mediator, owner) RealmAny.create(realmObject!! as RealmObject, clazz as KClass) } } - ValueType.RLM_TYPE_LIST -> { RealmAny.create(container.getList()) } - ValueType.RLM_TYPE_SET -> { RealmAny.create(container.getSet()) } - ValueType.RLM_TYPE_DICTIONARY -> { RealmAny.create(container.getDictionary()) } + ValueType.RLM_TYPE_SET -> { + val nativePointer = set() + val operator = realmAnySetOperator(mediator, owner, nativePointer, issueDynamicObject, issueDynamicMutableObject) + return RealmAny.create(ManagedRealmSet(parent, nativePointer, operator)) + } + ValueType.RLM_TYPE_LIST -> { + val nativePointer = list() + val operator = realmAnyListOperator(mediator, owner, nativePointer, issueDynamicObject, issueDynamicMutableObject) + RealmAny.create(ManagedRealmList(parent, nativePointer, operator)) + } + ValueType.RLM_TYPE_DICTIONARY -> { + val nativePointer = dictionary() + val operator = realmAnyMapOperator(mediator, owner, nativePointer, issueDynamicObject, issueDynamicMutableObject) + RealmAny.create(ManagedRealmDictionary(parent, nativePointer, operator)) + } else -> throw IllegalArgumentException("Unsupported type: ${type.name}") } } } + +// FIXME Do we really need to propagate dynamic and dynamic mutable info here +internal fun MemTrackingAllocator.realmAnyHandler(value: RealmAny?, + primitiveValues: (RealmValue) -> T = { throw IllegalArgumentException("Operation not support for primitive values")}, + reference: (RealmAny) -> T = { throw IllegalArgumentException("Operation not support for objects")}, + set: (RealmAny) -> T = { throw IllegalArgumentException("Operation not support for sets")}, + list: (RealmAny) -> T = { throw IllegalArgumentException("Operation not support for lists")}, + dictionary: (RealmAny) -> T = { throw IllegalArgumentException("Operation not support for dictionaries")}, +): T { + return when (value?.type) { + null -> + primitiveValues(nullTransport()) + io.realm.kotlin.types.RealmAny.Type.INT, + io.realm.kotlin.types.RealmAny.Type.BOOL, + io.realm.kotlin.types.RealmAny.Type.STRING, + io.realm.kotlin.types.RealmAny.Type.BINARY, + io.realm.kotlin.types.RealmAny.Type.TIMESTAMP, + io.realm.kotlin.types.RealmAny.Type.FLOAT, + io.realm.kotlin.types.RealmAny.Type.DOUBLE, + io.realm.kotlin.types.RealmAny.Type.DECIMAL128, + io.realm.kotlin.types.RealmAny.Type.OBJECT_ID, + io.realm.kotlin.types.RealmAny.Type.UUID -> + primitiveValues(realmAnyPrimitiveToRealmValue(value)) + io.realm.kotlin.types.RealmAny.Type.OBJECT -> { +// val converter = if (value.type == io.realm.kotlin.types.RealmAny.Type.OBJECT) { +// when ((value as RealmAnyImpl<*>).clazz) { +// DynamicRealmObject::class -> +// realmAnyConverter(mediator, realmReference, true) +// +// DynamicMutableRealmObject::class -> +// realmAnyConverter( +// mediator, +// realmReference, +// issueDynamicObject = true, +// issueDynamicMutableObject = true +// ) +// +// else -> +// realmAnyConverter(obj.mediator, obj.owner) +// } +// } else { +// realmAnyConverter(obj.mediator, obj.owner) +// } +// with(converter) { +// setValueTransportByKey(obj, key, publicToRealmValue(value)) +// } + // UPDATE POLICY +// val transport = realmAnyToRealmValueWithImport(value, mediator, realmReference, false) +// primitiveValues() +// primitiveValues(transport) + reference(value) + } + + io.realm.kotlin.types.RealmAny.Type.SET -> { + set(value) +// val nativePointer = set() +// val operator = realmAnySetOperator(mediator, realmReference, nativePointer, false, false) +// operator.addAll(value.asSet())//, updatePolicy, cache) + } + + io.realm.kotlin.types.RealmAny.Type.LIST -> { + list(value) +// val nativePointer = list() +// val operator = realmAnyList(mediator, realmReference, nativePointer, false, false) +// operator.insertAll(0, value.asList())//, updatePolicy, cache) + } + + io.realm.kotlin.types.RealmAny.Type.DICTIONARY -> { + dictionary(value) +// val nativePointer = dictionary() +// val operator = realmAnyMapOperator(mediator, realmReference, nativePointer, false, false) +// operator.putAll( value.asDictionary()) //, updatePolicy, cache ) + } + } +} + /** * Composite converters that combines a [PublicConverter] and a [StorageTypeConverter] into a * [RealmValueConverter]. @@ -361,7 +447,7 @@ internal object RealmValueArgumentConverter { is RealmObject -> { realmObjectTransport(realmObjectToRealmReferenceOrError(value)) } - is RealmAny -> realmAnyToRealmValue(value) + is RealmAny -> realmAnyToRealmValueWithoutImport(value) else -> { primitiveTypeConverters[value::class]?.let { converter -> with(converter as RealmValueConverter) { @@ -413,25 +499,6 @@ internal object RealmValueArgumentConverter { } } -// Realm object converter that also imports (copyToRealm) objects when setting it -internal fun realmObjectConverter( - clazz: KClass, - mediator: Mediator, - realmReference: RealmReference -): RealmValueConverter { - return object : PassThroughPublicConverter() { - // TODO OPTIMIZE We could lookup the companion and keep a reference to - // `companion.newInstance` method to avoid repeated mediator lookups in Link.toRealmObject() - override fun fromRealmValue(realmValue: RealmValue): T? = - realmValueToRealmObject(realmValue, clazz, mediator, realmReference) - - override fun MemTrackingAllocator.toRealmValue(value: T?): RealmValue = - realmObjectTransport( - value?.let { realmObjectToRealmReferenceOrError(it) as RealmObjectInterop } - ) - } -} - /** * Tries to convert a [RealmValue] into a [RealmAny], it handles the cases for all primitive types * and leaves the other cases to an else block. @@ -454,106 +521,106 @@ internal inline fun RealmValue.asPrimitiveRealmAnyOrElse( else -> elseBlock() } -@Suppress("OVERRIDE_BY_INLINE", "NestedBlockDepth", "LongParameterList") -internal fun realmAnyConverter( - mediator: Mediator, - realmReference: RealmReference, - issueDynamicObject: Boolean = false, - issueDynamicMutableObject: Boolean = false, - updatePolicy: UpdatePolicy = UpdatePolicy.ERROR, - cache: UnmanagedToManagedObjectCache = mutableMapOf() -): RealmValueConverter { - return object : PassThroughPublicConverter() { - override inline fun fromRealmValue(realmValue: RealmValue): RealmAny? = - realmValue.asPrimitiveRealmAnyOrElse { - when (val type = realmValue.getType()) { - ValueType.RLM_TYPE_LINK -> { - val link = realmValue.getLink() - val clazz = if (issueDynamicObject) { - if (issueDynamicMutableObject) { - DynamicMutableRealmObject::class - } else { - DynamicRealmObject::class - } - } else { - realmReference.schemaMetadata - .get(link.classKey) - ?.clazz - ?: throw IllegalArgumentException("The object class is not present in the current schema - are you using an outdated schema version?") - } - val internalObject = mediator.createInstanceOf(clazz) - val obj = internalObject.link( - realmReference, - mediator, - clazz, - link - ) - when (issueDynamicObject) { - true -> when (issueDynamicMutableObject) { - true -> RealmAny.create(obj as DynamicMutableRealmObject) - else -> RealmAny.create(obj as DynamicRealmObject) - } - - false -> RealmAny.create( - obj as RealmObject, - clazz as KClass - ) - } - } - - else -> throw IllegalArgumentException("Invalid type '$type' for RealmValue.") - } - } - - override inline fun MemTrackingAllocator.toRealmValue(value: RealmAny?): RealmValue { - return realmAnyToRealmValueWithImport( - value, - mediator, - realmReference, - issueDynamicObject, - updatePolicy, cache - ) - } - } -} - -/** - * // FIXME - * Used for converting values to query arguments. - */ -@Suppress("LongParameterList") -internal inline fun MemTrackingAllocator.realmAnyToRealmValueWithImport( - value: RealmAny?, - mediator: Mediator, - realmReference: RealmReference, - issueDynamicObject: Boolean = false, - updatePolicy: UpdatePolicy = UpdatePolicy.ERROR, - cache: UnmanagedToManagedObjectCache = mutableMapOf() -): RealmValue { - return when (value) { - null -> nullTransport() - else -> when (value.type) { - RealmAny.Type.OBJECT -> { - val obj = when (issueDynamicObject) { - true -> value.asRealmObject() - false -> value.asRealmObject() - } - val objRef = realmObjectToRealmReferenceWithImport(obj, mediator, realmReference, updatePolicy, cache) - realmObjectTransport(objRef as RealmObjectInterop) - } - RealmAny.Type.SET -> TODO() - RealmAny.Type.LIST -> { TODO() } - RealmAny.Type.DICTIONARY -> TODO() - else -> realmAnyPrimitiveToRealmValue(value) - } - } -} +//@Suppress("OVERRIDE_BY_INLINE", "NestedBlockDepth", "LongParameterList") +//internal fun realmAnyConverter( +// mediator: Mediator, +// realmReference: RealmReference, +// issueDynamicObject: Boolean = false, +// issueDynamicMutableObject: Boolean = false, +// updatePolicy: UpdatePolicy = UpdatePolicy.ERROR, +// cache: UnmanagedToManagedObjectCache = mutableMapOf() +//): RealmValueConverter { +// return object : PassThroughPublicConverter() { +// override inline fun fromRealmValue(realmValue: RealmValue): RealmAny? = +// realmValue.asPrimitiveRealmAnyOrElse { +// when (val type = realmValue.getType()) { +// ValueType.RLM_TYPE_LINK -> { +// val link = realmValue.getLink() +// val clazz = if (issueDynamicObject) { +// if (issueDynamicMutableObject) { +// DynamicMutableRealmObject::class +// } else { +// DynamicRealmObject::class +// } +// } else { +// realmReference.schemaMetadata +// .get(link.classKey) +// ?.clazz +// ?: throw IllegalArgumentException("The object class is not present in the current schema - are you using an outdated schema version?") +// } +// val internalObject = mediator.createInstanceOf(clazz) +// val obj = internalObject.link( +// realmReference, +// mediator, +// clazz, +// link +// ) +// when (issueDynamicObject) { +// true -> when (issueDynamicMutableObject) { +// true -> RealmAny.create(obj as DynamicMutableRealmObject) +// else -> RealmAny.create(obj as DynamicRealmObject) +// } +// +// false -> RealmAny.create( +// obj as RealmObject, +// clazz as KClass +// ) +// } +// } +// +// else -> throw IllegalArgumentException("Invalid type '$type' for RealmValue.") +// } +// } +// +// override inline fun MemTrackingAllocator.toRealmValue(value: RealmAny?): RealmValue { +// return realmAnyToRealmValueWithImport( +// value, +// mediator, +// realmReference, +// issueDynamicObject, +// updatePolicy, cache +// ) +// } +// } +//} + +///** +// * // FIXME +// * Used for converting values to query arguments. +// */ +//@Suppress("LongParameterList") +//internal inline fun MemTrackingAllocator.realmAnyToRealmValueWithImport( +// value: RealmAny?, +// mediator: Mediator, +// realmReference: RealmReference, +// issueDynamicObject: Boolean = false, +// updatePolicy: UpdatePolicy = UpdatePolicy.ERROR, +// cache: UnmanagedToManagedObjectCache = mutableMapOf() +//): RealmValue { +// return when (value) { +// null -> nullTransport() +// else -> when (value.type) { +// RealmAny.Type.OBJECT -> { +// val obj = when (issueDynamicObject) { +// true -> value.asRealmObject() +// false -> value.asRealmObject() +// } +// val objRef = realmObjectToRealmReferenceWithImport(obj, mediator, realmReference, updatePolicy, cache) +// realmObjectTransport(objRef as RealmObjectInterop) +// } +// RealmAny.Type.SET -> TODO() +// RealmAny.Type.LIST -> { TODO() } +// RealmAny.Type.DICTIONARY -> TODO() +// else -> realmAnyPrimitiveToRealmValue(value) +// } +// } +//} /** * Used for converting RealmAny values to RealmValues suitable for query arguments and primary keys. * Importing objects isn't allowed here. */ -internal inline fun MemTrackingAllocator.realmAnyToRealmValue(value: RealmAny?): RealmValue { +internal inline fun MemTrackingAllocator.realmAnyToRealmValueWithoutImport(value: RealmAny?): RealmValue { return when (value) { null -> nullTransport() else -> when (value.type) { @@ -602,6 +669,12 @@ internal inline fun realmValueToRealmObject( } } +internal fun MemTrackingAllocator.realmObjectToRealmValue(value: BaseRealmObject?): RealmValue { + return realmObjectTransport( + value?.let { realmObjectToRealmReferenceOrError(it) as RealmObjectInterop } + ) +} + // Will return a managed realm object reference or null. If the object is unmanaged it will be // imported according to the update policy. If the object is an outdated object it will throw an // error. @@ -660,23 +733,5 @@ internal inline fun realmObjectToRealmReferenceOrError( // Returns a converter fixed to convert objects of the given type in the context of the given mediator/realm internal fun converter( - clazz: KClass, - mediator: Mediator, - realmReference: RealmReference -): RealmValueConverter { - return if (realmObjectCompanionOrNull(clazz) != null || clazz in setOf>( - DynamicRealmObject::class, - DynamicMutableRealmObject::class - ) - ) { - realmObjectConverter( - clazz as KClass, - mediator, - realmReference - ) as RealmValueConverter - } else if (clazz == RealmAny::class) { - realmAnyConverter(mediator, realmReference) as RealmValueConverter - } else { - primitiveTypeConverters.getValue(clazz) as RealmValueConverter - } -} + clazz: KClass +): RealmValueConverter = primitiveTypeConverters.getValue(clazz) as RealmValueConverter diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmAnyImpl.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmAnyImpl.kt index 16389bed8c..69fdb8c003 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmAnyImpl.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmAnyImpl.kt @@ -16,14 +16,6 @@ package io.realm.kotlin.internal -import io.realm.kotlin.UpdatePolicy -import io.realm.kotlin.dynamic.DynamicMutableRealmObject -import io.realm.kotlin.dynamic.DynamicRealmObject -import io.realm.kotlin.internal.RealmObjectHelper.setValueTransportByKey -import io.realm.kotlin.internal.interop.CollectionType -import io.realm.kotlin.internal.interop.MemTrackingAllocator -import io.realm.kotlin.internal.interop.PropertyKey -import io.realm.kotlin.internal.interop.RealmInterop import io.realm.kotlin.types.BaseRealmObject import io.realm.kotlin.types.RealmAny import io.realm.kotlin.types.RealmDictionary @@ -37,152 +29,6 @@ import org.mongodb.kbson.Decimal128 import kotlin.reflect.KClass import kotlin.reflect.cast -internal sealed interface RealmAnyContainer { - - val mediator: Mediator - val realm: RealmReference - val obj: RealmObjectReference<*> - fun set( - allocator: MemTrackingAllocator, - value: RealmAny, - updatePolicy: UpdatePolicy = UpdatePolicy.ALL, - cache: UnmanagedToManagedObjectCache = mutableMapOf() - ) { - with(allocator) { - when (value.type) { - RealmAny.Type.INT, - RealmAny.Type.BOOL, - RealmAny.Type.STRING, - RealmAny.Type.BINARY, - RealmAny.Type.TIMESTAMP, - RealmAny.Type.FLOAT, - RealmAny.Type.DOUBLE, - RealmAny.Type.DECIMAL128, - RealmAny.Type.OBJECT_ID, - RealmAny.Type.UUID, - RealmAny.Type.OBJECT -> - setPrimitive(value) - RealmAny.Type.SET -> { - createCollection(CollectionType.RLM_COLLECTION_TYPE_SET) - (getSet() as ManagedRealmSet).operator.addAll( - value.asSet(), - updatePolicy, - cache - ) - } - - RealmAny.Type.LIST -> { - createCollection(CollectionType.RLM_COLLECTION_TYPE_LIST) - (getList() as ManagedRealmList).operator.insertAll( - 0, - value.asList(), - updatePolicy, - cache - ) - } - RealmAny.Type.DICTIONARY -> { - createCollection(CollectionType.RLM_COLLECTION_TYPE_DICTIONARY) - (getDictionary() as ManagedRealmDictionary).operator.putAll( - value.asDictionary(), - updatePolicy, - cache - ) - } - } - } - } - - fun MemTrackingAllocator.setPrimitive(value: RealmAny) - fun MemTrackingAllocator.createCollection(collectionType: CollectionType) - fun getSet(): RealmSet - fun getList(): RealmList - fun getDictionary(): RealmDictionary -} - -internal class RealmAnyProperty( - override val obj: RealmObjectReference<*>, - val key: PropertyKey, - val issueDynamicObject: Boolean, - val issueDynamicMutableObject: Boolean, -) : RealmAnyContainer { - - override val mediator = obj.mediator - override val realm: RealmReference = obj.owner - - override fun MemTrackingAllocator.createCollection(collectionType: CollectionType) { - RealmInterop.realm_set_value(obj.objectPointer, key, nullTransport(), false) - when(collectionType) { - CollectionType.RLM_COLLECTION_TYPE_NONE -> TODO() - CollectionType.RLM_COLLECTION_TYPE_LIST -> { - RealmInterop.realm_set_list(obj.objectPointer, key) - } - CollectionType.RLM_COLLECTION_TYPE_SET -> { - RealmInterop.realm_set_set(obj.objectPointer, key) - } - CollectionType.RLM_COLLECTION_TYPE_DICTIONARY -> { - RealmInterop.realm_set_dictionary(obj.objectPointer, key) - } - else -> TODO() - } - } - - override fun MemTrackingAllocator.setPrimitive(value: RealmAny) { - val converter = if (value.type == RealmAny.Type.OBJECT) { - when ((value as RealmAnyImpl<*>).clazz) { - DynamicRealmObject::class -> - realmAnyConverter(obj.mediator, obj.owner, true) - DynamicMutableRealmObject::class -> - realmAnyConverter( - obj.mediator, - obj.owner, - issueDynamicObject = true, - issueDynamicMutableObject = true - ) - else -> - realmAnyConverter(obj.mediator, obj.owner) - } - } else { - realmAnyConverter(obj.mediator, obj.owner) - } - with(converter) { - setValueTransportByKey(obj, key, publicToRealmValue(value)) - } - } - - override fun getSet(): RealmSet { - val nativePointer = RealmInterop.realm_get_set(obj.objectPointer, key) - val operator = RealmAnySetOperator( - mediator, - realm, - nativePointer, - issueDynamicObject, - issueDynamicMutableObject - ) - return ManagedRealmSet(obj, nativePointer, operator) - } - - override fun getList(): RealmList { - val nativePointer = RealmInterop.realm_get_list(obj.objectPointer, key) - val operator = RealmAnyListOperator(mediator, realm, nativePointer, issueDynamicObject = issueDynamicObject, issueDynamicMutableObject = issueDynamicMutableObject) - val realmAnyList = ManagedRealmList(obj, nativePointer, operator) - return realmAnyList - } - - override fun getDictionary(): RealmDictionary { - val nativePointer = RealmInterop.realm_get_dictionary(obj.objectPointer, key) - val operator = RealmAnyMapOperator( - mediator, - realm, - realmAnyConverter(mediator, realm, issueDynamicObject, issueDynamicMutableObject), - converter(String::class, mediator, realm), - nativePointer, - issueDynamicObject, issueDynamicMutableObject - ) - val realmAnyDictionary = ManagedRealmDictionary(obj, nativePointer, operator) - return realmAnyDictionary - } -} - internal class RealmAnyImpl constructor( override val type: RealmAny.Type, internal val clazz: KClass, diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmListInternal.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmListInternal.kt index 429050538b..c6f2cb8921 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmListInternal.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmListInternal.kt @@ -18,19 +18,18 @@ package io.realm.kotlin.internal import io.realm.kotlin.UpdatePolicy import io.realm.kotlin.Versioned +import io.realm.kotlin.ext.asRealmObject import io.realm.kotlin.internal.RealmValueArgumentConverter.convertToQueryArgs import io.realm.kotlin.internal.interop.Callback import io.realm.kotlin.internal.interop.ClassKey -import io.realm.kotlin.internal.interop.CollectionType import io.realm.kotlin.internal.interop.RealmChangesPointer import io.realm.kotlin.internal.interop.RealmInterop import io.realm.kotlin.internal.interop.RealmInterop.realm_list_get -import io.realm.kotlin.internal.interop.RealmInterop.realm_list_is_valid import io.realm.kotlin.internal.interop.RealmInterop.realm_list_set_embedded import io.realm.kotlin.internal.interop.RealmListPointer import io.realm.kotlin.internal.interop.RealmNotificationTokenPointer import io.realm.kotlin.internal.interop.RealmObjectInterop -import io.realm.kotlin.internal.interop.ValueType +import io.realm.kotlin.internal.interop.RealmValue import io.realm.kotlin.internal.interop.getterScope import io.realm.kotlin.internal.interop.inputScope import io.realm.kotlin.internal.query.ObjectBoundQuery @@ -43,6 +42,7 @@ import io.realm.kotlin.query.RealmQuery import io.realm.kotlin.types.BaseRealmObject import io.realm.kotlin.types.RealmAny import io.realm.kotlin.types.RealmList +import io.realm.kotlin.types.RealmObject import kotlinx.coroutines.channels.ProducerScope import kotlinx.coroutines.flow.Flow import kotlin.reflect.KClass @@ -250,7 +250,7 @@ internal interface ListOperator : CollectionOperator { internal class PrimitiveListOperator( override val mediator: Mediator, override val realmReference: RealmReference, - override val valueConverter: RealmValueConverter, + val realmValueConverter: RealmValueConverter, override val nativePointer: RealmListPointer ) : ListOperator { @@ -258,7 +258,7 @@ internal class PrimitiveListOperator( override fun get(index: Int): E { return getterScope { val transport = realm_list_get(nativePointer, index.toLong()) - with(valueConverter) { + with(realmValueConverter) { realmValueToPublic(transport) as E } } @@ -271,7 +271,7 @@ internal class PrimitiveListOperator( cache: UnmanagedToManagedObjectCache ) { inputScope { - with(valueConverter) { + with(realmValueConverter) { val transport = publicToRealmValue(element) RealmInterop.realm_list_add(nativePointer, index.toLong(), transport) } @@ -287,7 +287,7 @@ internal class PrimitiveListOperator( ): E { return get(index).also { inputScope { - with(valueConverter) { + with(realmValueConverter) { val transport = publicToRealmValue(element) RealmInterop.realm_list_set(nativePointer, index.toLong(), transport) } @@ -299,9 +299,23 @@ internal class PrimitiveListOperator( realmReference: RealmReference, nativePointer: RealmListPointer ): ListOperator = - PrimitiveListOperator(mediator, realmReference, valueConverter, nativePointer) + PrimitiveListOperator(mediator, realmReference, realmValueConverter, nativePointer) } +internal fun realmAnyListOperator( + mediator: Mediator, + realm: RealmReference, + nativePointer: RealmListPointer, + issueDynamicObject: Boolean, + issueDynamicMutableObject: Boolean, +) : RealmAnyListOperator = RealmAnyListOperator( + mediator, + realm, + nativePointer, + issueDynamicObject = issueDynamicObject, + issueDynamicMutableObject = issueDynamicMutableObject + ) + @Suppress("LongParameterList") internal class RealmAnyListOperator( override val mediator: Mediator, @@ -313,48 +327,18 @@ internal class RealmAnyListOperator( val issueDynamicMutableObject: Boolean ) : ListOperator { - override val valueConverter: RealmValueConverter = realmAnyConverter(mediator, realmReference, issueDynamicObject, issueDynamicMutableObject) - @Suppress("UNCHECKED_CAST") override fun get(index: Int): RealmAny? { return getterScope { val transport = realm_list_get(nativePointer, index.toLong()) - val element: ValueType = transport.getType() - if (element == ValueType.RLM_TYPE_SET) { - val newNativePointer = RealmInterop.realm_list_get_set(nativePointer, index.toLong()) - val operator = RealmAnySetOperator( - mediator, - realmReference, - newNativePointer, - issueDynamicObject, - issueDynamicMutableObject - ) - val realmAnySet = ManagedRealmSet(null, newNativePointer, operator) - RealmAny.Companion.create(realmAnySet) - } else if (element == ValueType.RLM_TYPE_LIST) { - val newNativePointer = RealmInterop.realm_list_get_list(nativePointer, index.toLong()) - val operator = RealmAnyListOperator(mediator, realmReference, newNativePointer, updatePolicy, cache, issueDynamicObject, issueDynamicMutableObject) - val realmAnyList = ManagedRealmList(null, newNativePointer, operator) - RealmAny.Companion.create(realmAnyList) - } else if (element == ValueType.RLM_TYPE_DICTIONARY) { - val newNativePointer = RealmInterop.realm_list_get_dictionary(nativePointer, index.toLong()) - val operator = RealmAnyMapOperator( - mediator, realmReference, - realmAnyConverter(mediator, realmReference, issueDynamicObject, issueDynamicMutableObject), - converter(String::class, mediator, realmReference), - newNativePointer, issueDynamicObject, issueDynamicMutableObject - ) - val realmDict = ManagedRealmDictionary( - null, - newNativePointer, - operator - ) - RealmAny.create(realmDict) - } else { - with(valueConverter) { - realmValueToPublic(transport) - } - } + return realmValueToRealmAny( + transport, null, mediator, realmReference, + issueDynamicObject, + issueDynamicMutableObject, + { RealmInterop.realm_list_get_set(nativePointer, index.toLong()) }, + { RealmInterop.realm_list_get_list(nativePointer, index.toLong()) }, + { RealmInterop.realm_list_get_dictionary(nativePointer, index.toLong()) } + ) } } @@ -365,42 +349,43 @@ internal class RealmAnyListOperator( cache: UnmanagedToManagedObjectCache ) { inputScope { - if (element != null && element.type in RealmAny.Type.COLLECTION_TYPES) { - when (element.type) { - RealmAny.Type.SET -> { - val newNativePointer = RealmInterop.realm_list_insert_set(nativePointer, index.toLong()) - val operator = RealmAnySetOperator( - mediator, - realmReference, - newNativePointer, - issueDynamicObject, - issueDynamicMutableObject - ) // , updatePolicy, cache) - operator.addAllInternal(element.asSet(), updatePolicy, cache) - } - RealmAny.Type.LIST -> { - val newNativePointer = RealmInterop.realm_list_insert_list(nativePointer, index.toLong()) - val operator = RealmAnyListOperator(mediator, realmReference, newNativePointer, updatePolicy, cache, issueDynamicObject, issueDynamicMutableObject) - operator.insertAll(0, element.asList(), updatePolicy, cache) - } - RealmAny.Type.DICTIONARY -> { - val newNativePointer = RealmInterop.realm_list_insert_dictionary(nativePointer, index.toLong()) - val operator = RealmAnyMapOperator( - mediator, realmReference, - realmAnyConverter(mediator, realmReference, issueDynamicObject, issueDynamicMutableObject), - converter(String::class, mediator, realmReference), - newNativePointer, issueDynamicObject, issueDynamicMutableObject - ) - operator.putAll(element.asDictionary(), updatePolicy, cache) - } - else -> TODO() - } - } else { - with(valueConverter) { - val transport = publicToRealmValue(element) - RealmInterop.realm_list_add(nativePointer, index.toLong(), transport) + realmAnyHandler( + value = element, + primitiveValues = { realmValue: RealmValue -> + RealmInterop.realm_list_add(nativePointer, index.toLong(), realmValue) + }, + reference = { realmValue -> + val objRef = + realmObjectToRealmReferenceWithImport(realmValue.asRealmObject(), mediator, realmReference, updatePolicy, cache) + RealmInterop.realm_list_add(nativePointer, index.toLong(), realmObjectTransport(objRef)) + }, + set = { realmValue -> + val nativePointer = RealmInterop.realm_list_insert_set(nativePointer, index.toLong()) + val operator = realmAnySetOperator( + mediator, + realmReference, + nativePointer, + issueDynamicObject, issueDynamicMutableObject + ) + operator.addAll(realmValue.asSet(), updatePolicy, cache) + }, + list = { realmValue -> + val nativePointer = RealmInterop.realm_list_insert_list(nativePointer, index.toLong()) + val operator = realmAnyListOperator( + mediator, + realmReference, + nativePointer, + issueDynamicObject, issueDynamicMutableObject + ) + operator.insertAll(0, realmValue.asList(), updatePolicy, cache) + }, + dictionary = { realmValue -> + val nativePointer = RealmInterop.realm_list_insert_dictionary(nativePointer, index.toLong()) + val operator = + realmAnyMapOperator(mediator, realmReference, nativePointer, issueDynamicObject, issueDynamicMutableObject) + operator.putAll(realmValue.asDictionary(), updatePolicy, cache) } - } + ) } } @@ -413,48 +398,46 @@ internal class RealmAnyListOperator( ): RealmAny? { return get(index).also { inputScope { - if (element != null && element.type in RealmAny.Type.COLLECTION_TYPES) { - // Core will not detect updates if resetting with similar containertype, so - // force detection of new reference by clearing the value first. - RealmInterop.realm_list_set(nativePointer, index.toLong(), nullTransport()) - when (element.type) { - RealmAny.Type.SET -> { - RealmInterop.realm_list_set_set(nativePointer, index.toLong()) - val newNativePointer = RealmInterop.realm_list_get_set(nativePointer, index.toLong()) - val operator = RealmAnySetOperator( - mediator, - realmReference, - newNativePointer, - issueDynamicObject, - issueDynamicMutableObject - ) // , updatePolicy, cache) - operator.addAllInternal(element.asSet(), updatePolicy, cache) - } - RealmAny.Type.LIST -> { - RealmInterop.realm_list_set_list(nativePointer, index.toLong()) - val newNativePointer = RealmInterop.realm_list_get_list(nativePointer, index.toLong()) - val operator = RealmAnyListOperator(mediator, realmReference, newNativePointer, updatePolicy, cache, issueDynamicObject, issueDynamicMutableObject) - operator.insertAll(0, element.asList(), updatePolicy, cache) - } - RealmAny.Type.DICTIONARY -> { - RealmInterop.realm_list_set_dictionary(nativePointer, index.toLong()) - val newNativePointer = RealmInterop.realm_list_get_dictionary(nativePointer, index.toLong()) - val operator = RealmAnyMapOperator( - mediator, realmReference, - realmAnyConverter(mediator, realmReference, issueDynamicObject, issueDynamicMutableObject), - converter(String::class, mediator, realmReference), - newNativePointer, issueDynamicObject, issueDynamicMutableObject - ) - operator.putAll(element.asDictionary(), updatePolicy, cache) - } - else -> TODO() - } - } else { - with(valueConverter) { - val transport = publicToRealmValue(element) - RealmInterop.realm_list_set(nativePointer, index.toLong(), transport) + realmAnyHandler( + value = element, + primitiveValues = { realmValue: RealmValue -> + RealmInterop.realm_list_set(nativePointer, index.toLong(), realmValue) + }, + reference = { realmValue -> + val objRef = + realmObjectToRealmReferenceWithImport(realmValue.asRealmObject(), mediator, realmReference, updatePolicy, cache) + RealmInterop.realm_list_set(nativePointer, index.toLong(), realmObjectTransport(objRef)) + }, + set = { realmValue -> + RealmInterop.realm_list_set(nativePointer, index.toLong(), nullTransport()) + val nativePointer = RealmInterop.realm_list_set_set(nativePointer, index.toLong()) + val operator = realmAnySetOperator( + mediator, + realmReference, + nativePointer, + issueDynamicObject, issueDynamicMutableObject + ) + operator.addAll(realmValue.asSet(), updatePolicy, cache) + }, + list = { realmValue -> + RealmInterop.realm_list_set(nativePointer, index.toLong(), nullTransport()) + val nativePointer = RealmInterop.realm_list_set_list(nativePointer, index.toLong()) + val operator = realmAnyListOperator( + mediator, + realmReference, + nativePointer, + issueDynamicObject, issueDynamicMutableObject + ) + operator.insertAll(0, realmValue.asList(), updatePolicy, cache) + }, + dictionary = { realmValue -> + RealmInterop.realm_list_set(nativePointer, index.toLong(), nullTransport()) + val nativePointer = RealmInterop.realm_list_set_dictionary(nativePointer, index.toLong()) + val operator = + realmAnyMapOperator(mediator, realmReference, nativePointer, issueDynamicObject, issueDynamicMutableObject) + operator.putAll(realmValue.asDictionary(), updatePolicy, cache) } - } + ) } } } @@ -466,10 +449,9 @@ internal class RealmAnyListOperator( RealmAnyListOperator(mediator, realmReference, nativePointer, issueDynamicObject = issueDynamicObject, issueDynamicMutableObject = issueDynamicMutableObject) } -internal abstract class BaseRealmObjectListOperator( +internal abstract class BaseRealmObjectListOperator ( override val mediator: Mediator, override val realmReference: RealmReference, - override val valueConverter: RealmValueConverter, override val nativePointer: RealmListPointer, val clazz: KClass, val classKey: ClassKey, @@ -479,21 +461,18 @@ internal abstract class BaseRealmObjectListOperator( override fun get(index: Int): E { return getterScope { val transport = realm_list_get(nativePointer, index.toLong()) - with(valueConverter) { - realmValueToPublic(transport) as E - } + realmValueToRealmObject(transport, clazz, mediator, realmReference) as E } } } -internal class RealmObjectListOperator( +internal class RealmObjectListOperator( mediator: Mediator, realmReference: RealmReference, - converter: RealmValueConverter, nativePointer: RealmListPointer, clazz: KClass, classKey: ClassKey, -) : BaseRealmObjectListOperator(mediator, realmReference, converter, nativePointer, clazz, classKey) { +) : BaseRealmObjectListOperator(mediator, realmReference, nativePointer, clazz, classKey) { override fun insert( index: Int, @@ -530,11 +509,9 @@ internal class RealmObjectListOperator( cache ) val transport = realmObjectTransport(objRef as RealmObjectInterop) - with(valueConverter) { - val originalValue = get(index) - RealmInterop.realm_list_set(nativePointer, index.toLong(), transport) - originalValue - } + val originalValue = get(index) + RealmInterop.realm_list_set(nativePointer, index.toLong(), transport) + originalValue } } @@ -542,12 +519,9 @@ internal class RealmObjectListOperator( realmReference: RealmReference, nativePointer: RealmListPointer ): ListOperator { - val converter: RealmValueConverter = - converter(clazz, mediator, realmReference) as CompositeConverter return RealmObjectListOperator( mediator, realmReference, - converter, nativePointer, clazz, classKey @@ -558,11 +532,10 @@ internal class RealmObjectListOperator( internal class EmbeddedRealmObjectListOperator( mediator: Mediator, realmReference: RealmReference, - converter: RealmValueConverter, nativePointer: RealmListPointer, clazz: KClass, classKey: ClassKey, -) : BaseRealmObjectListOperator(mediator, realmReference, converter, nativePointer, clazz, classKey) { +) : BaseRealmObjectListOperator(mediator, realmReference, nativePointer, clazz, classKey) { @Suppress("UNCHECKED_CAST") override fun insert( @@ -592,11 +565,9 @@ internal class EmbeddedRealmObjectListOperator( // return null as this is not allowed for lists with non-nullable elements, so just return // the newly created object even though it goes against the list API. val embedded = realm_list_set_embedded(nativePointer, index.toLong()) - with(valueConverter) { - val newEmbeddedRealmObject = realmValueToPublic(embedded) as BaseRealmObject - RealmObjectHelper.assign(newEmbeddedRealmObject, element, updatePolicy, cache) - newEmbeddedRealmObject as E - } + val newEmbeddedRealmObject = realmValueToRealmObject(embedded, clazz, mediator, realmReference) as E + RealmObjectHelper.assign(newEmbeddedRealmObject, element, updatePolicy, cache) + newEmbeddedRealmObject } } @@ -604,12 +575,9 @@ internal class EmbeddedRealmObjectListOperator( realmReference: RealmReference, nativePointer: RealmListPointer ): EmbeddedRealmObjectListOperator { - val converter: RealmValueConverter = - converter(clazz, mediator, realmReference) as CompositeConverter return EmbeddedRealmObjectListOperator( mediator, realmReference, - converter, nativePointer, clazz, classKey diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt index 5f37d0db6e..bc74f6f33a 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt @@ -18,12 +18,12 @@ package io.realm.kotlin.internal import io.realm.kotlin.UpdatePolicy import io.realm.kotlin.Versioned +import io.realm.kotlin.dynamic.DynamicRealmObject import io.realm.kotlin.ext.asRealmObject import io.realm.kotlin.ext.isManaged import io.realm.kotlin.internal.RealmValueArgumentConverter.convertToQueryArgs import io.realm.kotlin.internal.interop.Callback import io.realm.kotlin.internal.interop.ClassKey -import io.realm.kotlin.internal.interop.CollectionType import io.realm.kotlin.internal.interop.RealmChangesPointer import io.realm.kotlin.internal.interop.RealmInterop import io.realm.kotlin.internal.interop.RealmInterop.realm_dictionary_erase @@ -37,7 +37,6 @@ import io.realm.kotlin.internal.interop.RealmNotificationTokenPointer import io.realm.kotlin.internal.interop.RealmObjectInterop import io.realm.kotlin.internal.interop.RealmResultsPointer import io.realm.kotlin.internal.interop.RealmValue -import io.realm.kotlin.internal.interop.ValueType import io.realm.kotlin.internal.interop.getterScope import io.realm.kotlin.internal.interop.inputScope import io.realm.kotlin.internal.query.ObjectBoundQuery @@ -52,6 +51,7 @@ import io.realm.kotlin.types.BaseRealmObject import io.realm.kotlin.types.RealmAny import io.realm.kotlin.types.RealmDictionary import io.realm.kotlin.types.RealmMap +import io.realm.kotlin.types.RealmObject import kotlinx.coroutines.channels.ProducerScope import kotlinx.coroutines.flow.Flow import kotlin.reflect.KClass @@ -215,14 +215,7 @@ internal interface MapOperator : CollectionOperator { } @Suppress("UNCHECKED_CAST") - fun getValue(resultsPointer: RealmResultsPointer, index: Int): V? { - return getterScope { - with(valueConverter) { - val transport = realm_results_get(resultsPointer, index.toLong()) - realmValueToPublic(transport) - } as V - } - } + fun getValue(resultsPointer: RealmResultsPointer, index: Int): V? @Suppress("UNCHECKED_CAST") fun getKey(resultsPointer: RealmResultsPointer, index: Int): K { @@ -286,7 +279,7 @@ internal interface MapOperator : CollectionOperator { internal open class PrimitiveMapOperator constructor( override val mediator: Mediator, override val realmReference: RealmReference, - override val valueConverter: RealmValueConverter, + val realmValueConverter: RealmValueConverter, override val keyConverter: RealmValueConverter, override val nativePointer: RealmMapPointer ) : MapOperator { @@ -301,7 +294,7 @@ internal open class PrimitiveMapOperator constructor( ): Pair { return inputScope { val keyTransport = with(keyConverter) { publicToRealmValue(key) } - with(valueConverter) { + with(realmValueConverter) { val valueTransport = publicToRealmValue(value) realm_dictionary_insert( nativePointer, @@ -317,7 +310,7 @@ internal open class PrimitiveMapOperator constructor( override fun eraseInternal(key: K): Pair { return inputScope { val keyTransport = with(keyConverter) { publicToRealmValue(key) } - with(valueConverter) { + with(realmValueConverter) { realm_dictionary_erase(nativePointer, keyTransport).let { Pair(realmValueToPublic(it.first), it.second) } @@ -331,19 +324,29 @@ internal open class PrimitiveMapOperator constructor( realm_dictionary_get(nativePointer, position) .let { val key = with(keyConverter) { realmValueToPublic(it.first) } - val value = with(valueConverter) { realmValueToPublic(it.second) } + val value = with(realmValueConverter) { realmValueToPublic(it.second) } Pair(key, value) } as Pair } } + override fun getValue(resultsPointer: RealmResultsPointer, index: Int): V? + { + return getterScope { + with(realmValueConverter) { + val transport = realm_results_get(resultsPointer, index.toLong()) + realmValueToPublic(transport) + } as V + } + } + override fun getInternal(key: K): V? { // Even though we are getting a value we need to free the data buffers of the string we // send down to Core, so we need to use an inputScope. return inputScope { val keyTransport = with(keyConverter) { publicToRealmValue(key) } val valueTransport = realm_dictionary_find(nativePointer, keyTransport) - with(valueConverter) { realmValueToPublic(valueTransport) } + with(realmValueConverter) { realmValueToPublic(valueTransport) } } } @@ -351,7 +354,8 @@ internal open class PrimitiveMapOperator constructor( // Even though we are getting a value we need to free the data buffers of the string values // we send down to Core, so we need to use an inputScope. return inputScope { - with(valueConverter) { + // FIXME This could potentially import an object? + with(realmValueConverter) { RealmInterop.realm_dictionary_contains_value( nativePointer, publicToRealmValue(value) @@ -370,25 +374,44 @@ internal open class PrimitiveMapOperator constructor( realmReference: RealmReference, nativePointer: RealmMapPointer ): MapOperator = - PrimitiveMapOperator(mediator, realmReference, valueConverter, keyConverter, nativePointer) + PrimitiveMapOperator(mediator, realmReference, realmValueConverter, keyConverter, nativePointer) } -@Suppress("LongParameterList") -internal class RealmAnyMapOperator constructor( +internal fun realmAnyMapOperator( mediator: Mediator, - realmReference: RealmReference, - valueConverter: RealmValueConverter, - keyConverter: RealmValueConverter, + realm: RealmReference, nativePointer: RealmMapPointer, - val issueDynamicObject: Boolean, - val issueDynamicMutableObject: Boolean -) : PrimitiveMapOperator( + issueDynamicObject: Boolean, + issueDynamicMutableObject: Boolean +): RealmAnyMapOperator = RealmAnyMapOperator( mediator, - realmReference, - valueConverter, - keyConverter, - nativePointer -) { + realm, + converter(String::class), + nativePointer, + issueDynamicObject, + issueDynamicMutableObject +) +@Suppress("LongParameterList") +internal class RealmAnyMapOperator constructor( + override val mediator: Mediator, + override val realmReference: RealmReference, + override val keyConverter: RealmValueConverter, + override val nativePointer: RealmMapPointer, + private val issueDynamicObject: Boolean, + private val issueDynamicMutableObject: Boolean +) : MapOperator { + + override var modCount: Int = 0 + + override fun eraseInternal(key: K): Pair { + return inputScope { + val keyTransport = with(keyConverter) { publicToRealmValue(key) } + realm_dictionary_erase(nativePointer, keyTransport).let { + Pair(realmAny(it.first, keyTransport), it.second) + } + } + } + override fun containsValueInternal(value: RealmAny?): Boolean { // Unmanaged objects are never found in a managed dictionary if (value?.type == RealmAny.Type.OBJECT) { @@ -398,12 +421,10 @@ internal class RealmAnyMapOperator constructor( // Even though we are getting a value we need to free the data buffers of the string values // we send down to Core, so we need to use an inputScope. return inputScope { - with(valueConverter) { - RealmInterop.realm_dictionary_contains_value( - nativePointer, - publicToRealmValue(value) - ) - } + RealmInterop.realm_dictionary_contains_value( + nativePointer, + realmAnyToRealmValueWithoutImport(value) + ) } } @@ -413,57 +434,51 @@ internal class RealmAnyMapOperator constructor( realm_dictionary_get(nativePointer, position) .let { val keyTransport: K = with(keyConverter) { realmValueToPublic(it.first) as K } - // FIXME Should we have realm_dictionaryy_get_list, ..._dict, ..._set return keyTransport to getInternal(keyTransport) } } } + + override fun getValue(resultsPointer: RealmResultsPointer, index: Int): RealmAny? { + return getterScope { + val transport = realm_results_get(resultsPointer, index.toLong()) + realmValueToRealmAny(transport, null, mediator, realmReference, issueDynamicObject, issueDynamicMutableObject, + {TODO("Nested sets cannot be obtained from iterator")}, + {TODO("Nested lists cannot be obtained from iterator")}, + {TODO("Nested dictionaries cannot be obtained from iterator")}, + ) + } + } + + override fun copy( + realmReference: RealmReference, + nativePointer: RealmMapPointer + ): MapOperator = + RealmAnyMapOperator(mediator, realmReference, keyConverter, nativePointer, issueDynamicObject, issueDynamicMutableObject) + + override fun areValuesEqual(expected: RealmAny?, actual: RealmAny?): Boolean { + return expected == actual + } + override fun getInternal(key: K): RealmAny? { return inputScope { val keyTransport: RealmValue = with(keyConverter) { publicToRealmValue(key) } val valueTransport: RealmValue = realm_dictionary_find(nativePointer, keyTransport) - val element = valueTransport.getType() - if (element == ValueType.RLM_TYPE_SET) { - val newNativePointer = RealmInterop.realm_dictionary_find_set(nativePointer, keyTransport) - val operator = RealmAnySetOperator( - mediator, - realmReference, - newNativePointer, - issueDynamicObject, - issueDynamicMutableObject - ) - val realmAnySet = ManagedRealmSet(null, newNativePointer, operator) - RealmAny.Companion.create(realmAnySet) - } else if (element == ValueType.RLM_TYPE_LIST) { - val newNativePointer = RealmInterop.realm_dictionary_find_list(nativePointer, keyTransport) - val operator = RealmAnyListOperator(mediator, realmReference, newNativePointer, issueDynamicObject = issueDynamicObject, issueDynamicMutableObject = issueDynamicMutableObject) - val realmAnyList = ManagedRealmList(null, newNativePointer, operator) - RealmAny.Companion.create(realmAnyList) - } else if (element == ValueType.RLM_TYPE_DICTIONARY) { - val newNativePointer = RealmInterop.realm_dictionary_find_dictionary(nativePointer, keyTransport) - val operator = RealmAnyMapOperator( - mediator, - realmReference, - realmAnyConverter(mediator, realmReference, issueDynamicObject, issueDynamicMutableObject), - converter(String::class, mediator, realmReference), - newNativePointer, - issueDynamicObject, - issueDynamicMutableObject - ) - val realmDict = ManagedRealmDictionary( - null, - newNativePointer, - operator - ) - RealmAny.create(realmDict) - } else { - with(valueConverter) { - realmValueToPublic(valueTransport) - } - } + realmAny(valueTransport, keyTransport) } } + private fun realmAny( + valueTransport: RealmValue, + keyTransport: RealmValue + ) = realmValueToRealmAny( + valueTransport, null, mediator, realmReference, + issueDynamicObject, + issueDynamicMutableObject, + { RealmInterop.realm_dictionary_find_set(nativePointer, keyTransport) }, + { RealmInterop.realm_dictionary_find_list(nativePointer, keyTransport) } + ) { RealmInterop.realm_dictionary_find_dictionary(nativePointer, keyTransport) } + override fun insertInternal( key: K, value: RealmAny?, @@ -472,67 +487,64 @@ internal class RealmAnyMapOperator constructor( ): Pair { return inputScope { val keyTransport = with(keyConverter) { publicToRealmValue(key) } - if (value != null && value.type in RealmAny.Type.COLLECTION_TYPES) { - // Core will not detect updates if resetting with similar containertype, so - // force detection of new reference by clearing the value first. - realm_dictionary_insert(nativePointer, keyTransport, nullTransport()) - when (value.type) { - RealmAny.Type.SET -> { - val newNativePointer = RealmInterop.realm_dictionary_insert_set(nativePointer, keyTransport) - val operator = RealmAnySetOperator( - mediator, - realmReference, - newNativePointer, - issueDynamicObject, - issueDynamicMutableObject - ) // , updatePolicy, cache) - operator.addAllInternal(value.asSet(), updatePolicy, cache) - // FIXME Return value for updates??!? - RealmAny.create(ManagedRealmSet(null, newNativePointer, operator)) to true + return realmAnyHandler(value, + primitiveValues = { + realm_dictionary_insert(nativePointer, keyTransport, it).let { result -> + realmAny(result.first, keyTransport) to result.second } - RealmAny.Type.LIST -> { - val newNativePointer = RealmInterop.realm_dictionary_insert_list(nativePointer, keyTransport) - val operator = RealmAnyListOperator(mediator, realmReference, newNativePointer, updatePolicy, cache, issueDynamicObject, issueDynamicMutableObject) - // FIXME Return value for updates??!? - operator.insertAll(0, value.asList(), updatePolicy, cache) - RealmAny.create(ManagedRealmList(null, newNativePointer, operator)) to true + }, + reference = { + val obj = when (issueDynamicObject) { + true -> it.asRealmObject() + false -> it.asRealmObject() } - RealmAny.Type.DICTIONARY -> { - val newNativePointer = RealmInterop.realm_dictionary_insert_dictionary(nativePointer, keyTransport) - val operator = RealmAnyMapOperator( - mediator, realmReference, - realmAnyConverter(mediator, realmReference, issueDynamicObject, issueDynamicMutableObject), - converter(String::class, mediator, realmReference), - newNativePointer, - issueDynamicObject, issueDynamicMutableObject - ) - operator.putAll(value.asDictionary(), updatePolicy, cache) - // FIXME Return value for updates??!? - RealmAny.create(ManagedRealmDictionary(null, nativePointer, operator)) to true + val objRef = realmObjectToRealmReferenceWithImport(obj, mediator, realmReference, updatePolicy, cache) + val transport = realmObjectTransport(objRef as RealmObjectInterop) + realm_dictionary_insert(nativePointer, keyTransport, transport).let { result -> + realmAny(result.first, keyTransport) to result.second } - else -> TODO() - } - } else { - with(valueConverter) { - val valueTransport = publicToRealmValue(value) - realm_dictionary_insert( + }, + set = { realmValue -> + val previous = getInternal(key) + val nativePointer = RealmInterop.realm_dictionary_insert_set(nativePointer, keyTransport) + val operator = realmAnySetOperator( + mediator, + realmReference, nativePointer, - keyTransport, - valueTransport - ).let { - Pair(realmValueToPublic(it.first), it.second) - } + issueDynamicObject, issueDynamicMutableObject + ) + operator.addAll(realmValue.asSet(), updatePolicy, cache) + previous to true + }, + list = { realmValue -> + val previous = getInternal(key) + val nativePointer = RealmInterop.realm_dictionary_insert_list(nativePointer, keyTransport) + val operator = realmAnyListOperator( + mediator, + realmReference, + nativePointer, + issueDynamicObject, issueDynamicMutableObject + ) + operator.insertAll(0, realmValue.asList(), updatePolicy, cache) + previous to true + }, + dictionary = { realmValue -> + val previous = getInternal(key) + val nativePointer = RealmInterop.realm_dictionary_insert_dictionary(nativePointer, keyTransport) + val operator = + realmAnyMapOperator(mediator, realmReference, nativePointer, issueDynamicObject, issueDynamicMutableObject) + operator.putAll(realmValue.asDictionary(), updatePolicy, cache) + previous to true } - } + ) } } } @Suppress("LongParameterList") -internal abstract class BaseRealmObjectMapOperator constructor( +internal abstract class BaseRealmObjectMapOperator constructor( override val mediator: Mediator, override val realmReference: RealmReference, - override val valueConverter: RealmValueConverter, override val keyConverter: RealmValueConverter, override val nativePointer: RealmMapPointer, val clazz: KClass, @@ -589,6 +601,13 @@ internal abstract class BaseRealmObjectMapOperator constructor( } as V? } + override fun getValue(resultsPointer: RealmResultsPointer, index: Int): V? { + return getterScope { + val transport = realm_results_get(resultsPointer, index.toLong()) + realmValueToRealmObject(transport, clazz, mediator, realmReference) + } + } + override fun containsValueInternal(value: V): Boolean { value?.also { // Unmanaged objects are never found in a managed dictionary @@ -598,12 +617,10 @@ internal abstract class BaseRealmObjectMapOperator constructor( // Even though we are getting a value we need to free the data buffers of the string we // send down to Core, so we need to use an inputScope. return inputScope { - with(valueConverter) { - RealmInterop.realm_dictionary_contains_value( - nativePointer, - publicToRealmValue(value) - ) - } + RealmInterop.realm_dictionary_contains_value( + nativePointer, + realmObjectToRealmValue(value) + ) } } @@ -623,8 +640,7 @@ internal abstract class BaseRealmObjectMapOperator constructor( ): MapOperator = RealmObjectMapOperator( mediator, realmReference, - converter(clazz, mediator, realmReference), - converter(String::class, mediator, realmReference) as RealmValueConverter, + converter(String::class) as RealmValueConverter, nativePointer, clazz, classKey @@ -632,10 +648,9 @@ internal abstract class BaseRealmObjectMapOperator constructor( } @Suppress("LongParameterList") -internal class RealmObjectMapOperator constructor( +internal class RealmObjectMapOperator constructor( mediator: Mediator, realmReference: RealmReference, - valueConverter: RealmValueConverter, keyConverter: RealmValueConverter, nativePointer: RealmMapPointer, clazz: KClass, @@ -643,7 +658,6 @@ internal class RealmObjectMapOperator constructor( ) : BaseRealmObjectMapOperator( mediator, realmReference, - valueConverter, keyConverter, nativePointer, clazz, @@ -682,13 +696,13 @@ internal class RealmObjectMapOperator constructor( } as Pair } } + } @Suppress("LongParameterList") internal class EmbeddedRealmObjectMapOperator constructor( mediator: Mediator, realmReference: RealmReference, - valueConverter: RealmValueConverter, keyConverter: RealmValueConverter, nativePointer: RealmMapPointer, clazz: KClass, @@ -696,7 +710,6 @@ internal class EmbeddedRealmObjectMapOperator constructo ) : BaseRealmObjectMapOperator( mediator, realmReference, - valueConverter, keyConverter, nativePointer, clazz, @@ -728,11 +741,9 @@ internal class EmbeddedRealmObjectMapOperator constructo // We cannot return the old object as it is deleted when losing its parent so just // return the newly created object even though it goes against the API val embedded = realm_dictionary_insert_embedded(nativePointer, keyTransport) - with(valueConverter) { - val newEmbeddedRealmObject = realmValueToPublic(embedded) as BaseRealmObject - RealmObjectHelper.assign(newEmbeddedRealmObject, value, updatePolicy, cache) - Pair(newEmbeddedRealmObject, true) - } + val newEmbeddedRealmObject = realmValueToRealmObject(embedded, clazz, mediator, realmReference) as V + RealmObjectHelper.assign(newEmbeddedRealmObject, value, updatePolicy, cache) + Pair(newEmbeddedRealmObject, true) } as Pair } } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt index b8d8de72be..5638753a84 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt @@ -223,7 +223,39 @@ internal object RealmObjectHelper { ) is MutableRealmInt -> setValueTransportByKey(obj, key, longTransport(value.get())) is RealmAny -> { - RealmAnyProperty(obj, key, false, false).set(this, value) + realmAnyHandler( + value = value, + primitiveValues = { realmValue -> setValueTransportByKey( obj, key, realmValue ) }, + reference = { realmValue -> + setObjectByKey( obj, key, realmValue.asRealmObject() ) + }, + set = { realmValue -> + RealmInterop.realm_set_value(obj.objectPointer, key, nullTransport(), false) + val nativePointer = RealmInterop.realm_set_set(obj.objectPointer, key) + val operator = realmAnySetOperator( + obj.mediator, + obj.owner, + nativePointer, + false, + false + ) + operator.addAll(value.asSet())//, updatePolicy, cache) + }, + list = { realmValue -> + RealmInterop.realm_set_value(obj.objectPointer, key, nullTransport(), false) + val nativePointer = RealmInterop.realm_set_list(obj.objectPointer, key) + val operator = + realmAnyListOperator(obj.mediator, obj.owner, nativePointer, false, false) + operator.insertAll(0, value.asList()) + }, + dictionary = { realmValue -> + RealmInterop.realm_set_value(obj.objectPointer, key, nullTransport(), false) + val nativePointer = RealmInterop.realm_set_dictionary(obj.objectPointer, key) + val operator = + realmAnyMapOperator(obj.mediator, obj.owner, nativePointer, false, false) + operator.putAll(value.asDictionary()) + } + ) } else -> throw IllegalArgumentException("Unsupported value for transport: $value") } @@ -290,7 +322,15 @@ internal object RealmObjectHelper { ): RealmAny? = getterScope { val key = obj.propertyInfoOrThrow(propertyName).key getRealmValueFromKey(obj, key) - ?.let { realmValueToRealmAny(it, RealmAnyProperty(obj, key, false, false)) } + ?.let { + realmValueToRealmAny( + it, obj, obj.mediator, obj.owner, + false, + false, + { RealmInterop.realm_get_set(obj.objectPointer, key) }, + { RealmInterop.realm_get_list(obj.objectPointer, key) } + ) { RealmInterop.realm_get_dictionary(obj.objectPointer, key) } + } } internal inline fun MemAllocator.getRealmValue( @@ -334,7 +374,7 @@ internal object RealmObjectHelper { obj: RealmObjectReference, propertyName: String ): ManagedMutableRealmInt? { - val converter = converter(Long::class, obj.mediator, obj.owner) + val converter = converter(Long::class) val propertyKey = obj.propertyInfoOrThrow(propertyName).key // In order to be able to use Kotlin's nullability handling baked into the accessor we need @@ -421,7 +461,7 @@ internal object RealmObjectHelper { CollectionOperatorType.PRIMITIVE -> PrimitiveListOperator( mediator, realm, - converter(clazz, mediator, realm) as CompositeConverter, + converter(clazz) as CompositeConverter, listPtr ) CollectionOperatorType.REALM_ANY -> RealmAnyListOperator( @@ -436,18 +476,16 @@ internal object RealmObjectHelper { RealmObjectListOperator( mediator, realm, - converter(clazz, mediator, realm) as CompositeConverter, listPtr, - clazz, + clazz as KClass, classKey, - ) + ) as ListOperator } CollectionOperatorType.EMBEDDED_OBJECT -> { val classKey: ClassKey = realm.schemaMetadata.getOrThrow(propertyMetadata.linkTarget).classKey EmbeddedRealmObjectListOperator( mediator, realm, - converter(clazz, mediator, realm) as RealmValueConverter, listPtr, clazz as KClass, classKey, @@ -513,7 +551,7 @@ internal object RealmObjectHelper { CollectionOperatorType.PRIMITIVE -> PrimitiveSetOperator( mediator, realm, - converter(clazz, mediator, realm), + converter(clazz), setPtr ) CollectionOperatorType.REALM_ANY -> RealmAnySetOperator( @@ -528,11 +566,10 @@ internal object RealmObjectHelper { RealmObjectSetOperator( mediator, realm, - converter(clazz, mediator, realm), setPtr, - clazz, + clazz as KClass, classKey - ) + ) as SetOperator } else -> throw IllegalArgumentException("Unsupported collection type: ${operatorType.name}") @@ -599,15 +636,14 @@ internal object RealmObjectHelper { CollectionOperatorType.PRIMITIVE -> PrimitiveMapOperator( mediator, realm, - converter(clazz, mediator, realm), - converter(String::class, mediator, realm), + converter(clazz), + converter(String::class), dictionaryPtr ) CollectionOperatorType.REALM_ANY -> RealmAnyMapOperator( mediator, realm, - realmAnyConverter(mediator, realm, issueDynamicObject, issueDynamicMutableObject), - converter(String::class, mediator, realm), + converter(String::class), dictionaryPtr, issueDynamicObject, issueDynamicMutableObject ) as MapOperator @@ -616,20 +652,18 @@ internal object RealmObjectHelper { RealmObjectMapOperator( mediator, realm, - converter(clazz, mediator, realm), - converter(String::class, mediator, realm), + converter(String::class), dictionaryPtr, - clazz, + clazz as KClass, classKey - ) + ) as MapOperator } CollectionOperatorType.EMBEDDED_OBJECT -> { val classKey = realm.schemaMetadata.getOrThrow(propertyMetadata.linkTarget).classKey EmbeddedRealmObjectMapOperator( mediator, realm, - converter(clazz, mediator, realm) as RealmValueConverter, - converter(String::class, mediator, realm), + converter(String::class), dictionaryPtr, clazz as KClass, classKey @@ -888,11 +922,16 @@ internal object RealmObjectHelper { obj.owner ) RealmAny::class -> realmValueToRealmAny( - transport, - RealmAnyProperty(obj, propertyInfo.key, true, issueDynamicMutableObject), - true, - issueDynamicMutableObject - ) + realmValue = transport, + parent = obj, + mediator = obj.mediator, + owner = obj.owner, + issueDynamicObject = true, + issueDynamicMutableObject = issueDynamicMutableObject, + set = { RealmInterop.realm_get_set(obj.objectPointer, propertyInfo.key) }, + list = { RealmInterop.realm_get_list(obj.objectPointer, propertyInfo.key) }, + ) { RealmInterop.realm_get_dictionary(obj.objectPointer, propertyInfo.key) } + else -> with(primitiveTypeConverters.getValue(clazz)) { realmValueToPublic(transport) } @@ -1034,76 +1073,101 @@ internal object RealmObjectHelper { } } when (propertyMetadata.collectionType) { - CollectionType.RLM_COLLECTION_TYPE_NONE -> when (propertyMetadata.type) { - PropertyType.RLM_PROPERTY_TYPE_OBJECT -> { - if (obj.owner.schemaMetadata[propertyMetadata.linkTarget]!!.isEmbeddedRealmObject) { - setEmbeddedRealmObjectByKey( - obj, - propertyMetadata.key, - value as BaseRealmObject?, - updatePolicy, - cache - ) - } else { - setObjectByKey( - obj, - propertyMetadata.key, - value as BaseRealmObject?, - updatePolicy, - cache - ) - } - } - PropertyType.RLM_PROPERTY_TYPE_MIXED -> { - val realmAnyValue = value as RealmAny? - when (realmAnyValue?.type) { - RealmAny.Type.OBJECT -> { - val objValue = value?.let { - val objectClass = ((it as RealmAnyImpl<*>).clazz) as KClass - if (objectClass == DynamicRealmObject::class || objectClass == DynamicMutableRealmObject::class) { - value.asRealmObject() - } else { - throw IllegalArgumentException("Dynamic RealmAny fields only support DynamicRealmObjects or DynamicMutableRealmObjects.") - } - } - val managedObj = realmObjectWithImport( - objValue, - obj.mediator, - obj.owner, + CollectionType.RLM_COLLECTION_TYPE_NONE -> { + val key = propertyMetadata.key + when (propertyMetadata.type) { + PropertyType.RLM_PROPERTY_TYPE_OBJECT -> { + if (obj.owner.schemaMetadata[propertyMetadata.linkTarget]!!.isEmbeddedRealmObject) { + setEmbeddedRealmObjectByKey( + obj, + key, + value as BaseRealmObject?, updatePolicy, cache - )!! + ) + } else { setObjectByKey( obj, - propertyMetadata.key, - managedObj, + key, + value as BaseRealmObject?, updatePolicy, cache ) } - else -> inputScope { - if (value == null) { - setValueTransportByKey(obj, propertyMetadata.key, nullTransport()) - } else { - val transport = - // FIXME Lacks tests - RealmAnyProperty( - obj, - propertyMetadata.key, - true, - true - ).set(this, value) + } + PropertyType.RLM_PROPERTY_TYPE_MIXED -> { + val realmAnyValue = value as RealmAny? + when (realmAnyValue?.type) { + RealmAny.Type.OBJECT -> { + val objValue = value?.let { + val objectClass = ((it as RealmAnyImpl<*>).clazz) as KClass + if (objectClass == DynamicRealmObject::class || objectClass == DynamicMutableRealmObject::class) { + value.asRealmObject() + } else { + throw IllegalArgumentException("Dynamic RealmAny fields only support DynamicRealmObjects or DynamicMutableRealmObjects.") + } + } + val managedObj = realmObjectWithImport( + objValue, + obj.mediator, + obj.owner, + updatePolicy, + cache + )!! + setObjectByKey( + obj, + key, + managedObj, + updatePolicy, + cache + ) + } + else -> inputScope { + if (value == null) { + setValueTransportByKey(obj, key, nullTransport()) + } else { + realmAnyHandler( + value = value, + primitiveValues = { realmValue -> setValueTransportByKey( obj, key, realmValue ) }, + reference = { realmValue -> + setObjectByKey( obj, key, realmValue.asRealmObject(), updatePolicy, cache ) + }, + set = { realmValue -> + val nativePointer = RealmInterop.realm_get_set(obj.objectPointer, key) + val operator = realmAnySetOperator( + obj.mediator, + obj.owner, + nativePointer, + false, + false + ) + operator.addAll(value.asSet(), updatePolicy, cache) + }, + list = { realmValue -> + val nativePointer = RealmInterop.realm_set_list(obj.objectPointer, key) + val operator = + realmAnyListOperator(obj.mediator, obj.owner, nativePointer, false, false) + operator.insertAll(0, value.asSet(), updatePolicy, cache) + }, + dictionary = { realmValue -> + val nativePointer = RealmInterop.realm_set_dictionary(obj.objectPointer, key) + val operator = + realmAnyMapOperator(obj.mediator, obj.owner, nativePointer, false, false) + operator.putAll(value.asDictionary(), updatePolicy, cache) + } + ) + } } } } - } - else -> { - val converter = primitiveTypeConverters.getValue(clazz) - .let { converter -> converter as RealmValueConverter } - inputScope { - with(converter) { - val realmValue = publicToRealmValue(value) - setValueTransportByKey(obj, propertyMetadata.key, realmValue) + else -> { + val converter = primitiveTypeConverters.getValue(clazz) + .let { converter -> converter as RealmValueConverter } + inputScope { + with(converter) { + val realmValue = publicToRealmValue(value) + setValueTransportByKey(obj, key, realmValue) + } } } } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmResultsImpl.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmResultsImpl.kt index fe82812f62..dc0cf94f80 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmResultsImpl.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmResultsImpl.kt @@ -35,7 +35,6 @@ import io.realm.kotlin.query.RealmQuery import io.realm.kotlin.query.RealmResults import io.realm.kotlin.query.TRUE_PREDICATE import io.realm.kotlin.types.BaseRealmObject -import io.realm.kotlin.types.RealmObject import kotlinx.coroutines.channels.ProducerScope import kotlinx.coroutines.flow.Flow import kotlin.reflect.KClass @@ -57,13 +56,6 @@ internal class RealmResultsImpl constructor( private val mode: Mode = Mode.RESULTS, ) : AbstractList(), RealmResults, InternalDeleteable, CoreNotifiable, ResultsChange>, RealmStateHolder { - @Suppress("UNCHECKED_CAST") - private val converter = realmObjectConverter( - clazz as KClass, - mediator, - realm - ) as RealmValueConverter - internal enum class Mode { // FIXME Needed to make working with @LinkingObjects easier. EMPTY, // RealmResults that is always empty. @@ -74,10 +66,12 @@ internal class RealmResultsImpl constructor( get() = RealmInterop.realm_results_count(nativePointer).toInt() override fun get(index: Int): E = getterScope { - with(converter) { - val transport = realm_results_get(nativePointer, index.toLong()) - realmValueToPublic(transport) - } as E + realmValueToRealmObject( + realm_results_get(nativePointer, index.toLong()), + clazz, + mediator, + realm + ) as E } override fun query(query: String, vararg args: Any?): RealmQuery = inputScope { diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmSetInternal.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmSetInternal.kt index 08e028c5df..af25323347 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmSetInternal.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmSetInternal.kt @@ -29,6 +29,7 @@ import io.realm.kotlin.internal.interop.RealmInterop.realm_set_get import io.realm.kotlin.internal.interop.RealmNotificationTokenPointer import io.realm.kotlin.internal.interop.RealmObjectInterop import io.realm.kotlin.internal.interop.RealmSetPointer +import io.realm.kotlin.internal.interop.RealmValue import io.realm.kotlin.internal.interop.ValueType import io.realm.kotlin.internal.interop.getterScope import io.realm.kotlin.internal.interop.inputScope @@ -49,7 +50,7 @@ import kotlin.reflect.KClass /** * Implementation for unmanaged sets, backed by a [MutableSet]. */ -internal class UnmanagedRealmSet( +public class UnmanagedRealmSet( private val backingSet: MutableSet = mutableSetOf() ) : RealmSet, InternalDeleteable, MutableSet by backingSet { override fun asFlow(): Flow> { @@ -238,7 +239,6 @@ internal interface SetOperator : CollectionOperator { updatePolicy: UpdatePolicy = UpdatePolicy.ALL, cache: UnmanagedToManagedObjectCache = mutableMapOf() ): Boolean { - realmReference.checkClosed() return addInternal(element, updatePolicy, cache) .also { modCount++ } } @@ -281,13 +281,12 @@ internal interface SetOperator : CollectionOperator { modCount++ } + abstract fun removeInternal(element: E): Boolean fun remove(element: E): Boolean { - return inputScope { - with(valueConverter) { - val transport = publicToRealmValue(element) - RealmInterop.realm_set_erase(nativePointer, transport) - } - }.also { modCount++ } + return removeInternal(element).also { + // FIXME Should this only be updated if above value is true? + modCount++ + } } fun removeAll(elements: Collection): Boolean { @@ -301,6 +300,20 @@ internal interface SetOperator : CollectionOperator { fun copy(realmReference: RealmReference, nativePointer: RealmSetPointer): SetOperator } +internal fun realmAnySetOperator( + mediator: Mediator, + realm: RealmReference, + nativePointer: RealmSetPointer, + issueDynamicObject: Boolean, + issueDynamicMutableObject: Boolean +): RealmAnySetOperator = RealmAnySetOperator( + mediator, + realm, + nativePointer, + issueDynamicObject, + issueDynamicMutableObject + ) + internal class RealmAnySetOperator( override val mediator: Mediator, override val realmReference: RealmReference, @@ -309,17 +322,19 @@ internal class RealmAnySetOperator( val issueDynamicMutableObject: Boolean ) : SetOperator { - override val valueConverter: RealmValueConverter = realmAnyConverter(mediator, realmReference, issueDynamicObject, issueDynamicMutableObject) override var modCount: Int = 0 @Suppress("UNCHECKED_CAST") override fun get(index: Int): RealmAny? { return getterScope { - with(valueConverter) { - val transport = realm_set_get(nativePointer, index.toLong()) - // Primitive + objects - realmValueToPublic(transport) - } + val transport = realm_set_get(nativePointer, index.toLong()) + return realmValueToRealmAny( + transport, null, mediator, realmReference, + issueDynamicObject, + issueDynamicMutableObject, + { error("Set should never container sets") }, + { error("Set should never container lists") } + ) { error("Set should never container dictionaries") } } } @@ -328,24 +343,32 @@ internal class RealmAnySetOperator( updatePolicy: UpdatePolicy, cache: UnmanagedToManagedObjectCache ): Boolean { - if (element?.type in RealmAny.Type.COLLECTION_TYPES) { - throw IllegalArgumentException("Cannot add collections to RealmSets") - } return inputScope { - with(valueConverter) { - // Primitive + objects with Import - val transport = publicToRealmValue(element) - RealmInterop.realm_set_insert(nativePointer, transport) - } + realmAnyHandler( + value = element, + primitiveValues = { realmValue: RealmValue -> + RealmInterop.realm_set_insert(nativePointer, realmValue) + }, + reference = { realmValue -> + val objRef = + realmObjectToRealmReferenceWithImport(realmValue.asRealmObject(), mediator, realmReference, updatePolicy, cache) + RealmInterop.realm_set_insert(nativePointer, realmObjectTransport(objRef)) + }, +// set = { realmValue -> throw IllegalArgumentException("Sets cannot contain other collections") }, +// list = { realmValue -> error("Sets cannot contain other collections ") }, +// dictionary = { realmValue -> error("Sets cannot contain other collections ") }, + ) } } - override fun remove(element: RealmAny?): Boolean { - // Unmanaged objects are never found in a managed dictionary + override fun removeInternal(element: RealmAny?): Boolean { if (element?.type == RealmAny.Type.OBJECT) { if (!element.asRealmObject().isManaged()) return false } - return super.remove(element) + return inputScope { + val transport = realmAnyToRealmValueWithoutImport(element) + RealmInterop.realm_set_erase(nativePointer, transport) + } } override fun contains(element: RealmAny?): Boolean { @@ -354,10 +377,8 @@ internal class RealmAnySetOperator( if (!element.asRealmObject().isManaged()) return false } return inputScope { - with(valueConverter) { - val transport = publicToRealmValue(element) - RealmInterop.realm_set_find(nativePointer, transport) - } + val transport = realmAnyToRealmValueWithoutImport(element) + RealmInterop.realm_set_find(nativePointer, transport) } } @@ -371,7 +392,7 @@ internal class RealmAnySetOperator( internal class PrimitiveSetOperator( override val mediator: Mediator, override val realmReference: RealmReference, - override val valueConverter: RealmValueConverter, + val realmValueConverter: RealmValueConverter, override val nativePointer: RealmSetPointer ) : SetOperator { @@ -380,7 +401,7 @@ internal class PrimitiveSetOperator( @Suppress("UNCHECKED_CAST") override fun get(index: Int): E { return getterScope { - with(valueConverter) { + with(realmValueConverter) { val transport = realm_set_get(nativePointer, index.toLong()) realmValueToPublic(transport) } as E @@ -393,16 +414,25 @@ internal class PrimitiveSetOperator( cache: UnmanagedToManagedObjectCache ): Boolean { return inputScope { - with(valueConverter) { + with(realmValueConverter) { val transport = publicToRealmValue(element) RealmInterop.realm_set_insert(nativePointer, transport) } } } + override fun removeInternal(element: E): Boolean { + return inputScope { + with(realmValueConverter) { + val transport = publicToRealmValue(element) + RealmInterop.realm_set_erase(nativePointer, transport) + } + } + } + override fun contains(element: E): Boolean { return inputScope { - with(valueConverter) { + with(realmValueConverter) { val transport = publicToRealmValue(element) RealmInterop.realm_set_find(nativePointer, transport) } @@ -413,17 +443,30 @@ internal class PrimitiveSetOperator( realmReference: RealmReference, nativePointer: RealmSetPointer ): SetOperator = - PrimitiveSetOperator(mediator, realmReference, valueConverter, nativePointer) + PrimitiveSetOperator(mediator, realmReference, realmValueConverter, nativePointer) } -internal class RealmObjectSetOperator constructor( - override val mediator: Mediator, - override val realmReference: RealmReference, - override val valueConverter: RealmValueConverter, - override val nativePointer: RealmSetPointer, - val clazz: KClass, +internal class RealmObjectSetOperator : SetOperator { + + override val mediator: Mediator + override val realmReference: RealmReference + override val nativePointer: RealmSetPointer + val clazz: KClass val classKey: ClassKey -) : SetOperator { + + constructor( + mediator: Mediator, + realmReference: RealmReference, + nativePointer: RealmSetPointer, + clazz: KClass, + classKey: ClassKey + ) { + this.mediator = mediator + this.realmReference = realmReference + this.nativePointer = nativePointer + this.clazz = clazz + this.classKey = classKey + } override var modCount: Int = 0 @@ -448,39 +491,35 @@ internal class RealmObjectSetOperator constructor( @Suppress("UNCHECKED_CAST") override fun get(index: Int): E { return getterScope { - with(valueConverter) { realm_set_get(nativePointer, index.toLong()) .let { transport -> when (ValueType.RLM_TYPE_NULL) { transport.getType() -> null - else -> realmValueToPublic(transport) + else -> realmValueToRealmObject(transport, clazz, mediator, realmReference) } } as E - } } } - override fun remove(element: E): Boolean { + override fun removeInternal(element: E): Boolean { // Unmanaged objects are never found in a managed set element?.also { if (!(it as RealmObjectInternal).isManaged()) return false } - return super.remove(element) + return inputScope { + val transport = realmObjectToRealmValue(element as BaseRealmObject?) + RealmInterop.realm_set_erase(nativePointer, transport) + } } + override fun contains(element: E): Boolean { // Unmanaged objects are never found in a managed set element?.also { if (!(it as RealmObjectInternal).isManaged()) return false } return inputScope { - val objRef = realmObjectToRealmReferenceWithImport( - element as BaseRealmObject?, - mediator, - realmReference, - UpdatePolicy.ALL, - mutableMapOf() - ) + val objRef = realmObjectToRealmReferenceOrError(element as BaseRealmObject?) val transport = realmObjectTransport(objRef as RealmObjectInterop) RealmInterop.realm_set_find(nativePointer, transport) } @@ -490,9 +529,7 @@ internal class RealmObjectSetOperator constructor( realmReference: RealmReference, nativePointer: RealmSetPointer ): SetOperator { - val converter = - converter(clazz, mediator, realmReference) as CompositeConverter - return RealmObjectSetOperator(mediator, realmReference, converter, nativePointer, clazz, classKey) + return RealmObjectSetOperator(mediator, realmReference, nativePointer, clazz, classKey) } } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/ScalarQuery.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/ScalarQuery.kt index 43e9964d93..be2b3fe08f 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/ScalarQuery.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/ScalarQuery.kt @@ -18,12 +18,16 @@ package io.realm.kotlin.internal.query import io.realm.kotlin.TypedRealm import io.realm.kotlin.dynamic.DynamicRealm +import io.realm.kotlin.internal.Decimal128Converter +import io.realm.kotlin.internal.DoubleConverter +import io.realm.kotlin.internal.FloatConverter +import io.realm.kotlin.internal.IntConverter import io.realm.kotlin.internal.Mediator import io.realm.kotlin.internal.Notifiable import io.realm.kotlin.internal.Observable +import io.realm.kotlin.internal.RealmInstantConverter import io.realm.kotlin.internal.RealmReference import io.realm.kotlin.internal.RealmResultsImpl -import io.realm.kotlin.internal.RealmValueConverter import io.realm.kotlin.internal.interop.ClassKey import io.realm.kotlin.internal.interop.PropertyType import io.realm.kotlin.internal.interop.RealmInterop @@ -33,10 +37,8 @@ import io.realm.kotlin.internal.interop.RealmInterop.realm_results_sum import io.realm.kotlin.internal.interop.RealmQueryPointer import io.realm.kotlin.internal.interop.RealmResultsPointer import io.realm.kotlin.internal.interop.RealmValue -import io.realm.kotlin.internal.interop.ValueType import io.realm.kotlin.internal.interop.getterScope -import io.realm.kotlin.internal.primitiveTypeConverters -import io.realm.kotlin.internal.realmAnyConverter +import io.realm.kotlin.internal.realmValueToRealmAny import io.realm.kotlin.internal.schema.PropertyMetadata import io.realm.kotlin.notifications.ResultsChange import io.realm.kotlin.query.RealmQuery @@ -50,7 +52,6 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import org.mongodb.kbson.BsonDecimal128 -import org.mongodb.kbson.Decimal128 import kotlin.reflect.KClass /** @@ -107,9 +108,9 @@ internal class CountQuery constructor( * Type-bound query linked to a property. Unlike [CountQuery] this is executed at a table level * rather than at a column level. */ -internal interface TypeBoundQuery { +internal interface TypeBoundQuery { val propertyMetadata: PropertyMetadata - val converter: RealmValueConverter<*> + val converter: (RealmValue) -> T? } /** @@ -126,15 +127,19 @@ internal class MinMaxQuery constructor( override val propertyMetadata: PropertyMetadata, private val type: KClass, private val queryType: AggregatorQueryType -) : BaseScalarQuery(realmReference, queryPointer, mediator, classKey, clazz), TypeBoundQuery, RealmScalarNullableQuery { +) : BaseScalarQuery(realmReference, queryPointer, mediator, classKey, clazz), TypeBoundQuery, RealmScalarNullableQuery { - override val converter: RealmValueConverter<*> = when (propertyMetadata.type) { - PropertyType.RLM_PROPERTY_TYPE_INT -> primitiveTypeConverters[Long::class]!! - PropertyType.RLM_PROPERTY_TYPE_FLOAT -> primitiveTypeConverters[Float::class]!! - PropertyType.RLM_PROPERTY_TYPE_DOUBLE -> primitiveTypeConverters[Double::class]!! - PropertyType.RLM_PROPERTY_TYPE_DECIMAL128 -> primitiveTypeConverters[Decimal128::class]!! - PropertyType.RLM_PROPERTY_TYPE_TIMESTAMP -> primitiveTypeConverters[RealmInstant::class]!! - PropertyType.RLM_PROPERTY_TYPE_MIXED -> realmAnyConverter(mediator, realmReference) + override val converter: (RealmValue) -> T? = when(propertyMetadata.type) { + PropertyType.RLM_PROPERTY_TYPE_INT -> { it -> IntConverter.fromRealmValue(it)?.let { coerceLong(propertyMetadata.name, it, type) } as T? } + PropertyType.RLM_PROPERTY_TYPE_FLOAT -> { it -> FloatConverter.fromRealmValue(it)?.let { coerceFloat(propertyMetadata.name, it, type) } as T? } + PropertyType.RLM_PROPERTY_TYPE_DOUBLE -> { it -> DoubleConverter.fromRealmValue(it)?.let { coerceDouble(propertyMetadata.name, it, type) } as T? } + PropertyType.RLM_PROPERTY_TYPE_TIMESTAMP -> { it -> RealmInstantConverter.fromRealmValue(it) as T? } + PropertyType.RLM_PROPERTY_TYPE_DECIMAL128 -> { it -> Decimal128Converter.fromRealmValue(it) as T? } + PropertyType.RLM_PROPERTY_TYPE_MIXED -> { it -> + // Mixed fields rely on updated realmReference to resolve objects, so postpone + // conversion until values are resolved to unity immediate and async results + error("Mixed values should be aggregated elsewhere") + } else -> throw IllegalArgumentException("Conversion not possible between '$type' and '${type.simpleName}'.") } @@ -143,7 +148,7 @@ internal class MinMaxQuery constructor( queryTypeValidator(propertyMetadata, type, validateTimestamp = true) } - override fun find(): T? = findFromResults(RealmInterop.realm_query_find_all(queryPointer)) + override fun find(): T? = findFromResults(RealmInterop.realm_query_find_all(queryPointer), realmReference) override fun asFlow(): Flow { realmReference.checkClosed() @@ -160,7 +165,7 @@ internal class MinMaxQuery constructor( // e.g. when computing MAX on a RealmAny property when the MAX value is a RealmObject private fun findFromResults( resultsPointer: RealmResultsPointer, - updatedRealmReference: RealmReference? = null + realmReference: RealmReference ): T? = getterScope { val transport = when (queryType) { AggregatorQueryType.MIN -> realm_results_min(resultsPointer, propertyMetadata.key) @@ -171,11 +176,11 @@ internal class MinMaxQuery constructor( @Suppress("UNCHECKED_CAST") when (type) { // Asynchronous aggregations require a converter with an updated realm reference - RealmAny::class -> when (updatedRealmReference) { - null -> converter - else -> realmAnyConverter(mediator, updatedRealmReference) - }.realmValueToPublic(transport) - else -> coerceType(converter, propertyMetadata.name, type, transport) + RealmAny::class -> + realmValueToRealmAny( + transport, null, mediator, realmReference, false, false, + ) as T? + else -> converter(transport) } as T? } } @@ -193,16 +198,16 @@ internal class SumQuery constructor( clazz: KClass, override val propertyMetadata: PropertyMetadata, private val type: KClass -) : BaseScalarQuery(realmReference, queryPointer, mediator, classKey, clazz), TypeBoundQuery, RealmScalarQuery { +) : BaseScalarQuery(realmReference, queryPointer, mediator, classKey, clazz), TypeBoundQuery, RealmScalarQuery { - // RealmAny SUMs are computed as Decimal128 and Float fields return Double - override val converter: RealmValueConverter<*> = when (propertyMetadata.type) { - PropertyType.RLM_PROPERTY_TYPE_INT -> primitiveTypeConverters[Long::class]!! - PropertyType.RLM_PROPERTY_TYPE_FLOAT -> primitiveTypeConverters[Double::class]!! - PropertyType.RLM_PROPERTY_TYPE_DOUBLE -> primitiveTypeConverters[Double::class]!! - PropertyType.RLM_PROPERTY_TYPE_TIMESTAMP -> primitiveTypeConverters[RealmInstant::class]!! + override val converter: (RealmValue) -> T? = when(propertyMetadata.type) { + PropertyType.RLM_PROPERTY_TYPE_INT -> { it -> IntConverter.fromRealmValue(it)?.let { coerceLong(propertyMetadata.name, it, type) } as T? } + PropertyType.RLM_PROPERTY_TYPE_FLOAT -> { it -> DoubleConverter.fromRealmValue(it)?.let { coerceDouble(propertyMetadata.name, it, type) } as T? } + PropertyType.RLM_PROPERTY_TYPE_DOUBLE -> { it -> DoubleConverter.fromRealmValue(it)?.let { coerceDouble(propertyMetadata.name, it, type) } as T? } + PropertyType.RLM_PROPERTY_TYPE_TIMESTAMP -> { it -> RealmInstantConverter.fromRealmValue(it) as T? } PropertyType.RLM_PROPERTY_TYPE_DECIMAL128, - PropertyType.RLM_PROPERTY_TYPE_MIXED -> primitiveTypeConverters[Decimal128::class]!! + PropertyType.RLM_PROPERTY_TYPE_MIXED -> + { it -> Decimal128Converter.fromRealmValue(it) as T? } else -> throw IllegalArgumentException("Conversion not possible between '$type' and '${type.simpleName}'.") } @@ -222,12 +227,7 @@ internal class SumQuery constructor( } private fun findFromResults(resultsPointer: RealmResultsPointer): T = getterScope { - val transport = realm_results_sum(resultsPointer, propertyMetadata.key) - - when (type) { - RealmAny::class -> converter.realmValueToPublic(transport) - else -> coerceType(converter, propertyMetadata.name, type, transport) - } + converter(realm_results_sum(resultsPointer, propertyMetadata.key)) } as T } @@ -269,69 +269,41 @@ private fun queryTypeValidator( } } -/** - * Converts a value in the form of "storage type", i.e. type of the transport object produced by the - * C-API, to a user-specified type in the query, i.e. "coerced type". - */ -@Suppress("ComplexMethod") -// TODO optimize: try to move this to query construction -private fun coerceType( - converter: RealmValueConverter<*>, - propertyName: String, - coercedType: KClass, - transport: RealmValue -): T? { - return when (transport.getType()) { - ValueType.RLM_TYPE_NULL -> null - // Core INT can be coerced to any numeric as long as Kotlin supports it - ValueType.RLM_TYPE_INT -> { - val storageTypeValue = converter.realmValueToPublic(transport) as Long? - when (coercedType) { - Short::class -> storageTypeValue?.toShort() - Int::class -> storageTypeValue?.toInt() - Byte::class -> storageTypeValue?.toByte() - Char::class -> storageTypeValue?.toInt()?.toChar() - Long::class -> storageTypeValue - Double::class -> storageTypeValue?.toDouble() - Float::class -> storageTypeValue?.toFloat() - else -> throw IllegalArgumentException("Cannot coerce type of property '$propertyName' to '${coercedType.simpleName}'.") - } - } - // Core FLOAT can be coerced to any numeric as long as Kotlin supports it - ValueType.RLM_TYPE_FLOAT -> { - val storageTypeValue = converter.realmValueToPublic(transport) as Float? - when (coercedType) { - Short::class -> storageTypeValue?.toInt()?.toShort() - Int::class -> storageTypeValue?.toInt() - Byte::class -> storageTypeValue?.toInt()?.toByte() - Char::class -> storageTypeValue?.toInt()?.toChar() - Long::class -> storageTypeValue?.toInt()?.toLong() - Double::class -> storageTypeValue?.toDouble() - Float::class -> storageTypeValue - else -> throw IllegalArgumentException("Cannot coerce type of property '$$propertyName' to '${coercedType.simpleName}'.") - } +internal fun coerceLong(propertyName: String, value: Long, coercedType: KClass<*>):Any { + return when (coercedType) { + Short::class -> value.toShort() + Int::class -> value.toInt() + Byte::class -> value.toByte() + Char::class -> value.toInt().toChar() + Long::class -> value + Double::class -> value.toDouble() + Float::class -> value.toFloat() + else -> throw IllegalArgumentException("Cannot coerce type of property '$propertyName' to '${coercedType.simpleName}'.") } - // Core DOUBLE can be coerced to any numeric as long as Kotlin supports it - ValueType.RLM_TYPE_DOUBLE -> { - val storageTypeValue = converter.realmValueToPublic(transport) as Double? - when (coercedType) { - Short::class -> storageTypeValue?.toInt()?.toShort() - Int::class -> storageTypeValue?.toInt() - Byte::class -> storageTypeValue?.toInt()?.toByte() - Char::class -> storageTypeValue?.toInt()?.toChar() - Long::class -> storageTypeValue?.toInt()?.toLong() - Double::class -> storageTypeValue - Float::class -> storageTypeValue?.toFloat() - else -> throw IllegalArgumentException("Cannot coerce type of property '$propertyName' to '${coercedType.simpleName}'.") - } - } - // Core TIMESTAMP cannot be coerced to any type other than RealmInstant - ValueType.RLM_TYPE_DECIMAL128, - ValueType.RLM_TYPE_TIMESTAMP -> { - converter.realmValueToPublic(transport) - } - else -> throw IllegalArgumentException("Cannot coerce type of property '$propertyName' to '${coercedType.simpleName}'.") - } as T? +} +internal fun coerceFloat(propertyName: String, value: Float, coercedType: KClass<*>): Any { + return when (coercedType) { + Short::class -> value.toInt().toShort() + Int::class -> value.toInt() + Byte::class -> value.toInt().toByte() + Char::class -> value.toInt().toChar() + Long::class -> value.toInt().toLong() + Double::class -> value.toDouble() + Float::class -> value + else -> throw IllegalArgumentException("Cannot coerce type of property '$$propertyName' to '${coercedType.simpleName}'.") + } +} +internal fun coerceDouble(propertyName: String, value: Double, coercedType: KClass<*>): Any { + return when (coercedType) { + Short::class -> value.toInt().toShort() + Int::class -> value.toInt() + Byte::class -> value.toInt().toByte() + Char::class -> value.toInt().toChar() + Long::class -> value.toInt().toLong() + Double::class -> value + Float::class -> value.toFloat() + else -> throw IllegalArgumentException("Cannot coerce type of property '$$propertyName' to '${coercedType.simpleName}'.") + } } private fun KClass<*>.isNumeric(): Boolean { From 6d0ab0f7808ae286c0bab490e7a8ab7fb3ad35b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Sun, 27 Aug 2023 17:46:08 +0200 Subject: [PATCH 14/41] More test fixes --- .../kotlin/internal/RealmListInternal.kt | 14 ++- .../realm/kotlin/internal/RealmMapInternal.kt | 8 +- .../kotlin/internal/RealmObjectHelper.kt | 11 ++- .../realm/kotlin/internal/RealmSetInternal.kt | 18 ++-- .../common/RealmAnyNestedCollectionTests.kt | 85 ++++++------------- .../dynamic/DynamicMutableRealmObjectTests.kt | 6 +- ...ealmAnyNestedDictionaryNotificationTest.kt | 8 +- 7 files changed, 65 insertions(+), 85 deletions(-) diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmListInternal.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmListInternal.kt index c6f2cb8921..f61f0fbcde 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmListInternal.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmListInternal.kt @@ -18,6 +18,8 @@ package io.realm.kotlin.internal import io.realm.kotlin.UpdatePolicy import io.realm.kotlin.Versioned +import io.realm.kotlin.dynamic.DynamicMutableRealmObject +import io.realm.kotlin.dynamic.DynamicRealmObject import io.realm.kotlin.ext.asRealmObject import io.realm.kotlin.internal.RealmValueArgumentConverter.convertToQueryArgs import io.realm.kotlin.internal.interop.Callback @@ -306,8 +308,8 @@ internal fun realmAnyListOperator( mediator: Mediator, realm: RealmReference, nativePointer: RealmListPointer, - issueDynamicObject: Boolean, - issueDynamicMutableObject: Boolean, + issueDynamicObject: Boolean = false, + issueDynamicMutableObject: Boolean = false, ) : RealmAnyListOperator = RealmAnyListOperator( mediator, realm, @@ -354,9 +356,13 @@ internal class RealmAnyListOperator( primitiveValues = { realmValue: RealmValue -> RealmInterop.realm_list_add(nativePointer, index.toLong(), realmValue) }, - reference = { realmValue -> + reference = { realmValue: RealmAny -> + val obj = when (issueDynamicObject) { + true -> realmValue.asRealmObject() + false -> realmValue.asRealmObject() + } val objRef = - realmObjectToRealmReferenceWithImport(realmValue.asRealmObject(), mediator, realmReference, updatePolicy, cache) + realmObjectToRealmReferenceWithImport(obj, mediator, realmReference, updatePolicy, cache) RealmInterop.realm_list_add(nativePointer, index.toLong(), realmObjectTransport(objRef)) }, set = { realmValue -> diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt index bc74f6f33a..ad70ff96ea 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt @@ -381,8 +381,8 @@ internal fun realmAnyMapOperator( mediator: Mediator, realm: RealmReference, nativePointer: RealmMapPointer, - issueDynamicObject: Boolean, - issueDynamicMutableObject: Boolean + issueDynamicObject: Boolean = false, + issueDynamicMutableObject: Boolean = false, ): RealmAnyMapOperator = RealmAnyMapOperator( mediator, realm, @@ -505,6 +505,7 @@ internal class RealmAnyMapOperator constructor( } }, set = { realmValue -> + realm_dictionary_insert(nativePointer, keyTransport, nullTransport()) val previous = getInternal(key) val nativePointer = RealmInterop.realm_dictionary_insert_set(nativePointer, keyTransport) val operator = realmAnySetOperator( @@ -517,6 +518,7 @@ internal class RealmAnyMapOperator constructor( previous to true }, list = { realmValue -> + realm_dictionary_insert(nativePointer, keyTransport, nullTransport()) val previous = getInternal(key) val nativePointer = RealmInterop.realm_dictionary_insert_list(nativePointer, keyTransport) val operator = realmAnyListOperator( @@ -529,6 +531,8 @@ internal class RealmAnyMapOperator constructor( previous to true }, dictionary = { realmValue -> + // Need to clear out + realm_dictionary_insert(nativePointer, keyTransport, nullTransport()) val previous = getInternal(key) val nativePointer = RealmInterop.realm_dictionary_insert_dictionary(nativePointer, keyTransport) val operator = diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt index 5638753a84..80f5c68f46 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt @@ -1133,26 +1133,25 @@ internal object RealmObjectHelper { setObjectByKey( obj, key, realmValue.asRealmObject(), updatePolicy, cache ) }, set = { realmValue -> - val nativePointer = RealmInterop.realm_get_set(obj.objectPointer, key) + val nativePointer = RealmInterop.realm_set_set(obj.objectPointer, key) val operator = realmAnySetOperator( obj.mediator, obj.owner, nativePointer, - false, - false + true, ) operator.addAll(value.asSet(), updatePolicy, cache) }, list = { realmValue -> val nativePointer = RealmInterop.realm_set_list(obj.objectPointer, key) val operator = - realmAnyListOperator(obj.mediator, obj.owner, nativePointer, false, false) - operator.insertAll(0, value.asSet(), updatePolicy, cache) + realmAnyListOperator(obj.mediator, obj.owner, nativePointer, true) + operator.insertAll(0, value.asList(), updatePolicy, cache) }, dictionary = { realmValue -> val nativePointer = RealmInterop.realm_set_dictionary(obj.objectPointer, key) val operator = - realmAnyMapOperator(obj.mediator, obj.owner, nativePointer, false, false) + realmAnyMapOperator(obj.mediator, obj.owner, nativePointer) operator.putAll(value.asDictionary(), updatePolicy, cache) } ) diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmSetInternal.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmSetInternal.kt index af25323347..a4420a6d34 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmSetInternal.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmSetInternal.kt @@ -18,6 +18,7 @@ package io.realm.kotlin.internal import io.realm.kotlin.UpdatePolicy import io.realm.kotlin.Versioned +import io.realm.kotlin.dynamic.DynamicRealmObject import io.realm.kotlin.ext.asRealmObject import io.realm.kotlin.ext.isManaged import io.realm.kotlin.internal.RealmValueArgumentConverter.convertToQueryArgs @@ -42,6 +43,7 @@ import io.realm.kotlin.notifications.internal.UpdatedSetImpl import io.realm.kotlin.query.RealmQuery import io.realm.kotlin.types.BaseRealmObject import io.realm.kotlin.types.RealmAny +import io.realm.kotlin.types.RealmObject import io.realm.kotlin.types.RealmSet import kotlinx.coroutines.channels.ProducerScope import kotlinx.coroutines.flow.Flow @@ -304,8 +306,8 @@ internal fun realmAnySetOperator( mediator: Mediator, realm: RealmReference, nativePointer: RealmSetPointer, - issueDynamicObject: Boolean, - issueDynamicMutableObject: Boolean + issueDynamicObject: Boolean = false, + issueDynamicMutableObject: Boolean = false, ): RealmAnySetOperator = RealmAnySetOperator( mediator, realm, @@ -350,13 +352,17 @@ internal class RealmAnySetOperator( RealmInterop.realm_set_insert(nativePointer, realmValue) }, reference = { realmValue -> + val obj = when (issueDynamicObject) { + true -> realmValue.asRealmObject() + false -> realmValue.asRealmObject() + } val objRef = - realmObjectToRealmReferenceWithImport(realmValue.asRealmObject(), mediator, realmReference, updatePolicy, cache) + realmObjectToRealmReferenceWithImport(obj, mediator, realmReference, updatePolicy, cache) RealmInterop.realm_set_insert(nativePointer, realmObjectTransport(objRef)) }, -// set = { realmValue -> throw IllegalArgumentException("Sets cannot contain other collections") }, -// list = { realmValue -> error("Sets cannot contain other collections ") }, -// dictionary = { realmValue -> error("Sets cannot contain other collections ") }, + set = { realmValue -> throw IllegalArgumentException("Sets cannot contain other collections") }, + list = { realmValue -> throw IllegalArgumentException("Sets cannot contain other collections ") }, + dictionary = { realmValue -> throw IllegalArgumentException("Sets cannot contain other collections ") }, ) } } diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt index 920f740c96..8f7eadfebf 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt @@ -122,7 +122,7 @@ class RealmAnyNestedCollectionTests { value = RealmAny.create(realmSetOf(RealmAny.create(realmListOf(RealmAny.create(5))))) }.let { - assertFailsWithMessage("Cannot add collections to RealmSets") { + assertFailsWithMessage("Sets cannot contain other collections") { copyToRealm(it) } } @@ -133,7 +133,7 @@ class RealmAnyNestedCollectionTests { ) ) }.let { - assertFailsWithMessage("Cannot add collections to RealmSets") { + assertFailsWithMessage("Sets cannot contain other collections") { copyToRealm(it) } } @@ -148,12 +148,14 @@ class RealmAnyNestedCollectionTests { ) val set = instance.value!!.asSet() - assertFailsWithMessage("Cannot add collections to RealmSets") { - set.add(RealmAny.create(realmListOf())) + val realmAnyList = RealmAny.create(realmListOf()) + assertFailsWithMessage("Sets cannot contain other collections") { + set.add(realmAnyList) } - assertFailsWithMessage("Cannot add collections to RealmSets") { - set.add(RealmAny.create(realmDictionaryOf())) + val realmAnyDictionary = RealmAny.create(realmDictionaryOf()) + assertFailsWithMessage("Sets cannot contain other collections") { + set.add(realmAnyDictionary) } } } @@ -241,6 +243,7 @@ class RealmAnyNestedCollectionTests { // Assert structure anyValue.asList().let { + it.contains(realmAnyListOf()) assertEquals(RealmAny.create(5), it[0]) assertEquals(RealmAny.create("Realm"), it[1]) assertEquals("SAMPLE", it[2]!!.asRealmObject().stringField) @@ -434,47 +437,6 @@ class RealmAnyNestedCollectionTests { nestedList[0]!!.asInt() } -// assertFailsWithMessage("List is no longer valid") { -// nestedList[0]!!.asInt() -// } - - -// assertFailsWithMessage("List is no longer valid") { -// nestedList[0]!!.asInt() -// -// } -// -// // Overwrite with null value -// instance.value = null -// assertFailsWithMessage("List is no longer valid") { -// nestedList[0]!!.asInt() -// } -// -// // Overwrite with another list suddently revives the old list -// instance.value = realmAnyListOf(5) -// assertFailsWithMessage("List is no longer valid") { -// nestedList[0]!!.asInt() -// } -// -// -// instance.value = realmAnyListOf() -// println("Updated empty") -// assertFailsWithMessage("List is no longer valid") { -// println("Assert empty") -// nestedList[0]!!.asInt() -// } -// -// // Overwrite nested list element with new collection of different type -// instance.value = realmAnySetOf(8) -// -// // Update old list -// assertFailsWithMessage("List is no longer valid") { -// nestedList.add(RealmAny.create(1)) -// } -// // FIXME Throws RLM_ERR_INDEX_OUT_OF_BOUNDS instead of RLM_ERR_ILLEGAL_OPERATION -// assertFailsWithMessage("List is no longer valid") { -// val realmAny = nestedList[0] -// } // Recreating list doesn't bring things back to shape instance.value = realmAnyListOf(realmAnyListOf(8)) assertFailsWithMessage("List is no longer valid") { @@ -639,7 +601,7 @@ class RealmAnyNestedCollectionTests { } @Test - fun nestedCollectionsInDictionary_put_invalidatesOldElement() = runBlocking { + fun nestedCollectionsInDictionary_put_invalidatesOldElement() = runBlocking { realm.write { val instance = copyToRealm( JsonStyleRealmObject().apply { @@ -648,24 +610,27 @@ class RealmAnyNestedCollectionTests { ) } ) - val nestedList = instance.value!!.asDictionary()["key"]!!.asList() + // Store local reference to existing list + var nestedList = instance.value!!.asDictionary()["key"]!!.asList() + // Accessing returns excepted value 5 assertEquals(5, nestedList[0]!!.asInt()) - // Overwrite nested list element with new list - instance.value!!.asDictionary()["key"] = null + // Overwriting exact list with new list + instance.value!!.asDictionary()["key"] = realmAnyListOf(7) + // Fails due to https://github.com/realm/realm-core/issues/6895 assertFailsWithMessage("List is no longer valid") { nestedList[0]!!.asInt() } - // Overwrite nested list element with new collection of different type - instance.value!!.asDictionary()["key"] = RealmAny.create(realmSetOf(RealmAny.create(8))) - // Access the old list - // FIXME Seems like we don't throw a nice error when accessing a delete collection - // Overwriting with different collection type seems to ruin original item without - // throwing proper fix - nestedList[0] = RealmAny.create(5) - val realmAny = nestedList[0] - assertEquals(7, realmAny!!.asInt()) + // Getting updated reference to embedded list + nestedList = instance.value!!.asDictionary()["key"]!!.asList() + assertEquals(7, nestedList[0]!!.asInt()) + + // Overwriting root entry + instance.value = null + assertFailsWithMessage("List is no longer valid") { + nestedList[0]!!.asInt() + } } } diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/dynamic/DynamicMutableRealmObjectTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/dynamic/DynamicMutableRealmObjectTests.kt index 3c49186802..0d5d9d743d 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/dynamic/DynamicMutableRealmObjectTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/dynamic/DynamicMutableRealmObjectTests.kt @@ -1415,13 +1415,13 @@ class DynamicMutableRealmObjectTests { // Verify that we can add elements to the set set.add(RealmAny.Companion.create(dynamicSampleInner)) // Verify that we cannot add nested collections - assertFailsWithMessage("Cannot add collections to RealmSets") { + assertFailsWithMessage("Sets cannot contain other collections") { set.add(RealmAny.Companion.create(realmSetOf())) } - assertFailsWithMessage("Cannot add collections to RealmSets") { + assertFailsWithMessage("Sets cannot contain other collections") { set.add(RealmAny.Companion.create(realmListOf())) } - assertFailsWithMessage("Cannot add collections to RealmSets") { + assertFailsWithMessage("Sets cannot contain other collections") { set.add(RealmAny.Companion.create(realmDictionaryOf())) } } diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedDictionaryNotificationTest.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedDictionaryNotificationTest.kt index 3675c6b18f..0c53dd9150 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedDictionaryNotificationTest.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedDictionaryNotificationTest.kt @@ -153,10 +153,10 @@ class RealmAnyNestedDictionaryNotificationTest : RealmEntityNotificationTests { // Trigger an update realm.write { val queriedContainer = findLatest(container) - queriedContainer!!.value!!.asList()[0]!!.asDictionary().put("key1", RealmAny.create(1)) + queriedContainer!!.value!!.asDictionary()["root"]!!.asDictionary().put("key1", RealmAny.create(1)) } - assertEquals(2, channel1.receiveOrFail().map.size) - assertEquals(2, channel2.receiveOrFail().map.size) + assertEquals(1, channel1.receiveOrFail().map.size) + assertEquals(1, channel2.receiveOrFail().map.size) // Cancel observer 1 observer1.cancel() @@ -170,7 +170,7 @@ class RealmAnyNestedDictionaryNotificationTest : RealmEntityNotificationTests { // Check channel 1 didn't receive the update assertTrue(channel1.isEmpty) // But channel 2 did - assertEquals(3, channel2.receiveOrFail().map.size) + assertEquals(2, channel2.receiveOrFail().map.size) observer2.cancel() channel1.close() From 01ee687d4d0c623540024af0cd04d057108c56cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Sun, 27 Aug 2023 20:04:15 +0200 Subject: [PATCH 15/41] Fix caching of imported object in RealmAny --- .../io/realm/kotlin/internal/Converters.kt | 52 ++++--------------- .../kotlin/internal/RealmObjectHelper.kt | 24 ++++++--- 2 files changed, 27 insertions(+), 49 deletions(-) diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/Converters.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/Converters.kt index e59fda625d..3f48264b41 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/Converters.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/Converters.kt @@ -168,18 +168,18 @@ internal inline fun realmValueToRealmAny( } } - -// FIXME Do we really need to propagate dynamic and dynamic mutable info here -internal fun MemTrackingAllocator.realmAnyHandler(value: RealmAny?, - primitiveValues: (RealmValue) -> T = { throw IllegalArgumentException("Operation not support for primitive values")}, - reference: (RealmAny) -> T = { throw IllegalArgumentException("Operation not support for objects")}, - set: (RealmAny) -> T = { throw IllegalArgumentException("Operation not support for sets")}, - list: (RealmAny) -> T = { throw IllegalArgumentException("Operation not support for lists")}, - dictionary: (RealmAny) -> T = { throw IllegalArgumentException("Operation not support for dictionaries")}, +internal fun MemTrackingAllocator.realmAnyHandler( + value: RealmAny?, + primitiveValues: (RealmValue) -> T = { throw IllegalArgumentException("Operation not support for primitive values") }, + reference: (RealmAny) -> T = { throw IllegalArgumentException("Operation not support for objects") }, + set: (RealmAny) -> T = { throw IllegalArgumentException("Operation not support for sets") }, + list: (RealmAny) -> T = { throw IllegalArgumentException("Operation not support for lists") }, + dictionary: (RealmAny) -> T = { throw IllegalArgumentException("Operation not support for dictionaries") }, ): T { return when (value?.type) { null -> primitiveValues(nullTransport()) + io.realm.kotlin.types.RealmAny.Type.INT, io.realm.kotlin.types.RealmAny.Type.BOOL, io.realm.kotlin.types.RealmAny.Type.STRING, @@ -191,55 +191,21 @@ internal fun MemTrackingAllocator.realmAnyHandler(value: RealmAny?, io.realm.kotlin.types.RealmAny.Type.OBJECT_ID, io.realm.kotlin.types.RealmAny.Type.UUID -> primitiveValues(realmAnyPrimitiveToRealmValue(value)) + io.realm.kotlin.types.RealmAny.Type.OBJECT -> { -// val converter = if (value.type == io.realm.kotlin.types.RealmAny.Type.OBJECT) { -// when ((value as RealmAnyImpl<*>).clazz) { -// DynamicRealmObject::class -> -// realmAnyConverter(mediator, realmReference, true) -// -// DynamicMutableRealmObject::class -> -// realmAnyConverter( -// mediator, -// realmReference, -// issueDynamicObject = true, -// issueDynamicMutableObject = true -// ) -// -// else -> -// realmAnyConverter(obj.mediator, obj.owner) -// } -// } else { -// realmAnyConverter(obj.mediator, obj.owner) -// } -// with(converter) { -// setValueTransportByKey(obj, key, publicToRealmValue(value)) -// } - // UPDATE POLICY -// val transport = realmAnyToRealmValueWithImport(value, mediator, realmReference, false) -// primitiveValues() -// primitiveValues(transport) reference(value) } io.realm.kotlin.types.RealmAny.Type.SET -> { set(value) -// val nativePointer = set() -// val operator = realmAnySetOperator(mediator, realmReference, nativePointer, false, false) -// operator.addAll(value.asSet())//, updatePolicy, cache) } io.realm.kotlin.types.RealmAny.Type.LIST -> { list(value) -// val nativePointer = list() -// val operator = realmAnyList(mediator, realmReference, nativePointer, false, false) -// operator.insertAll(0, value.asList())//, updatePolicy, cache) } io.realm.kotlin.types.RealmAny.Type.DICTIONARY -> { dictionary(value) -// val nativePointer = dictionary() -// val operator = realmAnyMapOperator(mediator, realmReference, nativePointer, false, false) -// operator.putAll( value.asDictionary()) //, updatePolicy, cache ) } } } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt index 80f5c68f46..046d1e1bbd 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt @@ -190,7 +190,9 @@ internal object RealmObjectHelper { internal fun setValueByKey( obj: RealmObjectReference, key: PropertyKey, - value: Any? + value: Any?, + updatePolicy: UpdatePolicy = UpdatePolicy.ALL, + cache: UnmanagedToManagedObjectCache = mutableMapOf() ) { // TODO optimize: avoid this by creating the scope in the accessor via the compiler plugin // See comment in AccessorModifierIrGeneration.modifyAccessor about this. @@ -227,7 +229,7 @@ internal object RealmObjectHelper { value = value, primitiveValues = { realmValue -> setValueTransportByKey( obj, key, realmValue ) }, reference = { realmValue -> - setObjectByKey( obj, key, realmValue.asRealmObject() ) + setObjectByKey( obj, key, realmValue.asRealmObject(), updatePolicy, cache) }, set = { realmValue -> RealmInterop.realm_set_value(obj.objectPointer, key, nullTransport(), false) @@ -239,21 +241,21 @@ internal object RealmObjectHelper { false, false ) - operator.addAll(value.asSet())//, updatePolicy, cache) + operator.addAll(value.asSet(), updatePolicy, cache) }, list = { realmValue -> RealmInterop.realm_set_value(obj.objectPointer, key, nullTransport(), false) val nativePointer = RealmInterop.realm_set_list(obj.objectPointer, key) val operator = realmAnyListOperator(obj.mediator, obj.owner, nativePointer, false, false) - operator.insertAll(0, value.asList()) + operator.insertAll(0, value.asList(), updatePolicy, cache) }, dictionary = { realmValue -> RealmInterop.realm_set_value(obj.objectPointer, key, nullTransport(), false) val nativePointer = RealmInterop.realm_set_dictionary(obj.objectPointer, key) val operator = realmAnyMapOperator(obj.mediator, obj.owner, nativePointer, false, false) - operator.putAll(value.asDictionary()) + operator.putAll(value.asDictionary(), updatePolicy, cache) } ) } @@ -809,6 +811,16 @@ internal object RealmObjectHelper { ) } } + PropertyType.RLM_PROPERTY_TYPE_MIXED -> { + val value = accessor.get(source) + setValueByKey( + target.realmObjectReference!!, + property.key, + value, + updatePolicy, + cache + ) + } else -> { val getterValue = accessor.get(source) accessor.set(target, getterValue) @@ -1151,7 +1163,7 @@ internal object RealmObjectHelper { dictionary = { realmValue -> val nativePointer = RealmInterop.realm_set_dictionary(obj.objectPointer, key) val operator = - realmAnyMapOperator(obj.mediator, obj.owner, nativePointer) + realmAnyMapOperator(obj.mediator, obj.owner, nativePointer, true) operator.putAll(value.asDictionary(), updatePolicy, cache) } ) From bd29c947ea10ec3d05d4207fb03c7415a8ec5cb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Sun, 27 Aug 2023 20:18:04 +0200 Subject: [PATCH 16/41] Linting --- .../kotlin/internal/interop/RealmInterop.kt | 4 +- .../kotlin/internal/interop/RealmInterop.kt | 6 +- .../kotlin/io/realm/kotlin/ext/RealmAnyExt.kt | 8 +- .../io/realm/kotlin/internal/Converters.kt | 101 +----------------- .../kotlin/internal/RealmListInternal.kt | 29 +++-- .../realm/kotlin/internal/RealmMapInternal.kt | 31 +++--- .../kotlin/internal/RealmObjectHelper.kt | 14 ++- .../realm/kotlin/internal/RealmSetInternal.kt | 31 +++--- .../kotlin/internal/query/ScalarQuery.kt | 28 ++--- .../common/RealmAnyNestedCollectionTests.kt | 68 ++++++------ .../test/common/RealmDictionaryTests.kt | 1 - .../kotlin/test/common/RealmListTests.kt | 3 - ...ealmAnyNestedCollectionNotificationTest.kt | 25 ++--- ...ealmAnyNestedDictionaryNotificationTest.kt | 65 ++++++----- .../RealmAnyNestedListNotificationTest.kt | 42 ++++---- .../RealmAnyNestedSetNotificationTest.kt | 20 ++-- 16 files changed, 199 insertions(+), 277 deletions(-) diff --git a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt index 066471642f..78997578a0 100644 --- a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt +++ b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt @@ -293,8 +293,8 @@ expect object RealmInterop { isDefault: Boolean ) fun realm_set_embedded(obj: RealmObjectPointer, key: PropertyKey): RealmObjectPointer - fun realm_set_set(obj: RealmObjectPointer, key: PropertyKey) : RealmSetPointer - fun realm_set_list(obj: RealmObjectPointer, key: PropertyKey) : RealmListPointer + fun realm_set_set(obj: RealmObjectPointer, key: PropertyKey): RealmSetPointer + fun realm_set_list(obj: RealmObjectPointer, key: PropertyKey): RealmListPointer fun realm_set_dictionary(obj: RealmObjectPointer, key: PropertyKey): RealmMapPointer fun realm_object_add_int(obj: RealmObjectPointer, key: PropertyKey, value: Long) fun realm_object_get_parent( diff --git a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt index 1b6cdef65e..0173a2a040 100644 --- a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt +++ b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt @@ -463,15 +463,15 @@ actual object RealmInterop { return LongPointerWrapper(realmc.realm_set_embedded(obj.cptr(), key.key)) } - actual fun realm_set_set(obj: RealmObjectPointer, key: PropertyKey) : RealmSetPointer { + actual fun realm_set_set(obj: RealmObjectPointer, key: PropertyKey): RealmSetPointer { realmc.realm_set_set(obj.cptr(), key.key) return realm_get_set(obj, key) } - actual fun realm_set_list(obj: RealmObjectPointer, key: PropertyKey) : RealmListPointer { + actual fun realm_set_list(obj: RealmObjectPointer, key: PropertyKey): RealmListPointer { realmc.realm_set_list(obj.cptr(), key.key) return realm_get_list(obj, key) } - actual fun realm_set_dictionary(obj: RealmObjectPointer, key: PropertyKey) : RealmMapPointer { + actual fun realm_set_dictionary(obj: RealmObjectPointer, key: PropertyKey): RealmMapPointer { realmc.realm_set_dictionary(obj.cptr(), key.key) return realm_get_dictionary(obj, key) } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmAnyExt.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmAnyExt.kt index ba204ef0e0..d42d91d14c 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmAnyExt.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmAnyExt.kt @@ -18,8 +18,9 @@ public inline fun RealmAny.asRealmObject(): T = asRealmObject(T::class) // FIXME Doc if this is public +@Suppress("ComplexMethod") public fun realmAnyOf(value: Any?): RealmAny? { - return when(value) { + return when (value) { (value == null) -> null is Boolean -> RealmAny.create(value) is Byte -> RealmAny.create(value) @@ -54,6 +55,5 @@ public fun realmAnyListOf(vararg values: Any?): RealmAny = RealmAny.create(values.map { realmAnyOf(it) }.toRealmList()) // FIXME Doc -public fun realmAnyDictionaryOf(vararg values: Pair): RealmAny = - RealmAny.create(values.map { (key, value) -> key to realmAnyOf(value) } - .toRealmDictionary()) +public fun realmAnyDictionaryOf(vararg values: Pair): RealmAny = + RealmAny.create(values.map { (key, value) -> key to realmAnyOf(value) }.toRealmDictionary()) diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/Converters.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/Converters.kt index 3f48264b41..b42b5eb392 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/Converters.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/Converters.kt @@ -32,7 +32,6 @@ import io.realm.kotlin.internal.interop.RealmSetPointer import io.realm.kotlin.internal.interop.RealmValue import io.realm.kotlin.internal.interop.Timestamp import io.realm.kotlin.internal.interop.ValueType -import io.realm.kotlin.internal.platform.realmObjectCompanionOrNull import io.realm.kotlin.types.BaseRealmObject import io.realm.kotlin.types.ObjectId import io.realm.kotlin.types.RealmAny @@ -104,13 +103,13 @@ public inline fun realmValueToRealmUUID(transport: RealmValue): RealmUUID = Real public inline fun realmValueToDecimal128(transport: RealmValue): Decimal128 = transport.getDecimal128Array().let { Decimal128.fromIEEE754BIDEncoding(it[1], it[0]) } -@Suppress("ComplexMethod", "NestedBlockDepth") +@Suppress("ComplexMethod", "NestedBlockDepth", "LongParameterList") internal inline fun realmValueToRealmAny( realmValue: RealmValue, parent: RealmObjectReference<*>?, mediator: Mediator, owner: RealmReference, - issueDynamicObject: Boolean , + issueDynamicObject: Boolean, issueDynamicMutableObject: Boolean, set: () -> RealmSetPointer = { error("Cannot handled embedded sets") }, list: () -> RealmListPointer = { error("Cannot handled embedded lists") }, @@ -168,6 +167,7 @@ internal inline fun realmValueToRealmAny( } } +@Suppress("LongParameterList") internal fun MemTrackingAllocator.realmAnyHandler( value: RealmAny?, primitiveValues: (RealmValue) -> T = { throw IllegalArgumentException("Operation not support for primitive values") }, @@ -487,101 +487,6 @@ internal inline fun RealmValue.asPrimitiveRealmAnyOrElse( else -> elseBlock() } -//@Suppress("OVERRIDE_BY_INLINE", "NestedBlockDepth", "LongParameterList") -//internal fun realmAnyConverter( -// mediator: Mediator, -// realmReference: RealmReference, -// issueDynamicObject: Boolean = false, -// issueDynamicMutableObject: Boolean = false, -// updatePolicy: UpdatePolicy = UpdatePolicy.ERROR, -// cache: UnmanagedToManagedObjectCache = mutableMapOf() -//): RealmValueConverter { -// return object : PassThroughPublicConverter() { -// override inline fun fromRealmValue(realmValue: RealmValue): RealmAny? = -// realmValue.asPrimitiveRealmAnyOrElse { -// when (val type = realmValue.getType()) { -// ValueType.RLM_TYPE_LINK -> { -// val link = realmValue.getLink() -// val clazz = if (issueDynamicObject) { -// if (issueDynamicMutableObject) { -// DynamicMutableRealmObject::class -// } else { -// DynamicRealmObject::class -// } -// } else { -// realmReference.schemaMetadata -// .get(link.classKey) -// ?.clazz -// ?: throw IllegalArgumentException("The object class is not present in the current schema - are you using an outdated schema version?") -// } -// val internalObject = mediator.createInstanceOf(clazz) -// val obj = internalObject.link( -// realmReference, -// mediator, -// clazz, -// link -// ) -// when (issueDynamicObject) { -// true -> when (issueDynamicMutableObject) { -// true -> RealmAny.create(obj as DynamicMutableRealmObject) -// else -> RealmAny.create(obj as DynamicRealmObject) -// } -// -// false -> RealmAny.create( -// obj as RealmObject, -// clazz as KClass -// ) -// } -// } -// -// else -> throw IllegalArgumentException("Invalid type '$type' for RealmValue.") -// } -// } -// -// override inline fun MemTrackingAllocator.toRealmValue(value: RealmAny?): RealmValue { -// return realmAnyToRealmValueWithImport( -// value, -// mediator, -// realmReference, -// issueDynamicObject, -// updatePolicy, cache -// ) -// } -// } -//} - -///** -// * // FIXME -// * Used for converting values to query arguments. -// */ -//@Suppress("LongParameterList") -//internal inline fun MemTrackingAllocator.realmAnyToRealmValueWithImport( -// value: RealmAny?, -// mediator: Mediator, -// realmReference: RealmReference, -// issueDynamicObject: Boolean = false, -// updatePolicy: UpdatePolicy = UpdatePolicy.ERROR, -// cache: UnmanagedToManagedObjectCache = mutableMapOf() -//): RealmValue { -// return when (value) { -// null -> nullTransport() -// else -> when (value.type) { -// RealmAny.Type.OBJECT -> { -// val obj = when (issueDynamicObject) { -// true -> value.asRealmObject() -// false -> value.asRealmObject() -// } -// val objRef = realmObjectToRealmReferenceWithImport(obj, mediator, realmReference, updatePolicy, cache) -// realmObjectTransport(objRef as RealmObjectInterop) -// } -// RealmAny.Type.SET -> TODO() -// RealmAny.Type.LIST -> { TODO() } -// RealmAny.Type.DICTIONARY -> TODO() -// else -> realmAnyPrimitiveToRealmValue(value) -// } -// } -//} - /** * Used for converting RealmAny values to RealmValues suitable for query arguments and primary keys. * Importing objects isn't allowed here. diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmListInternal.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmListInternal.kt index f61f0fbcde..aac28e48fa 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmListInternal.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmListInternal.kt @@ -18,7 +18,6 @@ package io.realm.kotlin.internal import io.realm.kotlin.UpdatePolicy import io.realm.kotlin.Versioned -import io.realm.kotlin.dynamic.DynamicMutableRealmObject import io.realm.kotlin.dynamic.DynamicRealmObject import io.realm.kotlin.ext.asRealmObject import io.realm.kotlin.internal.RealmValueArgumentConverter.convertToQueryArgs @@ -305,18 +304,18 @@ internal class PrimitiveListOperator( } internal fun realmAnyListOperator( - mediator: Mediator, - realm: RealmReference, - nativePointer: RealmListPointer, - issueDynamicObject: Boolean = false, - issueDynamicMutableObject: Boolean = false, -) : RealmAnyListOperator = RealmAnyListOperator( - mediator, - realm, - nativePointer, - issueDynamicObject = issueDynamicObject, - issueDynamicMutableObject = issueDynamicMutableObject - ) + mediator: Mediator, + realm: RealmReference, + nativePointer: RealmListPointer, + issueDynamicObject: Boolean = false, + issueDynamicMutableObject: Boolean = false, +): RealmAnyListOperator = RealmAnyListOperator( + mediator, + realm, + nativePointer, + issueDynamicObject = issueDynamicObject, + issueDynamicMutableObject = issueDynamicMutableObject +) @Suppress("LongParameterList") internal class RealmAnyListOperator( @@ -455,7 +454,7 @@ internal class RealmAnyListOperator( RealmAnyListOperator(mediator, realmReference, nativePointer, issueDynamicObject = issueDynamicObject, issueDynamicMutableObject = issueDynamicMutableObject) } -internal abstract class BaseRealmObjectListOperator ( +internal abstract class BaseRealmObjectListOperator ( override val mediator: Mediator, override val realmReference: RealmReference, override val nativePointer: RealmListPointer, @@ -472,7 +471,7 @@ internal abstract class BaseRealmObjectListOperator ( } } -internal class RealmObjectListOperator( +internal class RealmObjectListOperator( mediator: Mediator, realmReference: RealmReference, nativePointer: RealmListPointer, diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt index ad70ff96ea..586e986b70 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt @@ -330,8 +330,7 @@ internal open class PrimitiveMapOperator constructor( } } - override fun getValue(resultsPointer: RealmResultsPointer, index: Int): V? - { + override fun getValue(resultsPointer: RealmResultsPointer, index: Int): V? { return getterScope { with(realmValueConverter) { val transport = realm_results_get(resultsPointer, index.toLong()) @@ -406,11 +405,11 @@ internal class RealmAnyMapOperator constructor( override fun eraseInternal(key: K): Pair { return inputScope { val keyTransport = with(keyConverter) { publicToRealmValue(key) } - realm_dictionary_erase(nativePointer, keyTransport).let { - Pair(realmAny(it.first, keyTransport), it.second) - } + realm_dictionary_erase(nativePointer, keyTransport).let { + Pair(realmAny(it.first, keyTransport), it.second) } } + } override fun containsValueInternal(value: RealmAny?): Boolean { // Unmanaged objects are never found in a managed dictionary @@ -442,10 +441,16 @@ internal class RealmAnyMapOperator constructor( override fun getValue(resultsPointer: RealmResultsPointer, index: Int): RealmAny? { return getterScope { val transport = realm_results_get(resultsPointer, index.toLong()) - realmValueToRealmAny(transport, null, mediator, realmReference, issueDynamicObject, issueDynamicMutableObject, - {TODO("Nested sets cannot be obtained from iterator")}, - {TODO("Nested lists cannot be obtained from iterator")}, - {TODO("Nested dictionaries cannot be obtained from iterator")}, + realmValueToRealmAny( + transport, + null, + mediator, + realmReference, + issueDynamicObject, + issueDynamicMutableObject, + { TODO("Nested sets cannot be obtained from iterator") }, + { TODO("Nested lists cannot be obtained from iterator") }, + { TODO("Nested dictionaries cannot be obtained from iterator") }, ) } } @@ -487,7 +492,8 @@ internal class RealmAnyMapOperator constructor( ): Pair { return inputScope { val keyTransport = with(keyConverter) { publicToRealmValue(key) } - return realmAnyHandler(value, + return realmAnyHandler( + value, primitiveValues = { realm_dictionary_insert(nativePointer, keyTransport, it).let { result -> realmAny(result.first, keyTransport) to result.second @@ -546,7 +552,7 @@ internal class RealmAnyMapOperator constructor( } @Suppress("LongParameterList") -internal abstract class BaseRealmObjectMapOperator constructor( +internal abstract class BaseRealmObjectMapOperator constructor( override val mediator: Mediator, override val realmReference: RealmReference, override val keyConverter: RealmValueConverter, @@ -652,7 +658,7 @@ internal abstract class BaseRealmObjectMapOperator const } @Suppress("LongParameterList") -internal class RealmObjectMapOperator constructor( +internal class RealmObjectMapOperator constructor( mediator: Mediator, realmReference: RealmReference, keyConverter: RealmValueConverter, @@ -700,7 +706,6 @@ internal class RealmObjectMapOperator constructor( } as Pair } } - } @Suppress("LongParameterList") diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt index 046d1e1bbd..ada014d7ea 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt @@ -227,9 +227,15 @@ internal object RealmObjectHelper { is RealmAny -> { realmAnyHandler( value = value, - primitiveValues = { realmValue -> setValueTransportByKey( obj, key, realmValue ) }, + primitiveValues = { realmValue -> + setValueTransportByKey( + obj, + key, + realmValue + ) + }, reference = { realmValue -> - setObjectByKey( obj, key, realmValue.asRealmObject(), updatePolicy, cache) + setObjectByKey(obj, key, realmValue.asRealmObject(), updatePolicy, cache) }, set = { realmValue -> RealmInterop.realm_set_value(obj.objectPointer, key, nullTransport(), false) @@ -1140,9 +1146,9 @@ internal object RealmObjectHelper { } else { realmAnyHandler( value = value, - primitiveValues = { realmValue -> setValueTransportByKey( obj, key, realmValue ) }, + primitiveValues = { realmValue -> setValueTransportByKey(obj, key, realmValue) }, reference = { realmValue -> - setObjectByKey( obj, key, realmValue.asRealmObject(), updatePolicy, cache ) + setObjectByKey(obj, key, realmValue.asRealmObject(), updatePolicy, cache) }, set = { realmValue -> val nativePointer = RealmInterop.realm_set_set(obj.objectPointer, key) diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmSetInternal.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmSetInternal.kt index a4420a6d34..5853d9c19d 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmSetInternal.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmSetInternal.kt @@ -283,7 +283,7 @@ internal interface SetOperator : CollectionOperator { modCount++ } - abstract fun removeInternal(element: E): Boolean + fun removeInternal(element: E): Boolean fun remove(element: E): Boolean { return removeInternal(element).also { // FIXME Should this only be updated if above value is true? @@ -309,12 +309,12 @@ internal fun realmAnySetOperator( issueDynamicObject: Boolean = false, issueDynamicMutableObject: Boolean = false, ): RealmAnySetOperator = RealmAnySetOperator( - mediator, - realm, - nativePointer, - issueDynamicObject, - issueDynamicMutableObject - ) + mediator, + realm, + nativePointer, + issueDynamicObject, + issueDynamicMutableObject +) internal class RealmAnySetOperator( override val mediator: Mediator, @@ -452,7 +452,7 @@ internal class PrimitiveSetOperator( PrimitiveSetOperator(mediator, realmReference, realmValueConverter, nativePointer) } -internal class RealmObjectSetOperator : SetOperator { +internal class RealmObjectSetOperator : SetOperator { override val mediator: Mediator override val realmReference: RealmReference @@ -497,13 +497,13 @@ internal class RealmObjectSetOperator : SetOperator { @Suppress("UNCHECKED_CAST") override fun get(index: Int): E { return getterScope { - realm_set_get(nativePointer, index.toLong()) - .let { transport -> - when (ValueType.RLM_TYPE_NULL) { - transport.getType() -> null - else -> realmValueToRealmObject(transport, clazz, mediator, realmReference) - } - } as E + realm_set_get(nativePointer, index.toLong()) + .let { transport -> + when (ValueType.RLM_TYPE_NULL) { + transport.getType() -> null + else -> realmValueToRealmObject(transport, clazz, mediator, realmReference) + } + } as E } } @@ -518,7 +518,6 @@ internal class RealmObjectSetOperator : SetOperator { } } - override fun contains(element: E): Boolean { // Unmanaged objects are never found in a managed set element?.also { diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/ScalarQuery.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/ScalarQuery.kt index be2b3fe08f..3bc8bd498a 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/ScalarQuery.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/ScalarQuery.kt @@ -129,7 +129,8 @@ internal class MinMaxQuery constructor( private val queryType: AggregatorQueryType ) : BaseScalarQuery(realmReference, queryPointer, mediator, classKey, clazz), TypeBoundQuery, RealmScalarNullableQuery { - override val converter: (RealmValue) -> T? = when(propertyMetadata.type) { + @Suppress("ExplicitItLambdaParameter") + override val converter: (RealmValue) -> T? = when (propertyMetadata.type) { PropertyType.RLM_PROPERTY_TYPE_INT -> { it -> IntConverter.fromRealmValue(it)?.let { coerceLong(propertyMetadata.name, it, type) } as T? } PropertyType.RLM_PROPERTY_TYPE_FLOAT -> { it -> FloatConverter.fromRealmValue(it)?.let { coerceFloat(propertyMetadata.name, it, type) } as T? } PropertyType.RLM_PROPERTY_TYPE_DOUBLE -> { it -> DoubleConverter.fromRealmValue(it)?.let { coerceDouble(propertyMetadata.name, it, type) } as T? } @@ -200,7 +201,8 @@ internal class SumQuery constructor( private val type: KClass ) : BaseScalarQuery(realmReference, queryPointer, mediator, classKey, clazz), TypeBoundQuery, RealmScalarQuery { - override val converter: (RealmValue) -> T? = when(propertyMetadata.type) { + @Suppress("ExplicitItLambdaParameter") + override val converter: (RealmValue) -> T? = when (propertyMetadata.type) { PropertyType.RLM_PROPERTY_TYPE_INT -> { it -> IntConverter.fromRealmValue(it)?.let { coerceLong(propertyMetadata.name, it, type) } as T? } PropertyType.RLM_PROPERTY_TYPE_FLOAT -> { it -> DoubleConverter.fromRealmValue(it)?.let { coerceDouble(propertyMetadata.name, it, type) } as T? } PropertyType.RLM_PROPERTY_TYPE_DOUBLE -> { it -> DoubleConverter.fromRealmValue(it)?.let { coerceDouble(propertyMetadata.name, it, type) } as T? } @@ -269,18 +271,19 @@ private fun queryTypeValidator( } } -internal fun coerceLong(propertyName: String, value: Long, coercedType: KClass<*>):Any { +internal fun coerceLong(propertyName: String, value: Long, coercedType: KClass<*>): Any { return when (coercedType) { - Short::class -> value.toShort() - Int::class -> value.toInt() - Byte::class -> value.toByte() - Char::class -> value.toInt().toChar() - Long::class -> value - Double::class -> value.toDouble() - Float::class -> value.toFloat() - else -> throw IllegalArgumentException("Cannot coerce type of property '$propertyName' to '${coercedType.simpleName}'.") - } + Short::class -> value.toShort() + Int::class -> value.toInt() + Byte::class -> value.toByte() + Char::class -> value.toInt().toChar() + Long::class -> value + Double::class -> value.toDouble() + Float::class -> value.toFloat() + else -> throw IllegalArgumentException("Cannot coerce type of property '$propertyName' to '${coercedType.simpleName}'.") + } } + internal fun coerceFloat(propertyName: String, value: Float, coercedType: KClass<*>): Any { return when (coercedType) { Short::class -> value.toInt().toShort() @@ -293,6 +296,7 @@ internal fun coerceFloat(propertyName: String, value: Float, coercedType: KClass else -> throw IllegalArgumentException("Cannot coerce type of property '$$propertyName' to '${coercedType.simpleName}'.") } } + internal fun coerceDouble(propertyName: String, value: Double, coercedType: KClass<*>): Any { return when (coercedType) { Short::class -> value.toInt().toShort() diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt index 8f7eadfebf..73cb5f1460 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt @@ -665,7 +665,6 @@ class RealmAnyNestedCollectionTests { assertFailsWithMessage("List is no longer valid") { nestedList[0]!!.asInt() } - } } @@ -685,35 +684,42 @@ class RealmAnyNestedCollectionTests { @Test fun query() = runBlocking { realm.write { - copyToRealm(JsonStyleRealmObject().apply { - _id = "SET" -// value = RealmAny.create(realmSetOf(RealmAny.create(1), RealmAny.create(2), RealmAny.create(3))) - value = realmAnySetOf(1, 2, 3) - }) - copyToRealm(JsonStyleRealmObject().apply { - _id = "LIST" - value = realmAnyListOf(4, 5, 6) - }) - copyToRealm(JsonStyleRealmObject().apply { - _id = "DICT" - value = realmAnyDictionaryOf( + copyToRealm( + JsonStyleRealmObject().apply { + id = "SET" + value = realmAnySetOf(1, 2, 3) + } + ) + copyToRealm( + JsonStyleRealmObject().apply { + id = "LIST" + value = realmAnyListOf(4, 5, 6) + } + ) + copyToRealm( + JsonStyleRealmObject().apply { + id = "DICT" + value = realmAnyDictionaryOf( "key1" to 7, "key2" to 8, "key3" to 9, ) - }) - copyToRealm(JsonStyleRealmObject().apply { - _id = "EMBEDDED" - value = realmAnyListOf( - setOf(1, 2, 3), - listOf(4, 5, 6), - mapOf( - "key1" to 7, - "key2" to 8, - "key3" to listOf(9), + } + ) + copyToRealm( + JsonStyleRealmObject().apply { + id = "EMBEDDED" + value = realmAnyListOf( + setOf(1, 2, 3), + listOf(4, 5, 6), + mapOf( + "key1" to 7, + "key2" to 8, + "key3" to listOf(9), + ) ) - ) - }) + } + ) } assertEquals(4, realm.query().find().size) @@ -731,10 +737,10 @@ class RealmAnyNestedCollectionTests { // Matching lists realm.query("value[0] == 4").find().single().run { - assertEquals("LIST", _id) + assertEquals("LIST", id) } realm.query("value[*] == 4").find().single().run { - assertEquals("LIST", _id) + assertEquals("LIST", id) } // Size // [RLM_ERR_INVALID_QUERY]: Operation '@size' is not supported on property of type 'mixed' @@ -755,16 +761,16 @@ class RealmAnyNestedCollectionTests { // assertEquals("EMBEDDED", id) // } realm.query("value[*][*] == 4").find().single().run { - assertEquals("EMBEDDED", _id) + assertEquals("EMBEDDED", id) } realm.query("value[*][*] == 7").find().single().run { - assertEquals("EMBEDDED", _id) + assertEquals("EMBEDDED", id) } realm.query("value[*].@keys == 'key1'").find().single().run { - assertEquals("EMBEDDED", _id) + assertEquals("EMBEDDED", id) } realm.query("value[*].key3[0] == 9").find().single().run { - assertEquals("EMBEDDED", _id) + assertEquals("EMBEDDED", id) } } } diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmDictionaryTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmDictionaryTests.kt index e89334f023..83d6a5faca 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmDictionaryTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmDictionaryTests.kt @@ -21,7 +21,6 @@ import io.realm.kotlin.Realm import io.realm.kotlin.RealmConfiguration import io.realm.kotlin.entities.dictionary.DictionaryEmbeddedLevel1 import io.realm.kotlin.entities.dictionary.RealmDictionaryContainer -import io.realm.kotlin.entities.list.RealmListContainer import io.realm.kotlin.ext.asRealmObject import io.realm.kotlin.ext.query import io.realm.kotlin.ext.realmDictionaryEntryOf diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmListTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmListTests.kt index dfd745a5ca..eeea0b30b4 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmListTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmListTests.kt @@ -27,7 +27,6 @@ import io.realm.kotlin.entities.list.Level2 import io.realm.kotlin.entities.list.Level3 import io.realm.kotlin.entities.list.RealmListContainer import io.realm.kotlin.entities.list.listTestSchema -import io.realm.kotlin.entities.set.RealmSetContainer import io.realm.kotlin.ext.asRealmObject import io.realm.kotlin.ext.query import io.realm.kotlin.ext.realmListOf @@ -662,8 +661,6 @@ class RealmListTests : EmbeddedObjectCollectionQueryTests { assertFalse(frozenObject.nullableRealmAnyListField.contains(RealmAny.create(RealmListContainer()))) } - - private fun getCloseableRealm(): Realm = RealmConfiguration.Builder(schema = listTestSchema) .directory(tmpDir) diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedCollectionNotificationTest.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedCollectionNotificationTest.kt index 90862094ac..30a41c34cd 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedCollectionNotificationTest.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedCollectionNotificationTest.kt @@ -21,37 +21,23 @@ import io.realm.kotlin.RealmConfiguration import io.realm.kotlin.entities.JsonStyleRealmObject import io.realm.kotlin.ext.asFlow import io.realm.kotlin.ext.realmAnyListOf -import io.realm.kotlin.ext.realmAnyOf -import io.realm.kotlin.ext.realmAnySetOf import io.realm.kotlin.internal.platform.runBlocking -import io.realm.kotlin.notifications.DeletedSet import io.realm.kotlin.notifications.InitialObject -import io.realm.kotlin.notifications.InitialSet import io.realm.kotlin.notifications.ObjectChange -import io.realm.kotlin.notifications.SetChange import io.realm.kotlin.notifications.UpdatedObject -import io.realm.kotlin.notifications.UpdatedSet -import io.realm.kotlin.test.common.utils.RealmEntityNotificationTests import io.realm.kotlin.test.platform.PlatformUtils -import io.realm.kotlin.test.util.receiveOrFail import io.realm.kotlin.types.RealmAny import kotlinx.coroutines.async import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.withTimeout import kotlin.test.AfterTest import kotlin.test.BeforeTest -import kotlin.test.Ignore import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertIs import kotlin.test.assertTrue -import kotlin.time.Duration.Companion.seconds class RealmAnyNestedCollectionNotificationTest { - lateinit var tmpDir: String lateinit var configuration: RealmConfiguration lateinit var realm: Realm @@ -79,10 +65,12 @@ class RealmAnyNestedCollectionNotificationTest { val channel = Channel>() val o: JsonStyleRealmObject = realm.write { - copyToRealm(JsonStyleRealmObject().apply { - _id = "SET" - value = realmAnyListOf(realmAnyListOf(1, 2, 3)) - }) + copyToRealm( + JsonStyleRealmObject().apply { + id = "SET" + value = realmAnyListOf(realmAnyListOf(1, 2, 3)) + } + ) } val listener = async { @@ -109,4 +97,3 @@ class RealmAnyNestedCollectionNotificationTest { channel.close() } } - diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedDictionaryNotificationTest.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedDictionaryNotificationTest.kt index 0c53dd9150..5112d6d730 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedDictionaryNotificationTest.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedDictionaryNotificationTest.kt @@ -22,9 +22,7 @@ import io.realm.kotlin.entities.JsonStyleRealmObject import io.realm.kotlin.ext.realmAnyDictionaryOf import io.realm.kotlin.ext.realmAnyListOf import io.realm.kotlin.ext.realmAnyOf -import io.realm.kotlin.ext.realmAnySetOf import io.realm.kotlin.internal.platform.runBlocking -import io.realm.kotlin.notifications.DeletedList import io.realm.kotlin.notifications.DeletedMap import io.realm.kotlin.notifications.InitialMap import io.realm.kotlin.notifications.MapChange @@ -80,10 +78,18 @@ class RealmAnyNestedDictionaryNotificationTest : RealmEntityNotificationTests { val channel = Channel>() val o: JsonStyleRealmObject = realm.write { - copyToRealm(JsonStyleRealmObject().apply { - _id = "DICTIONARY" - value = realmAnyDictionaryOf("root" to realmAnyDictionaryOf("key1" to 1, "key2" to 2, "key3" to 3)) - }) + copyToRealm( + JsonStyleRealmObject().apply { + id = "DICTIONARY" + value = realmAnyDictionaryOf( + "root" to realmAnyDictionaryOf( + "key1" to 1, + "key2" to 2, + "key3" to 3 + ) + ) + } + ) } val dict = o.value!!.asDictionary()["root"]!!.asDictionary() @@ -96,7 +102,10 @@ class RealmAnyNestedDictionaryNotificationTest : RealmEntityNotificationTests { channel.receiveOrFail(1.seconds).run { assertIs>(this) - assertEquals(mapOf("key1" to 1,"key2" to 2,"key3" to 3), this.map.mapValues{ it.value!!.asInt()}) + assertEquals( + mapOf("key1" to 1, "key2" to 2, "key3" to 3), + this.map.mapValues { it.value!!.asInt() } + ) } realm.write { @@ -105,8 +114,8 @@ class RealmAnyNestedDictionaryNotificationTest : RealmEntityNotificationTests { } channel.receiveOrFail(1.seconds).run { - assertIs>(this) - assertEquals(mapOf("key1" to 1,"key2" to 2,"key3" to 3, "key4" to 4), this.map.mapValues{ it.value!!.asInt()}) + assertIs>(this) + assertEquals(mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 4), this.map.mapValues { it.value!!.asInt() }) } realm.write { @@ -125,13 +134,14 @@ class RealmAnyNestedDictionaryNotificationTest : RealmEntityNotificationTests { override fun cancelAsFlow() { kotlinx.coroutines.runBlocking { val container = realm.write { - copyToRealm(JsonStyleRealmObject().apply { value = realmAnyDictionaryOf("root" to - realmAnyDictionaryOf() + copyToRealm( + JsonStyleRealmObject().apply { + value = realmAnyDictionaryOf("root" to realmAnyDictionaryOf()) + } ) - }) } - val channel1 = Channel>(1) - val channel2 = Channel>(1) + val channel1 = Channel>(1) + val channel2 = Channel>(1) val observedDict = container.value!!.asDictionary()["root"]!!.asDictionary() val observer1 = async { observedDict.asFlow() @@ -180,10 +190,13 @@ class RealmAnyNestedDictionaryNotificationTest : RealmEntityNotificationTests { @Test override fun deleteEntity() = runBlocking { - val container = - realm.write { copyToRealm(JsonStyleRealmObject().apply { value = realmAnyDictionaryOf("root" to - realmAnyDictionaryOf() - ) }) } + val container = realm.write { + copyToRealm( + JsonStyleRealmObject().apply { + value = realmAnyDictionaryOf("root" to realmAnyDictionaryOf()) + } + ) + } val mutex = Mutex(true) val flow = async { container.value!!.asDictionary()["root"]!!.asDictionary().asFlow().first { @@ -196,10 +209,7 @@ class RealmAnyNestedDictionaryNotificationTest : RealmEntityNotificationTests { mutex.lock() // Update mixed value to overwrite and delete set realm.write { - // FIMXE Overwriting with similar container type doesn't emit a deletion event - // https://github.com/realm/realm-core/issues/6895 -// findLatest(container)!!.value = realmAnyListOf() - findLatest(container)!!.value = realmAnySetOf() + findLatest(container)!!.value = realmAnyListOf() } // Await that notifier has signalled the deletion so we are certain that the entity @@ -211,10 +221,13 @@ class RealmAnyNestedDictionaryNotificationTest : RealmEntityNotificationTests { @Test override fun asFlowOnDeletedEntity() = runBlocking { - val container = - realm.write { copyToRealm(JsonStyleRealmObject().apply { value = realmAnyDictionaryOf("root" to - realmAnyDictionaryOf() - ) }) } + val container = realm.write { + copyToRealm( + JsonStyleRealmObject().apply { + value = realmAnyDictionaryOf("root" to realmAnyDictionaryOf()) + } + ) + } val mutex = Mutex(true) val flow = async { container.value!!.asDictionary()["root"]!!.asDictionary().asFlow().first { diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedListNotificationTest.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedListNotificationTest.kt index db7c2baa82..6fd2814cfa 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedListNotificationTest.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedListNotificationTest.kt @@ -21,7 +21,6 @@ import io.realm.kotlin.RealmConfiguration import io.realm.kotlin.entities.JsonStyleRealmObject import io.realm.kotlin.ext.realmAnyListOf import io.realm.kotlin.ext.realmAnyOf -import io.realm.kotlin.ext.realmAnySetOf import io.realm.kotlin.internal.platform.runBlocking import io.realm.kotlin.notifications.DeletedList import io.realm.kotlin.notifications.InitialList @@ -78,10 +77,12 @@ class RealmAnyNestedListNotificationTest : RealmEntityNotificationTests { val channel = Channel>() val o: JsonStyleRealmObject = realm.write { - copyToRealm(JsonStyleRealmObject().apply { - _id = "LIST" - value = realmAnyListOf(realmAnyListOf(1, 2, 3)) - }) + copyToRealm( + JsonStyleRealmObject().apply { + id = "LIST" + value = realmAnyListOf(realmAnyListOf(1, 2, 3)) + } + ) } val list = o.value!!.asList()[0]!!.asList() @@ -94,7 +95,7 @@ class RealmAnyNestedListNotificationTest : RealmEntityNotificationTests { channel.receiveOrFail(1.seconds).run { assertIs>(this) - assertEquals(listOf(1,2,3), this.list.map{ it!!.asInt()}) + assertEquals(listOf(1, 2, 3), this.list.map { it!!.asInt() }) } realm.write { @@ -104,7 +105,7 @@ class RealmAnyNestedListNotificationTest : RealmEntityNotificationTests { channel.receiveOrFail(1.seconds).run { assertIs>(this) - assertEquals(listOf(1,2,3, 4), this.list.map{ it!!.asInt()}) + assertEquals(listOf(1, 2, 3, 4), this.list.map { it!!.asInt() }) } realm.write { @@ -123,7 +124,7 @@ class RealmAnyNestedListNotificationTest : RealmEntityNotificationTests { override fun cancelAsFlow() { kotlinx.coroutines.runBlocking { val container = realm.write { - copyToRealm(JsonStyleRealmObject().apply { value = realmAnyListOf(realmAnyListOf())}) + copyToRealm(JsonStyleRealmObject().apply { value = realmAnyListOf(realmAnyListOf()) }) } val channel1 = Channel>(1) val channel2 = Channel>(1) @@ -135,7 +136,7 @@ class RealmAnyNestedListNotificationTest : RealmEntityNotificationTests { } } val observer2 = async { - observedSet.asFlow() + observedSet.asFlow() .collect { change -> channel2.trySend(change) } @@ -175,10 +176,15 @@ class RealmAnyNestedListNotificationTest : RealmEntityNotificationTests { @Test override fun deleteEntity() = runBlocking { - val container = - realm.write { copyToRealm(JsonStyleRealmObject().apply { value = realmAnyListOf( - realmAnyListOf() - ) }) } + val container = realm.write { + copyToRealm( + JsonStyleRealmObject().apply { + value = realmAnyListOf( + realmAnyListOf() + ) + } + ) + } val mutex = Mutex(true) val flow = async { container.value!!.asList()[0]!!.asList().asFlow().first { @@ -203,10 +209,11 @@ class RealmAnyNestedListNotificationTest : RealmEntityNotificationTests { @Test override fun asFlowOnDeletedEntity() = runBlocking { - val container = - realm.write { copyToRealm(JsonStyleRealmObject().apply { value = realmAnyListOf( - realmAnyListOf() - ) }) } + val container = realm.write { + copyToRealm( + JsonStyleRealmObject().apply { value = realmAnyListOf(realmAnyListOf()) } + ) + } val mutex = Mutex(true) val flow = async { container.value!!.asList()[0]!!.asList().asFlow().first { @@ -245,4 +252,3 @@ class RealmAnyNestedListNotificationTest : RealmEntityNotificationTests { TODO("Not yet implemented") } } - diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedSetNotificationTest.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedSetNotificationTest.kt index d71b677908..debe60fb23 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedSetNotificationTest.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedSetNotificationTest.kt @@ -19,17 +19,13 @@ package io.realm.kotlin.test.common.notifications import io.realm.kotlin.Realm import io.realm.kotlin.RealmConfiguration import io.realm.kotlin.entities.JsonStyleRealmObject -import io.realm.kotlin.ext.asFlow import io.realm.kotlin.ext.realmAnyListOf import io.realm.kotlin.ext.realmAnyOf import io.realm.kotlin.ext.realmAnySetOf import io.realm.kotlin.internal.platform.runBlocking import io.realm.kotlin.notifications.DeletedSet -import io.realm.kotlin.notifications.InitialObject import io.realm.kotlin.notifications.InitialSet -import io.realm.kotlin.notifications.ObjectChange import io.realm.kotlin.notifications.SetChange -import io.realm.kotlin.notifications.UpdatedObject import io.realm.kotlin.notifications.UpdatedSet import io.realm.kotlin.test.common.utils.RealmEntityNotificationTests import io.realm.kotlin.test.platform.PlatformUtils @@ -51,7 +47,6 @@ import kotlin.time.Duration.Companion.seconds class RealmAnyNestedSetNotificationTest : RealmEntityNotificationTests { - lateinit var tmpDir: String lateinit var configuration: RealmConfiguration lateinit var realm: Realm @@ -83,10 +78,12 @@ class RealmAnyNestedSetNotificationTest : RealmEntityNotificationTests { val channel = Channel>() val o: JsonStyleRealmObject = realm.write { - copyToRealm(JsonStyleRealmObject().apply { - _id = "SET" - value = realmAnySetOf(1, 2, 3) - }) + copyToRealm( + JsonStyleRealmObject().apply { + id = "SET" + value = realmAnySetOf(1, 2, 3) + } + ) } val set = o.value!!.asSet() @@ -132,7 +129,7 @@ class RealmAnyNestedSetNotificationTest : RealmEntityNotificationTests { override fun cancelAsFlow() { kotlinx.coroutines.runBlocking { val container = realm.write { - copyToRealm(JsonStyleRealmObject().apply { value = realmAnySetOf()}) + copyToRealm(JsonStyleRealmObject().apply { value = realmAnySetOf() }) } val channel1 = Channel>(1) val channel2 = Channel>(1) @@ -144,7 +141,7 @@ class RealmAnyNestedSetNotificationTest : RealmEntityNotificationTests { } } val observer2 = async { - observedSet.asFlow() + observedSet.asFlow() .collect { change -> channel2.trySend(change) } @@ -250,4 +247,3 @@ class RealmAnyNestedSetNotificationTest : RealmEntityNotificationTests { TODO("Not yet implemented") } } - From 4894d4a9f4ee2ab99e7c3cbe0654b239c4d23341 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Mon, 28 Aug 2023 09:00:07 +0200 Subject: [PATCH 17/41] Added Kotlin Native implementation --- .../kotlin/internal/interop/RealmEnums.kt | 6 +- .../kotlin/internal/interop/RealmInterop.kt | 79 ++++++++++++++++++- .../common/RealmAnyNestedCollectionTests.kt | 8 +- 3 files changed, 83 insertions(+), 10 deletions(-) diff --git a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmEnums.kt b/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmEnums.kt index 9f5849c1f8..b992b9816c 100644 --- a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmEnums.kt +++ b/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmEnums.kt @@ -115,7 +115,11 @@ actual enum class ValueType( RLM_TYPE_DECIMAL128(realm_value_type_e.RLM_TYPE_DECIMAL128), RLM_TYPE_OBJECT_ID(realm_value_type_e.RLM_TYPE_OBJECT_ID), RLM_TYPE_LINK(realm_value_type_e.RLM_TYPE_LINK), - RLM_TYPE_UUID(realm_value_type_e.RLM_TYPE_UUID); + RLM_TYPE_UUID(realm_value_type_e.RLM_TYPE_UUID), + RLM_TYPE_SET(realm_value_type_e.RLM_TYPE_SET), + RLM_TYPE_LIST(realm_value_type_e.RLM_TYPE_LIST), + RLM_TYPE_DICTIONARY(realm_value_type_e.RLM_TYPE_DICTIONARY), + ; companion object { fun from(nativeValue: realm_value_type): ValueType = values().find { diff --git a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt b/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt index dd4b2a469b..0d78588c02 100644 --- a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt +++ b/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt @@ -899,6 +899,19 @@ actual object RealmInterop { return CPointerWrapper(realm_wrapper.realm_set_embedded(obj.cptr(), key.key)) } + actual fun realm_set_set(obj: RealmObjectPointer, key: PropertyKey): RealmSetPointer { + realm_wrapper.realm_set_set(obj.cptr(), key.key) + return realm_get_set(obj, key) + } + actual fun realm_set_list(obj: RealmObjectPointer, key: PropertyKey): RealmListPointer { + realm_wrapper.realm_set_list(obj.cptr(), key.key) + return realm_get_list(obj, key) + } + actual fun realm_set_dictionary(obj: RealmObjectPointer, key: PropertyKey): RealmMapPointer { + realm_wrapper.realm_set_dictionary(obj.cptr(), key.key) + return realm_get_dictionary(obj, key) + } + actual fun realm_object_add_int(obj: RealmObjectPointer, key: PropertyKey, value: Long) { checkedBooleanResult(realm_wrapper.realm_object_add_int(obj.cptr(), key.key, value)) } @@ -951,6 +964,14 @@ actual object RealmInterop { return RealmValue(struct) } + actual fun realm_list_get_set(list: RealmListPointer, index: Long): RealmSetPointer = + CPointerWrapper(realm_wrapper.realm_list_get_set(list.cptr(), index.toULong())) + actual fun realm_list_get_list(list: RealmListPointer, index: Long): RealmListPointer = + CPointerWrapper(realm_wrapper.realm_list_get_list(list.cptr(), index.toULong())) + + actual fun realm_list_get_dictionary(list: RealmListPointer, index: Long): RealmMapPointer = + CPointerWrapper(realm_wrapper.realm_list_get_dictionary(list.cptr(), index.toULong())) + actual fun realm_list_add(list: RealmListPointer, index: Long, transport: RealmValue) { checkedBooleanResult( realm_wrapper.realm_list_insert( @@ -960,6 +981,24 @@ actual object RealmInterop { ) ) } + actual fun realm_list_insert_set(list: RealmListPointer, index: Long): RealmSetPointer { + return CPointerWrapper(realm_wrapper.realm_list_insert_set(list.cptr(), index.toULong())) + } + actual fun realm_list_insert_list(list: RealmListPointer, index: Long): RealmListPointer { + return CPointerWrapper(realm_wrapper.realm_list_insert_list(list.cptr(), index.toULong())) + } + actual fun realm_list_insert_dictionary(list: RealmListPointer, index: Long): RealmMapPointer { + return CPointerWrapper(realm_wrapper.realm_list_insert_dictionary(list.cptr(), index.toULong())) + } + actual fun realm_list_set_set(list: RealmListPointer, index: Long): RealmSetPointer { + return CPointerWrapper(realm_wrapper.realm_list_set_set(list.cptr(), index.toULong())) + } + actual fun realm_list_set_list(list: RealmListPointer, index: Long): RealmListPointer { + return CPointerWrapper(realm_wrapper.realm_list_set_list(list.cptr(), index.toULong())) + } + actual fun realm_list_set_dictionary(list: RealmListPointer, index: Long): RealmMapPointer { + return CPointerWrapper(realm_wrapper.realm_list_set_dictionary(list.cptr(), index.toULong())) + } actual fun realm_list_insert_embedded(list: RealmListPointer, index: Long): RealmObjectPointer { return CPointerWrapper(realm_wrapper.realm_list_insert_embedded(list.cptr(), index.toULong())) @@ -1175,6 +1214,26 @@ actual object RealmInterop { } } + actual fun realm_dictionary_find_set( + dictionary: RealmMapPointer, + mapKey: RealmValue + ): RealmSetPointer { + return CPointerWrapper(realm_wrapper.realm_dictionary_get_set(dictionary.cptr(), mapKey.value.readValue())) + } + + actual fun realm_dictionary_find_list( + dictionary: RealmMapPointer, + mapKey: RealmValue + ): RealmListPointer { + return CPointerWrapper(realm_wrapper.realm_dictionary_get_list(dictionary.cptr(), mapKey.value.readValue())) + } + actual fun realm_dictionary_find_dictionary( + dictionary: RealmMapPointer, + mapKey: RealmValue + ): RealmMapPointer { + return CPointerWrapper(realm_wrapper.realm_dictionary_get_dictionary(dictionary.cptr(), mapKey.value.readValue())) + } + actual fun MemAllocator.realm_dictionary_get( dictionary: RealmMapPointer, pos: Int @@ -1296,6 +1355,18 @@ actual object RealmInterop { return RealmValue(outputStruct) } + actual fun realm_dictionary_insert_set(dictionary: RealmMapPointer, mapKey: RealmValue): RealmSetPointer { + return CPointerWrapper(realm_wrapper.realm_dictionary_insert_set(dictionary.cptr(), mapKey.value.readValue())) + } + + actual fun realm_dictionary_insert_list(dictionary: RealmMapPointer, mapKey: RealmValue): RealmListPointer { + return CPointerWrapper(realm_wrapper.realm_dictionary_insert_list(dictionary.cptr(), mapKey.value.readValue())) + } + + actual fun realm_dictionary_insert_dictionary(dictionary: RealmMapPointer, mapKey: RealmValue): RealmMapPointer { + return CPointerWrapper(realm_wrapper.realm_dictionary_insert_dictionary(dictionary.cptr(), mapKey.value.readValue())) + } + actual fun realm_dictionary_get_keys(dictionary: RealmMapPointer): RealmResultsPointer { memScoped { val size = alloc() @@ -1862,12 +1933,15 @@ actual object RealmInterop { val deletions = allocArray(1) val insertions = allocArray(1) val modifications = allocArray(1) + val collectionWasCleared = alloc() + val collectionWasDeleted = alloc() realm_wrapper.realm_dictionary_get_changes( change.cptr(), deletions, insertions, - modifications + modifications, + collectionWasDeleted.ptr, ) val deletionStructs = allocArray(deletions[0].toInt()) val insertionStructs = allocArray(insertions[0].toInt()) @@ -1880,7 +1954,8 @@ actual object RealmInterop { insertionStructs, insertions, modificationStructs, - modifications + modifications, + collectionWasCleared.ptr, ) val deletedKeys = (0 until deletions[0].toInt()).map { diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt index 73cb5f1460..7d9831d999 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt @@ -33,7 +33,6 @@ import io.realm.kotlin.test.common.utils.assertFailsWithMessage import io.realm.kotlin.test.platform.PlatformUtils import io.realm.kotlin.types.RealmAny import org.mongodb.kbson.ObjectId -import java.lang.IllegalStateException import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test @@ -238,8 +237,6 @@ class RealmAnyNestedCollectionTests { val instance = realm.query().find().single() val anyValue: RealmAny = instance.value!! assertEquals(RealmAny.Type.LIST, anyValue.type) - // FIXME Duplicate references not identified through RealmAny imports -// assertEquals(1, realm.query().find().size) // Assert structure anyValue.asList().let { @@ -536,8 +533,6 @@ class RealmAnyNestedCollectionTests { ) } } - // FIXME Duplicate references not identified through RealmAny imports -// assertEquals(1, realm.query().find().size) } @Test fun dictionaryInRealmAny_put() = runBlocking { @@ -596,8 +591,7 @@ class RealmAnyNestedCollectionTests { embeddedDict["keyObject"]!!.asRealmObject().stringField ) } - } // FIXME Duplicate references not identified through RealmAny imports -// assertEquals(1, realm.query().find().size) + } } @Test From 36b6705cb8a39dc1c7b210ea3862b6ae86f6e080 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Mon, 28 Aug 2023 10:36:20 +0200 Subject: [PATCH 18/41] Inline realm object helper methods --- .../kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt index ada014d7ea..e8a753f987 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt @@ -162,7 +162,7 @@ internal object RealmObjectHelper { // Primitives // --------------------------------------------------------------------- - internal fun setValue( + internal inline fun setValue( obj: RealmObjectReference, propertyName: String, value: Any? @@ -187,7 +187,7 @@ internal object RealmObjectHelper { } @Suppress("ComplexMethod", "LongMethod") - internal fun setValueByKey( + internal inline fun setValueByKey( obj: RealmObjectReference, key: PropertyKey, value: Any?, @@ -324,7 +324,7 @@ internal object RealmObjectHelper { propertyName: String ): ByteArray? = getterScope { getRealmValue(obj, propertyName)?.let { realmValueToByteArray(it) } } - internal fun getRealmAny( + internal inline fun getRealmAny( obj: RealmObjectReference, propertyName: String ): RealmAny? = getterScope { From b570421b869765701d77cc0156761975639a70db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Mon, 28 Aug 2023 11:49:31 +0200 Subject: [PATCH 19/41] Add mapping of new core error code --- .../kotlin/io/realm/kotlin/internal/interop/ErrorCode.kt | 1 + .../jvm/kotlin/io/realm/kotlin/internal/interop/ErrorCode.kt | 1 + .../kotlin/io/realm/kotlin/internal/interop/ErrorCode.kt | 1 + .../kotlin/io/realm/kotlin/test/CinteropTest.kt | 5 ++++- 4 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/ErrorCode.kt b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/ErrorCode.kt index 13138a5605..55b9dd1305 100644 --- a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/ErrorCode.kt +++ b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/ErrorCode.kt @@ -162,6 +162,7 @@ expect enum class ErrorCode : CodeDescription { RLM_ERR_MAINTENANCE_IN_PROGRESS, RLM_ERR_USERPASS_TOKEN_INVALID, RLM_ERR_INVALID_SERVER_RESPONSE, + RLM_ERR_APP_SERVER_ERROR, RLM_ERR_WEBSOCKET_RESOLVE_FAILED_ERROR, RLM_ERR_WEBSOCKET_CONNECTION_CLOSED_CLIENT_ERROR, RLM_ERR_WEBSOCKET_CONNECTION_CLOSED_SERVER_ERROR, diff --git a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/ErrorCode.kt b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/ErrorCode.kt index 935354e9cb..24d6049482 100644 --- a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/ErrorCode.kt +++ b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/ErrorCode.kt @@ -158,6 +158,7 @@ actual enum class ErrorCode(override val description: String, override val nativ RLM_ERR_APP_UNKNOWN("Unknown", realm_errno_e.RLM_ERR_APP_UNKNOWN), RLM_ERR_MAINTENANCE_IN_PROGRESS("MaintenanceInProgress", realm_errno_e.RLM_ERR_MAINTENANCE_IN_PROGRESS), RLM_ERR_USERPASS_TOKEN_INVALID("UserpassTokenInvalid", realm_errno_e.RLM_ERR_USERPASS_TOKEN_INVALID), + RLM_ERR_APP_SERVER_ERROR("AppServerError", realm_errno_e.RLM_ERR_APP_SERVER_ERROR), RLM_ERR_INVALID_SERVER_RESPONSE("InvalidServerResponse", realm_errno_e.RLM_ERR_INVALID_SERVER_RESPONSE), RLM_ERR_WEBSOCKET_RESOLVE_FAILED_ERROR("ResolveFailedError", realm_errno_e.RLM_ERR_WEBSOCKET_RESOLVE_FAILED_ERROR), RLM_ERR_WEBSOCKET_CONNECTION_CLOSED_CLIENT_ERROR("ConnectionClosedClientError", realm_errno_e.RLM_ERR_WEBSOCKET_CONNECTION_CLOSED_CLIENT_ERROR), diff --git a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/ErrorCode.kt b/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/ErrorCode.kt index 70987acd03..d41a3e1419 100644 --- a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/ErrorCode.kt +++ b/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/ErrorCode.kt @@ -163,6 +163,7 @@ actual enum class ErrorCode( RLM_ERR_MAINTENANCE_IN_PROGRESS("MaintenanceInProgress", realm_errno.RLM_ERR_MAINTENANCE_IN_PROGRESS), RLM_ERR_USERPASS_TOKEN_INVALID("UserpassTokenInvalid", realm_errno.RLM_ERR_USERPASS_TOKEN_INVALID), RLM_ERR_INVALID_SERVER_RESPONSE("InvalidServerResponse", realm_errno.RLM_ERR_INVALID_SERVER_RESPONSE), + RLM_ERR_APP_SERVER_ERROR("AppServerError", realm_errno.RLM_ERR_APP_SERVER_ERROR), RLM_ERR_WEBSOCKET_RESOLVE_FAILED_ERROR("ResolveFailedError", realm_errno.RLM_ERR_WEBSOCKET_RESOLVE_FAILED_ERROR), RLM_ERR_WEBSOCKET_CONNECTION_CLOSED_CLIENT_ERROR("ConnectionClosedClientError", realm_errno.RLM_ERR_WEBSOCKET_CONNECTION_CLOSED_CLIENT_ERROR), RLM_ERR_WEBSOCKET_CONNECTION_CLOSED_SERVER_ERROR("ConnectionClosedServerError", realm_errno.RLM_ERR_WEBSOCKET_CONNECTION_CLOSED_SERVER_ERROR), diff --git a/packages/cinterop/src/nativeDarwinTest/kotlin/io/realm/kotlin/test/CinteropTest.kt b/packages/cinterop/src/nativeDarwinTest/kotlin/io/realm/kotlin/test/CinteropTest.kt index 110d6c47c4..9b5cc98476 100644 --- a/packages/cinterop/src/nativeDarwinTest/kotlin/io/realm/kotlin/test/CinteropTest.kt +++ b/packages/cinterop/src/nativeDarwinTest/kotlin/io/realm/kotlin/test/CinteropTest.kt @@ -242,6 +242,9 @@ class CinteropTest { } .toIntArray() + val unmappedErrors = coreErrorNativeValues + .filter { ErrorCode.of(it) == null } + val errorCodeValues = coreErrorNativeValues .map { ErrorCode.of(it) @@ -250,7 +253,7 @@ class CinteropTest { .toSet() // validate that all error codes are mapped - assertEquals(coreErrorNativeValues.size, errorCodeValues.size) + assertEquals(coreErrorNativeValues.size, errorCodeValues.size, "Unmapped error codes: $unmappedErrors") } } From 871819b0e0e4fcc075846fe06c4b7e1fe20752ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 29 Aug 2023 11:54:53 +0200 Subject: [PATCH 20/41] Minor cleanup and docs --- .../kotlin/io/realm/kotlin/ext/RealmAnyExt.kt | 16 +++++++++-- .../kotlin/io/realm/kotlin/types/RealmAny.kt | 25 +++++++++++------ .../kotlin/entities/JsonStyleRealmObject.kt | 28 +++++++++++++++++++ 3 files changed, 58 insertions(+), 11 deletions(-) create mode 100644 packages/test-base/src/commonMain/kotlin/io/realm/kotlin/entities/JsonStyleRealmObject.kt diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmAnyExt.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmAnyExt.kt index d42d91d14c..3cca00571f 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmAnyExt.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmAnyExt.kt @@ -46,14 +46,24 @@ public fun realmAnyOf(value: Any?): RealmAny? { } } -// FIXME Doc +/** + * Create a [RealmAny] containing a [RealmSet] of all arguments wrapped as [RealmAny]s. + * @param values elements of the set. + */ public fun realmAnySetOf(vararg values: Any?): RealmAny = RealmAny.create(values.map { realmAnyOf(it) }.toRealmSet()) -// FIXME Doc +/** + * Create a [RealmAny] containing a [RealmList] of all arguments wrapped as [RealmAny]s. + * @param values elements of the set. + */ public fun realmAnyListOf(vararg values: Any?): RealmAny = RealmAny.create(values.map { realmAnyOf(it) }.toRealmList()) -// FIXME Doc +/** + * Create a [RealmAny] containing a [RealmDictionary] with all argument values wrapped as + * [RealmAnys]s. + * @param values entries of the dictionary. + */ public fun realmAnyDictionaryOf(vararg values: Pair): RealmAny = RealmAny.create(values.map { (key, value) -> key to realmAnyOf(value) }.toRealmDictionary()) diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmAny.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmAny.kt index 449989db16..6cc1951e58 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmAny.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmAny.kt @@ -60,6 +60,9 @@ import kotlin.reflect.KClass * OBJECT_ID -> doSomething(realmAny.asObjectId()) * REALM_UUID -> doSomething(realmAny.asRealmUUID()) * REALM_OBJECT -> doSomething(realmAny.asRealmObject()) + * SET -> doSomething(realmAny.asSet()) + * LIST -> doSomething(realmAny.asList()) + * DICTIONARY -> doSomething(realmAny.asDictionary()) * } * ``` * [Short], [Int], [Byte], [Char] and [Long] values are converted internally to `int64_t` values. @@ -113,11 +116,6 @@ public interface RealmAny { */ public enum class Type { INT, BOOL, STRING, BINARY, TIMESTAMP, FLOAT, DOUBLE, DECIMAL128, OBJECT_ID, UUID, OBJECT, SET, LIST, DICTIONARY; - - // FIXME Should this be public!? - public companion object { - public val COLLECTION_TYPES: Set = setOf(SET, LIST, DICTIONARY) - } } /** @@ -238,11 +236,22 @@ public interface RealmAny { */ public fun asRealmObject(clazz: KClass): T - // FIXME Docs + /** + * Returns the value from this `RealmAny` as a [RealmSet] containing new [RealmAny]s. + * @throws [IllegalStateException] if the stored value is not a set. + */ public fun asSet(): RealmSet - // FIXME Docs + + /** + * Returns the value from this `RealmAny` as a [RealmList] containing new [RealmAny]s. + * @throws [IllegalStateException] if the stored value is not a list. + */ public fun asList(): RealmList - // FIXME Docs + + /** + * Returns the value from this `RealmAny` as a [RealmDictionary] containing new [RealmAny]s. + * @throws [IllegalStateException] if the stored value is not a dictionary. + */ public fun asDictionary(): RealmDictionary /** diff --git a/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/entities/JsonStyleRealmObject.kt b/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/entities/JsonStyleRealmObject.kt new file mode 100644 index 0000000000..4ebf588c9a --- /dev/null +++ b/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/entities/JsonStyleRealmObject.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2023 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.realm.kotlin.entities + +import io.realm.kotlin.types.RealmAny +import io.realm.kotlin.types.RealmObject +import io.realm.kotlin.types.annotations.PrimaryKey + +class JsonStyleRealmObject(id: String) : RealmObject { + constructor() : this("JsonStyleRealmObject") + @PrimaryKey + var id: String = id + var value: RealmAny? = null +} From 7d28860e04206f0d7e43dad70ddcd280b70c8c19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 29 Aug 2023 13:43:24 +0200 Subject: [PATCH 21/41] Fix json style object schema for sync support --- .../kotlin/io/realm/kotlin/entities/JsonStyleRealmObject.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/entities/JsonStyleRealmObject.kt b/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/entities/JsonStyleRealmObject.kt index 4ebf588c9a..d54855248e 100644 --- a/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/entities/JsonStyleRealmObject.kt +++ b/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/entities/JsonStyleRealmObject.kt @@ -18,11 +18,13 @@ package io.realm.kotlin.entities import io.realm.kotlin.types.RealmAny import io.realm.kotlin.types.RealmObject +import io.realm.kotlin.types.annotations.PersistedName import io.realm.kotlin.types.annotations.PrimaryKey class JsonStyleRealmObject(id: String) : RealmObject { constructor() : this("JsonStyleRealmObject") @PrimaryKey + @PersistedName("_id") var id: String = id var value: RealmAny? = null } From 620e2a7ef6aeeac3d052594be5940521e2ae399e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Wed, 30 Aug 2023 08:45:23 +0200 Subject: [PATCH 22/41] Minor updates --- CHANGELOG.md | 4 +++- .../realm/kotlin/serializers/RealmKSerializers.kt | 14 ++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a46dfed9c5..a8f7cf8944 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## 1.11.0-SNAPSHOT (YYYY-MM-DD) +This release will bump the Realm file format from version 23 to 24. Opening a file with an older format will automatically upgrade it. Downgrading to a previous file format is not possible. + ### Breaking Changes * `BaseRealmObject.equals()` has changed from being identity-based only (===) to instead return `true` if two objects come from the same Realm version. This e.g means that reading the same object property twice will now be identical. Note, two Realm objects, even with identical values will not be considered equal if they belong to different versions. @@ -34,7 +36,7 @@ if the content is the same. Custom implementations of these methods will be resp * None. ### Compatibility -* File format: Generates Realms with file format v23. +* File format: Generates Realms with file format v24. * Realm Studio 13.0.0 or above is required to open Realms created by this version. * This release is compatible with the following Kotlin releases: * Kotlin 1.8.0 and above. The K2 compiler is not supported yet. diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/serializers/RealmKSerializers.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/serializers/RealmKSerializers.kt index eb45bd1be8..80bf901b2e 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/serializers/RealmKSerializers.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/serializers/RealmKSerializers.kt @@ -371,9 +371,10 @@ public object RealmAnyKSerializer : KSerializer { Type.OBJECT_ID -> RealmAny.create(it.objectId!!) Type.UUID -> RealmAny.create(it.uuid!!) Type.OBJECT -> RealmAny.create(it.realmObject!!) - Type.SET -> TODO() - Type.LIST -> TODO() - Type.DICTIONARY -> TODO() + Type.SET, + Type.LIST, + Type.DICTIONARY -> + throw UnsupportedOperationException("Serialization of nested collections is not yet supported") } } } @@ -398,9 +399,10 @@ public object RealmAnyKSerializer : KSerializer { ) Type.UUID -> uuid = value.asRealmUUID() Type.OBJECT -> realmObject = value.asRealmObject() - Type.SET -> TODO() - Type.LIST -> TODO() - Type.DICTIONARY -> TODO() + Type.SET, + Type.LIST, + Type.DICTIONARY -> + throw UnsupportedOperationException("Serialization of nested collections is not yet supported") } } ) From 5a690356291a9cf0c6b1228bbc566c39070d7cc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Wed, 30 Aug 2023 12:44:57 +0200 Subject: [PATCH 23/41] Update core reference with latest fixes --- packages/external/core | 2 +- .../kotlin/io/realm/kotlin/test/common/VersionTrackingTests.kt | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/external/core b/packages/external/core index 425d02fd7d..0dc3868f1a 160000 --- a/packages/external/core +++ b/packages/external/core @@ -1 +1 @@ -Subproject commit 425d02fd7d71dbc9e3ede74790e91a11190272fe +Subproject commit 0dc3868f1a0d742b6d2163422263d37c1e007ed8 diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/VersionTrackingTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/VersionTrackingTests.kt index 37d59c81fb..aa58c07c4f 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/VersionTrackingTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/VersionTrackingTests.kt @@ -81,7 +81,6 @@ class VersionTrackingTests { realm.activeVersions().run { assertEquals(1, all.size) assertEquals(1, allTracked.size) - assertNull(notifier) assertNull(writer) } } From 84e8c919016596b988a8a77a10fb79ba67559f6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Wed, 30 Aug 2023 13:32:55 +0200 Subject: [PATCH 24/41] Comments --- .../kotlin/io/realm/kotlin/internal/RealmListInternal.kt | 3 +++ .../kotlin/io/realm/kotlin/internal/RealmMapInternal.kt | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmListInternal.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmListInternal.kt index aac28e48fa..110f678476 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmListInternal.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmListInternal.kt @@ -414,6 +414,7 @@ internal class RealmAnyListOperator( RealmInterop.realm_list_set(nativePointer, index.toLong(), realmObjectTransport(objRef)) }, set = { realmValue -> + // Have to clear existing elements for core to know if we are updating with a new collection RealmInterop.realm_list_set(nativePointer, index.toLong(), nullTransport()) val nativePointer = RealmInterop.realm_list_set_set(nativePointer, index.toLong()) val operator = realmAnySetOperator( @@ -425,6 +426,7 @@ internal class RealmAnyListOperator( operator.addAll(realmValue.asSet(), updatePolicy, cache) }, list = { realmValue -> + // Have to clear existing elements for core to know if we are updating with a new collection RealmInterop.realm_list_set(nativePointer, index.toLong(), nullTransport()) val nativePointer = RealmInterop.realm_list_set_list(nativePointer, index.toLong()) val operator = realmAnyListOperator( @@ -436,6 +438,7 @@ internal class RealmAnyListOperator( operator.insertAll(0, realmValue.asList(), updatePolicy, cache) }, dictionary = { realmValue -> + // Have to clear existing elements for core to know if we are updating with a new collection RealmInterop.realm_list_set(nativePointer, index.toLong(), nullTransport()) val nativePointer = RealmInterop.realm_list_set_dictionary(nativePointer, index.toLong()) val operator = diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt index 586e986b70..4a6051698d 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt @@ -511,6 +511,7 @@ internal class RealmAnyMapOperator constructor( } }, set = { realmValue -> + // Have to clear existing elements for core to know if we are updating with a new collection realm_dictionary_insert(nativePointer, keyTransport, nullTransport()) val previous = getInternal(key) val nativePointer = RealmInterop.realm_dictionary_insert_set(nativePointer, keyTransport) @@ -524,6 +525,7 @@ internal class RealmAnyMapOperator constructor( previous to true }, list = { realmValue -> + // Have to clear existing elements for core to know if we are updating with a new collection realm_dictionary_insert(nativePointer, keyTransport, nullTransport()) val previous = getInternal(key) val nativePointer = RealmInterop.realm_dictionary_insert_list(nativePointer, keyTransport) @@ -537,7 +539,7 @@ internal class RealmAnyMapOperator constructor( previous to true }, dictionary = { realmValue -> - // Need to clear out + // Have to clear existing elements for core to know if we are updating with a new collection realm_dictionary_insert(nativePointer, keyTransport, nullTransport()) val previous = getInternal(key) val nativePointer = RealmInterop.realm_dictionary_insert_dictionary(nativePointer, keyTransport) From bbf93f438895698c99785fb8533a6f2c1922d1b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Wed, 30 Aug 2023 14:21:06 +0200 Subject: [PATCH 25/41] Core issue reference for access to collections through results --- .../kotlin/io/realm/kotlin/internal/RealmMapInternal.kt | 1 + .../kotlin/io/realm/kotlin/serializers/RealmKSerializers.kt | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt index 4a6051698d..5435778909 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt @@ -448,6 +448,7 @@ internal class RealmAnyMapOperator constructor( realmReference, issueDynamicObject, issueDynamicMutableObject, + // Not supported by core yet. Tracked by https://github.com/realm/realm-core/issues/6936 { TODO("Nested sets cannot be obtained from iterator") }, { TODO("Nested lists cannot be obtained from iterator") }, { TODO("Nested dictionaries cannot be obtained from iterator") }, diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/serializers/RealmKSerializers.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/serializers/RealmKSerializers.kt index 80bf901b2e..d126187d05 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/serializers/RealmKSerializers.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/serializers/RealmKSerializers.kt @@ -506,3 +506,4 @@ public object MutableRealmIntKSerializer : KSerializer { encoder.encodeLong(value.toLong()) } } + From 0bee4f19eeb2488c3d0a60de33b768e15b5c23d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Wed, 30 Aug 2023 14:22:56 +0200 Subject: [PATCH 26/41] Add suggested workaround for serialization of collections in RealmAny --- .../kotlin/serializers/RealmKSerializers.kt | 22 +++-- .../kotlin/test/common/SerializationTests.kt | 80 +++++++++++-------- 2 files changed, 62 insertions(+), 40 deletions(-) diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/serializers/RealmKSerializers.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/serializers/RealmKSerializers.kt index d126187d05..ddf577f79b 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/serializers/RealmKSerializers.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/serializers/RealmKSerializers.kt @@ -30,6 +30,7 @@ import io.realm.kotlin.types.RealmList import io.realm.kotlin.types.RealmObject import io.realm.kotlin.types.RealmSet import io.realm.kotlin.types.RealmUUID +import kotlinx.serialization.Contextual import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.builtins.ListSerializer @@ -351,6 +352,13 @@ public object RealmAnyKSerializer : KSerializer { @Serializable(RealmUUIDKSerializer::class) var uuid: RealmUUID? = null var realmObject: RealmObject? = null + + @Contextual + var set: RealmSet? = null + @Contextual + var list: RealmList? = null + @Contextual + var dictionary: RealmDictionary? = null } private val serializer = SerializableRealmAny.serializer() @@ -371,10 +379,9 @@ public object RealmAnyKSerializer : KSerializer { Type.OBJECT_ID -> RealmAny.create(it.objectId!!) Type.UUID -> RealmAny.create(it.uuid!!) Type.OBJECT -> RealmAny.create(it.realmObject!!) - Type.SET, - Type.LIST, - Type.DICTIONARY -> - throw UnsupportedOperationException("Serialization of nested collections is not yet supported") + Type.SET -> RealmAny.create(it.set!!) + Type.LIST -> RealmAny.create(it.list!!) + Type.DICTIONARY -> RealmAny.create(it.dictionary!!) } } } @@ -399,10 +406,9 @@ public object RealmAnyKSerializer : KSerializer { ) Type.UUID -> uuid = value.asRealmUUID() Type.OBJECT -> realmObject = value.asRealmObject() - Type.SET, - Type.LIST, - Type.DICTIONARY -> - throw UnsupportedOperationException("Serialization of nested collections is not yet supported") + Type.SET -> set = value.asSet() + Type.LIST -> list = value.asList() + Type.DICTIONARY -> dictionary = value.asDictionary() } } ) diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/SerializationTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/SerializationTests.kt index 088cf1919e..0d15e87e9e 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/SerializationTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/SerializationTests.kt @@ -31,9 +31,13 @@ import io.realm.kotlin.RealmConfiguration import io.realm.kotlin.entities.SerializableEmbeddedObject import io.realm.kotlin.entities.SerializableSample import io.realm.kotlin.ext.asRealmObject +import io.realm.kotlin.ext.realmAnyDictionaryOf +import io.realm.kotlin.ext.realmAnyListOf +import io.realm.kotlin.ext.realmAnySetOf import io.realm.kotlin.internal.restrictToMillisPrecision import io.realm.kotlin.serializers.MutableRealmIntKSerializer import io.realm.kotlin.serializers.RealmAnyKSerializer +import io.realm.kotlin.serializers.RealmDictionaryKSerializer import io.realm.kotlin.serializers.RealmInstantKSerializer import io.realm.kotlin.serializers.RealmListKSerializer import io.realm.kotlin.serializers.RealmSetKSerializer @@ -46,17 +50,17 @@ import io.realm.kotlin.types.ObjectId import io.realm.kotlin.types.RealmAny import io.realm.kotlin.types.RealmDictionary import io.realm.kotlin.types.RealmInstant +import io.realm.kotlin.types.RealmList import io.realm.kotlin.types.RealmObject -import io.realm.kotlin.types.RealmUUID +import io.realm.kotlin.types.RealmSet import kotlinx.serialization.UseSerializers +import kotlinx.serialization.builtins.nullable import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.modules.polymorphic import kotlinx.serialization.modules.subclass -import org.mongodb.kbson.BsonObjectId -import org.mongodb.kbson.Decimal128 import kotlin.reflect.KClass import kotlin.reflect.KClassifier import kotlin.reflect.KMutableProperty1 @@ -84,6 +88,16 @@ class SerializationTests { polymorphic(EmbeddedRealmObject::class) { subclass(SerializableEmbeddedObject::class) } + + contextual(RealmSet::class) { args -> + RealmSetKSerializer(RealmAnyKSerializer.nullable) + } + contextual(RealmList::class) { args -> + RealmListKSerializer(RealmAnyKSerializer.nullable) + } + contextual(RealmDictionary::class) { args -> + RealmDictionaryKSerializer(RealmAnyKSerializer.nullable) + } } } @@ -268,62 +282,64 @@ class SerializationTests { @Test fun exhaustiveRealmAnyTester() { - TypeDescriptor - .anyClassifiers - .map { classifier -> - when (classifier.key) { - Byte::class -> SerializableSample().apply { - nullableRealmAnyField = RealmAny.create(byteField) - } - Char::class -> SerializableSample().apply { - nullableRealmAnyField = RealmAny.create(charField) - } - Short::class -> SerializableSample().apply { - nullableRealmAnyField = RealmAny.create(shortField) - } - Int::class -> SerializableSample().apply { - nullableRealmAnyField = RealmAny.create(intField) - } - Long::class -> SerializableSample().apply { + RealmAny.Type.values() + .map { type: RealmAny.Type -> + type to when (type) { + RealmAny.Type.INT -> SerializableSample().apply { nullableRealmAnyField = RealmAny.create(longField) } - Float::class -> SerializableSample().apply { + RealmAny.Type.FLOAT -> SerializableSample().apply { nullableRealmAnyField = RealmAny.create(floatField) } - Double::class -> SerializableSample().apply { + RealmAny.Type.DOUBLE -> SerializableSample().apply { nullableRealmAnyField = RealmAny.create(doubleField) } - ByteArray::class -> SerializableSample().apply { + RealmAny.Type.BINARY -> SerializableSample().apply { nullableRealmAnyField = RealmAny.create(binaryField) } - Boolean::class -> SerializableSample().apply { + RealmAny.Type.BOOL -> SerializableSample().apply { nullableRealmAnyField = RealmAny.create(booleanField) } - String::class -> SerializableSample().apply { + RealmAny.Type.STRING -> SerializableSample().apply { nullableRealmAnyField = RealmAny.create(stringField) } - Decimal128::class -> SerializableSample().apply { + RealmAny.Type.DECIMAL128 -> SerializableSample().apply { nullableRealmAnyField = RealmAny.create(decimal128Field) } - RealmInstant::class -> SerializableSample().apply { + RealmAny.Type.TIMESTAMP -> SerializableSample().apply { nullableRealmAnyField = RealmAny.create(timestampField) } - BsonObjectId::class -> SerializableSample().apply { + RealmAny.Type.OBJECT -> SerializableSample().apply { nullableRealmAnyField = RealmAny.create(bsonObjectIdField) } - RealmUUID::class -> SerializableSample().apply { + RealmAny.Type.UUID -> SerializableSample().apply { nullableRealmAnyField = RealmAny.create(uuidField) } - RealmObject::class -> SerializableSample().apply { + RealmAny.Type.OBJECT_ID -> SerializableSample().apply { SerializableSample().let { nullableObject = it nullableRealmAnyField = RealmAny.create(it) } } - else -> throw IllegalStateException("Untested type $classifier") + RealmAny.Type.OBJECT -> SerializableSample().apply { + SerializableSample().let { + nullableObject = it + nullableRealmAnyField = RealmAny.create(it) + } + } + RealmAny.Type.SET -> SerializableSample().apply { + nullableRealmAnyField = realmAnySetOf(1, 2, 3) + } + RealmAny.Type.LIST -> SerializableSample().apply { + nullableRealmAnyField = realmAnyListOf(RealmAny.create(1), RealmAny.create(2)) + } + RealmAny.Type.DICTIONARY -> SerializableSample().apply { + nullableRealmAnyField = realmAnyDictionaryOf("key1" to RealmAny.create(1), "key2" to RealmAny.create(2)) + } + else -> throw IllegalStateException("Untested type $type") } } - .forEach { expected -> + .forEach { (type, expected) -> val encoded: String = json.encodeToString(expected) val decoded: SerializableSample = json.decodeFromString(encoded) From 6b3d872b8afb55c0fa880f78b2b56f8f2808bcd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Mon, 4 Sep 2023 12:48:59 +0200 Subject: [PATCH 27/41] Update CHANGELOG.md Co-authored-by: Christian Melchior --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8f7cf8944..131d61f5dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,7 +29,7 @@ childA == childC if the content is the same. Custom implementations of these methods will be respected if they are present. (Issue [#1097](https://github.com/realm/realm-kotlin/issues/1097)) * Support for performing geospatial queries using the new classes: `GeoPoint`, `GeoCircle`, `GeoBox`, and `GeoPolygon`. See `GeoPoint` documentation on how to persist locations. (Issue [#1403](https://github.com/realm/realm-kotlin/pull/1403)) * Support for automatic resolution of embedded object constraints during migration through `RealmConfiguration.Builder.migration(migration: AutomaticSchemaMigration, resolveEmbeddedObjectConstraints: Boolean)`. (Issue [#1464](https://github.com/realm/realm-kotlin/issues/1464) -* Support for collections in `RealmAny`. (Issue [#1434](https://github.com/realm/realm-kotlin/issues/1434)) +* Support for RealmLists, RealmSets and RealmDictionaries in `RealmAny`. This is only supported in the local database, Device Sync support will come in a future release. (Issue [#1434](https://github.com/realm/realm-kotlin/issues/1434)) * [Sync] Add support for customizing authorization headers and adding additional custom headers to all Atlas App service requests with `AppConfiguration.Builder.authorizationHeaderName()` and `AppConfiguration.Builder.addCustomRequestHeader(...)`. (Issue [#1453](https://github.com/realm/realm-kotlin/pull/1453)) ### Fixed From 619d499a845e01b75c2c4731fa32c8dd87202910 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Mon, 4 Sep 2023 17:46:47 +0200 Subject: [PATCH 28/41] Updates according to review comments --- .../kotlin/internal/interop/RealmInterop.kt | 11 +- .../kotlin/io/realm/kotlin/ext/RealmAnyExt.kt | 19 ++- .../io/realm/kotlin/internal/Converters.kt | 90 ++++++----- .../io/realm/kotlin/internal/Notifiable.kt | 1 + .../kotlin/internal/RealmListInternal.kt | 20 +-- .../realm/kotlin/internal/RealmMapInternal.kt | 12 +- .../kotlin/internal/RealmObjectHelper.kt | 24 +-- .../realm/kotlin/internal/RealmSetInternal.kt | 10 +- .../kotlin/internal/SuspendableNotifier.kt | 2 +- .../kotlin/serializers/RealmKSerializers.kt | 1 - .../common/RealmAnyNestedCollectionTests.kt | 143 ++++-------------- .../realm/kotlin/test/common/RealmAnyTests.kt | 4 + .../test/common/RealmDictionaryTests.kt | 7 +- .../kotlin/test/common/SerializationTests.kt | 2 +- .../test/mongodb/common/SyncedRealmTests.kt | 45 +++--- 15 files changed, 162 insertions(+), 229 deletions(-) diff --git a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt index 44fbded0f7..992d1858d8 100644 --- a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt +++ b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt @@ -955,6 +955,9 @@ actual object RealmInterop { val deletionCount = LongArray(1) val modificationCount = LongArray(1) val movesCount = LongArray(1) + // Not exposed in SDK yet, but could be used to provide optimized notifications when + // collections are cleared. + // https://github.com/realm/realm-kotlin/issues/1498 val collectionWasCleared = BooleanArray(1) val collectionWasDeleted = BooleanArray(1) @@ -1061,8 +1064,10 @@ actual object RealmInterop { val deletionStructs = realmc.new_valueArray(deletions[0].toInt()) val insertionStructs = realmc.new_valueArray(insertions[0].toInt()) val modificationStructs = realmc.new_valueArray(modifications[0].toInt()) - // FIXME New in core ... what does it do? - val collection_was_cleared = booleanArrayOf(false) + // Not exposed in SDK yet, but could be used to provide optimized notifications when + // collections are cleared. + // https://github.com/realm/realm-kotlin/issues/1498 + val collectionWasCleared = booleanArrayOf(false) realmc.realm_dictionary_get_changed_keys( change.cptr(), deletionStructs, @@ -1071,7 +1076,7 @@ actual object RealmInterop { insertions, modificationStructs, modifications, - collection_was_cleared + collectionWasCleared ) // TODO optimize - integrate within mem allocator? diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmAnyExt.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmAnyExt.kt index 3cca00571f..d4c498e9f8 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmAnyExt.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmAnyExt.kt @@ -17,7 +17,14 @@ import org.mongodb.kbson.ObjectId public inline fun RealmAny.asRealmObject(): T = asRealmObject(T::class) -// FIXME Doc if this is public +/** + * Create a [RealmAny] encapsulating the [value] argument. + * + * This corresponds to calling [RealmAny.create]-variant with the specific typed non-null argument. + * + * @param value the value that should be wrapped in a [RealmAny]. + * @return a [RealmAny] wrapping the [value] argument, or `null` if [value] is null. + */ @Suppress("ComplexMethod") public fun realmAnyOf(value: Any?): RealmAny? { return when (value) { @@ -40,7 +47,15 @@ public fun realmAnyOf(value: Any?): RealmAny? { is DynamicRealmObject -> RealmAny.create(value) is Set<*> -> RealmAny.create(value.map { realmAnyOf(it) }.toRealmSet()) is List<*> -> RealmAny.create(value.map { realmAnyOf(it) }.toRealmList()) - is Map<*, *> -> RealmAny.create(value.map { (key, value) -> key as String to realmAnyOf(value) }.toRealmDictionary()) + is Map<*, *> -> RealmAny.create( + value.map { (mapKey, mapValue) -> + try { + mapKey as String + } catch (e: ClassCastException) { + throw IllegalArgumentException("Cannot create a RealmAny from a map with non-string key, found '${mapKey?.let { it::class.simpleName } ?: "null"}'") + } to realmAnyOf(mapValue) + }.toRealmDictionary() + ) is RealmAny -> value else -> throw IllegalArgumentException("Cannot create RealmAny from '$value'") } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/Converters.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/Converters.kt index f12cbd7315..e33d020ab9 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/Converters.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/Converters.kt @@ -115,9 +115,9 @@ internal inline fun realmValueToRealmAny( owner: RealmReference, issueDynamicObject: Boolean, issueDynamicMutableObject: Boolean, - set: () -> RealmSetPointer = { error("Cannot handled embedded sets") }, - list: () -> RealmListPointer = { error("Cannot handled embedded lists") }, - dictionary: () -> RealmMapPointer = { error("Cannot handled embedded dictionaries") }, + getSetFunction: () -> RealmSetPointer = { error("Cannot handled embedded sets") }, + getListFunction: () -> RealmListPointer = { error("Cannot handled embedded lists") }, + getDictionaryFunction: () -> RealmMapPointer = { error("Cannot handled embedded dictionaries") }, ): RealmAny? { return when (realmValue.isNull()) { true -> null @@ -152,17 +152,17 @@ internal inline fun realmValueToRealmAny( } } ValueType.RLM_TYPE_SET -> { - val nativePointer = set() + val nativePointer = getSetFunction() val operator = realmAnySetOperator(mediator, owner, nativePointer, issueDynamicObject, issueDynamicMutableObject) return RealmAny.create(ManagedRealmSet(parent, nativePointer, operator)) } ValueType.RLM_TYPE_LIST -> { - val nativePointer = list() + val nativePointer = getListFunction() val operator = realmAnyListOperator(mediator, owner, nativePointer, issueDynamicObject, issueDynamicMutableObject) RealmAny.create(ManagedRealmList(parent, nativePointer, operator)) } ValueType.RLM_TYPE_DICTIONARY -> { - val nativePointer = dictionary() + val nativePointer = getDictionaryFunction() val operator = realmAnyMapOperator(mediator, owner, nativePointer, issueDynamicObject, issueDynamicMutableObject) RealmAny.create(ManagedRealmDictionary(parent, nativePointer, operator)) } @@ -174,15 +174,15 @@ internal inline fun realmValueToRealmAny( @Suppress("LongParameterList") internal fun MemTrackingAllocator.realmAnyHandler( value: RealmAny?, - primitiveValues: (RealmValue) -> T = { throw IllegalArgumentException("Operation not support for primitive values") }, - reference: (RealmAny) -> T = { throw IllegalArgumentException("Operation not support for objects") }, - set: (RealmAny) -> T = { throw IllegalArgumentException("Operation not support for sets") }, - list: (RealmAny) -> T = { throw IllegalArgumentException("Operation not support for lists") }, - dictionary: (RealmAny) -> T = { throw IllegalArgumentException("Operation not support for dictionaries") }, + primitiveValueAsRealmValueHandler: (RealmValue) -> T = { throw IllegalArgumentException("Operation not support for primitive values") }, + referenceAsRealmAnyHandler: (RealmAny) -> T = { throw IllegalArgumentException("Operation not support for objects") }, + setAsRealmAnyHandler: (RealmAny) -> T = { throw IllegalArgumentException("Operation not support for sets") }, + listAsRealmAnyHandler: (RealmAny) -> T = { throw IllegalArgumentException("Operation not support for lists") }, + dictionaryAsRealmAnyHandler: (RealmAny) -> T = { throw IllegalArgumentException("Operation not support for dictionaries") }, ): T { return when (value?.type) { null -> - primitiveValues(nullTransport()) + primitiveValueAsRealmValueHandler(nullTransport()) io.realm.kotlin.types.RealmAny.Type.INT, io.realm.kotlin.types.RealmAny.Type.BOOL, @@ -194,22 +194,22 @@ internal fun MemTrackingAllocator.realmAnyHandler( io.realm.kotlin.types.RealmAny.Type.DECIMAL128, io.realm.kotlin.types.RealmAny.Type.OBJECT_ID, io.realm.kotlin.types.RealmAny.Type.UUID -> - primitiveValues(realmAnyPrimitiveToRealmValue(value)) + primitiveValueAsRealmValueHandler(realmAnyPrimitiveToRealmValue(value)) io.realm.kotlin.types.RealmAny.Type.OBJECT -> { - reference(value) + referenceAsRealmAnyHandler(value) } io.realm.kotlin.types.RealmAny.Type.SET -> { - set(value) + setAsRealmAnyHandler(value) } io.realm.kotlin.types.RealmAny.Type.LIST -> { - list(value) + listAsRealmAnyHandler(value) } io.realm.kotlin.types.RealmAny.Type.DICTIONARY -> { - dictionary(value) + dictionaryAsRealmAnyHandler(value) } } } @@ -395,38 +395,34 @@ internal val primitiveTypeConverters: Map, RealmValueConverter<*>> = internal object RealmValueArgumentConverter { fun MemTrackingAllocator.kAnyToPrimaryKeyRealmValue(value: Any?): RealmValue { return value?.let { value -> - when (value) { - is RealmObject -> { - realmObjectTransport(realmObjectToRealmReferenceOrError(value)) + primitiveTypeConverters[value::class]?.let { converter -> + with(converter as RealmValueConverter) { + publicToRealmValue(value) } - else -> { - primitiveTypeConverters[value::class]?.let { converter -> - with(converter as RealmValueConverter) { - publicToRealmValue(value) - } - } - // This is also hit from primary key - ?: throw IllegalArgumentException("Cannot use object '$value' of type '${value::class.simpleName}' as primary key argument") - } - } + } ?: throw IllegalArgumentException("Cannot use object '$value' of type '${value::class.simpleName}' as primary key argument") } ?: nullTransport() } - fun MemTrackingAllocator.kAnyToRealmValue(value: Any?): RealmValue { + + fun MemTrackingAllocator.kAnyToRealmValueWithoutImport(value: Any?): RealmValue { return value?.let { value -> - when (value) { - is RealmObject -> { - realmObjectTransport(realmObjectToRealmReferenceOrError(value)) - } - is RealmAny -> realmAnyToRealmValueWithoutImport(value) - else -> { - primitiveTypeConverters[value::class]?.let { converter -> - with(converter as RealmValueConverter) { - publicToRealmValue(value) + try { + when (value) { + is RealmObject -> { + realmObjectTransport(realmObjectToRealmReferenceOrError(value)) + } + is RealmAny -> + realmAnyToRealmValueWithoutImport(value) + else -> { + primitiveTypeConverters[value::class]?.let { converter -> + with(converter as RealmValueConverter) { + publicToRealmValue(value) + } } + ?: throw IllegalArgumentException("Cannot convert primitive type '$value' of type '${value::class.simpleName}' as query argument") } - // This is also hit from primary key - ?: throw IllegalArgumentException("Cannot use object '$value' of type '${value::class.simpleName}' as query argument") } + } catch (e: IllegalArgumentException) { + throw IllegalArgumentException("Invalid query argument: ${e.message}", e) } } ?: nullTransport() } @@ -438,7 +434,7 @@ internal object RealmValueArgumentConverter { RealmQueryListArgument( allocRealmValueList(value.size).apply { value.mapIndexed { index: Int, element: Any? -> - set(index, kAnyToRealmValue(element)) + set(index, kAnyToRealmValueWithoutImport(element)) } } ) @@ -449,7 +445,7 @@ internal object RealmValueArgumentConverter { RealmQueryListArgument( allocRealmValueList(args.size).apply { args.mapIndexed { index: Int, element: Any? -> - set(index, kAnyToRealmValue(element)) + set(index, kAnyToRealmValueWithoutImport(element)) } } ) @@ -459,10 +455,10 @@ internal object RealmValueArgumentConverter { is GeoPolygon -> { // Hack support for geospatial arguments until we have propert C-API support. // See https://github.com/realm/realm-core/pull/6934 - RealmQuerySingleArgument(kAnyToRealmValue(value.toString())) + RealmQuerySingleArgument(kAnyToRealmValueWithoutImport(value.toString())) } else -> { - RealmQuerySingleArgument(kAnyToRealmValue(value)) + RealmQuerySingleArgument(kAnyToRealmValueWithoutImport(value)) } } @@ -515,7 +511,7 @@ internal inline fun MemTrackingAllocator.realmAnyToRealmValueWithoutImport(value RealmAny.Type.SET, RealmAny.Type.LIST, RealmAny.Type.DICTIONARY -> - throw IllegalArgumentException("Cannot use nested collections as primary keys or query arguments") + throw IllegalArgumentException("Cannot pass unmanaged collections as input argument") else -> realmAnyPrimitiveToRealmValue(value) } } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/Notifiable.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/Notifiable.kt index ba8813bc8f..60cac46c82 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/Notifiable.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/Notifiable.kt @@ -136,5 +136,6 @@ internal interface CoreNotifiable : Notifiable, Observable, Ve override fun notifiable(): Notifiable = this override fun coreObservable(liveRealm: LiveRealm): CoreNotifiable? = thaw(liveRealm.realmReference) + // Checks if the underlying native pointer points still points to a valid object. fun isValid(): Boolean } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmListInternal.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmListInternal.kt index 110f678476..4d329ec98f 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmListInternal.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmListInternal.kt @@ -352,10 +352,10 @@ internal class RealmAnyListOperator( inputScope { realmAnyHandler( value = element, - primitiveValues = { realmValue: RealmValue -> + primitiveValueAsRealmValueHandler = { realmValue: RealmValue -> RealmInterop.realm_list_add(nativePointer, index.toLong(), realmValue) }, - reference = { realmValue: RealmAny -> + referenceAsRealmAnyHandler = { realmValue: RealmAny -> val obj = when (issueDynamicObject) { true -> realmValue.asRealmObject() false -> realmValue.asRealmObject() @@ -364,7 +364,7 @@ internal class RealmAnyListOperator( realmObjectToRealmReferenceWithImport(obj, mediator, realmReference, updatePolicy, cache) RealmInterop.realm_list_add(nativePointer, index.toLong(), realmObjectTransport(objRef)) }, - set = { realmValue -> + setAsRealmAnyHandler = { realmValue -> val nativePointer = RealmInterop.realm_list_insert_set(nativePointer, index.toLong()) val operator = realmAnySetOperator( mediator, @@ -374,7 +374,7 @@ internal class RealmAnyListOperator( ) operator.addAll(realmValue.asSet(), updatePolicy, cache) }, - list = { realmValue -> + listAsRealmAnyHandler = { realmValue -> val nativePointer = RealmInterop.realm_list_insert_list(nativePointer, index.toLong()) val operator = realmAnyListOperator( mediator, @@ -384,7 +384,7 @@ internal class RealmAnyListOperator( ) operator.insertAll(0, realmValue.asList(), updatePolicy, cache) }, - dictionary = { realmValue -> + dictionaryAsRealmAnyHandler = { realmValue -> val nativePointer = RealmInterop.realm_list_insert_dictionary(nativePointer, index.toLong()) val operator = realmAnyMapOperator(mediator, realmReference, nativePointer, issueDynamicObject, issueDynamicMutableObject) @@ -405,15 +405,15 @@ internal class RealmAnyListOperator( inputScope { realmAnyHandler( value = element, - primitiveValues = { realmValue: RealmValue -> + primitiveValueAsRealmValueHandler = { realmValue: RealmValue -> RealmInterop.realm_list_set(nativePointer, index.toLong(), realmValue) }, - reference = { realmValue -> + referenceAsRealmAnyHandler = { realmValue -> val objRef = realmObjectToRealmReferenceWithImport(realmValue.asRealmObject(), mediator, realmReference, updatePolicy, cache) RealmInterop.realm_list_set(nativePointer, index.toLong(), realmObjectTransport(objRef)) }, - set = { realmValue -> + setAsRealmAnyHandler = { realmValue -> // Have to clear existing elements for core to know if we are updating with a new collection RealmInterop.realm_list_set(nativePointer, index.toLong(), nullTransport()) val nativePointer = RealmInterop.realm_list_set_set(nativePointer, index.toLong()) @@ -425,7 +425,7 @@ internal class RealmAnyListOperator( ) operator.addAll(realmValue.asSet(), updatePolicy, cache) }, - list = { realmValue -> + listAsRealmAnyHandler = { realmValue -> // Have to clear existing elements for core to know if we are updating with a new collection RealmInterop.realm_list_set(nativePointer, index.toLong(), nullTransport()) val nativePointer = RealmInterop.realm_list_set_list(nativePointer, index.toLong()) @@ -437,7 +437,7 @@ internal class RealmAnyListOperator( ) operator.insertAll(0, realmValue.asList(), updatePolicy, cache) }, - dictionary = { realmValue -> + dictionaryAsRealmAnyHandler = { realmValue -> // Have to clear existing elements for core to know if we are updating with a new collection RealmInterop.realm_list_set(nativePointer, index.toLong(), nullTransport()) val nativePointer = RealmInterop.realm_list_set_dictionary(nativePointer, index.toLong()) diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt index 5435778909..73d23b9828 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt @@ -495,12 +495,12 @@ internal class RealmAnyMapOperator constructor( val keyTransport = with(keyConverter) { publicToRealmValue(key) } return realmAnyHandler( value, - primitiveValues = { + primitiveValueAsRealmValueHandler = { realm_dictionary_insert(nativePointer, keyTransport, it).let { result -> realmAny(result.first, keyTransport) to result.second } }, - reference = { + referenceAsRealmAnyHandler = { val obj = when (issueDynamicObject) { true -> it.asRealmObject() false -> it.asRealmObject() @@ -511,7 +511,7 @@ internal class RealmAnyMapOperator constructor( realmAny(result.first, keyTransport) to result.second } }, - set = { realmValue -> + setAsRealmAnyHandler = { realmValue -> // Have to clear existing elements for core to know if we are updating with a new collection realm_dictionary_insert(nativePointer, keyTransport, nullTransport()) val previous = getInternal(key) @@ -525,7 +525,7 @@ internal class RealmAnyMapOperator constructor( operator.addAll(realmValue.asSet(), updatePolicy, cache) previous to true }, - list = { realmValue -> + listAsRealmAnyHandler = { realmValue -> // Have to clear existing elements for core to know if we are updating with a new collection realm_dictionary_insert(nativePointer, keyTransport, nullTransport()) val previous = getInternal(key) @@ -539,7 +539,7 @@ internal class RealmAnyMapOperator constructor( operator.insertAll(0, realmValue.asList(), updatePolicy, cache) previous to true }, - dictionary = { realmValue -> + dictionaryAsRealmAnyHandler = { realmValue -> // Have to clear existing elements for core to know if we are updating with a new collection realm_dictionary_insert(nativePointer, keyTransport, nullTransport()) val previous = getInternal(key) @@ -987,7 +987,7 @@ internal class RealmMapValues constructor( owner.version().version, RealmInterop.realm_object_get_key(parent.objectPointer).key ) - } ?: TODO() + } ?: Triple("null", operator.realmReference.owner.version(), "null") return "RealmDictionary.values{size=$size,owner=$owner,objKey=$objKey,version=$version}" } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt index e8a753f987..13367aa2ca 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt @@ -227,17 +227,17 @@ internal object RealmObjectHelper { is RealmAny -> { realmAnyHandler( value = value, - primitiveValues = { realmValue -> + primitiveValueAsRealmValueHandler = { realmValue -> setValueTransportByKey( obj, key, realmValue ) }, - reference = { realmValue -> + referenceAsRealmAnyHandler = { realmValue -> setObjectByKey(obj, key, realmValue.asRealmObject(), updatePolicy, cache) }, - set = { realmValue -> + setAsRealmAnyHandler = { realmValue -> RealmInterop.realm_set_value(obj.objectPointer, key, nullTransport(), false) val nativePointer = RealmInterop.realm_set_set(obj.objectPointer, key) val operator = realmAnySetOperator( @@ -249,14 +249,14 @@ internal object RealmObjectHelper { ) operator.addAll(value.asSet(), updatePolicy, cache) }, - list = { realmValue -> + listAsRealmAnyHandler = { realmValue -> RealmInterop.realm_set_value(obj.objectPointer, key, nullTransport(), false) val nativePointer = RealmInterop.realm_set_list(obj.objectPointer, key) val operator = realmAnyListOperator(obj.mediator, obj.owner, nativePointer, false, false) operator.insertAll(0, value.asList(), updatePolicy, cache) }, - dictionary = { realmValue -> + dictionaryAsRealmAnyHandler = { realmValue -> RealmInterop.realm_set_value(obj.objectPointer, key, nullTransport(), false) val nativePointer = RealmInterop.realm_set_dictionary(obj.objectPointer, key) val operator = @@ -946,8 +946,8 @@ internal object RealmObjectHelper { owner = obj.owner, issueDynamicObject = true, issueDynamicMutableObject = issueDynamicMutableObject, - set = { RealmInterop.realm_get_set(obj.objectPointer, propertyInfo.key) }, - list = { RealmInterop.realm_get_list(obj.objectPointer, propertyInfo.key) }, + getSetFunction = { RealmInterop.realm_get_set(obj.objectPointer, propertyInfo.key) }, + getListFunction = { RealmInterop.realm_get_list(obj.objectPointer, propertyInfo.key) }, ) { RealmInterop.realm_get_dictionary(obj.objectPointer, propertyInfo.key) } else -> with(primitiveTypeConverters.getValue(clazz)) { @@ -1146,11 +1146,11 @@ internal object RealmObjectHelper { } else { realmAnyHandler( value = value, - primitiveValues = { realmValue -> setValueTransportByKey(obj, key, realmValue) }, - reference = { realmValue -> + primitiveValueAsRealmValueHandler = { realmValue -> setValueTransportByKey(obj, key, realmValue) }, + referenceAsRealmAnyHandler = { realmValue -> setObjectByKey(obj, key, realmValue.asRealmObject(), updatePolicy, cache) }, - set = { realmValue -> + setAsRealmAnyHandler = { realmValue -> val nativePointer = RealmInterop.realm_set_set(obj.objectPointer, key) val operator = realmAnySetOperator( obj.mediator, @@ -1160,13 +1160,13 @@ internal object RealmObjectHelper { ) operator.addAll(value.asSet(), updatePolicy, cache) }, - list = { realmValue -> + listAsRealmAnyHandler = { realmValue -> val nativePointer = RealmInterop.realm_set_list(obj.objectPointer, key) val operator = realmAnyListOperator(obj.mediator, obj.owner, nativePointer, true) operator.insertAll(0, value.asList(), updatePolicy, cache) }, - dictionary = { realmValue -> + dictionaryAsRealmAnyHandler = { realmValue -> val nativePointer = RealmInterop.realm_set_dictionary(obj.objectPointer, key) val operator = realmAnyMapOperator(obj.mediator, obj.owner, nativePointer, true) diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmSetInternal.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmSetInternal.kt index 5853d9c19d..636a471684 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmSetInternal.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmSetInternal.kt @@ -348,10 +348,10 @@ internal class RealmAnySetOperator( return inputScope { realmAnyHandler( value = element, - primitiveValues = { realmValue: RealmValue -> + primitiveValueAsRealmValueHandler = { realmValue: RealmValue -> RealmInterop.realm_set_insert(nativePointer, realmValue) }, - reference = { realmValue -> + referenceAsRealmAnyHandler = { realmValue -> val obj = when (issueDynamicObject) { true -> realmValue.asRealmObject() false -> realmValue.asRealmObject() @@ -360,9 +360,9 @@ internal class RealmAnySetOperator( realmObjectToRealmReferenceWithImport(obj, mediator, realmReference, updatePolicy, cache) RealmInterop.realm_set_insert(nativePointer, realmObjectTransport(objRef)) }, - set = { realmValue -> throw IllegalArgumentException("Sets cannot contain other collections") }, - list = { realmValue -> throw IllegalArgumentException("Sets cannot contain other collections ") }, - dictionary = { realmValue -> throw IllegalArgumentException("Sets cannot contain other collections ") }, + setAsRealmAnyHandler = { realmValue -> throw IllegalArgumentException("Sets cannot contain other collections") }, + listAsRealmAnyHandler = { realmValue -> throw IllegalArgumentException("Sets cannot contain other collections ") }, + dictionaryAsRealmAnyHandler = { realmValue -> throw IllegalArgumentException("Sets cannot contain other collections ") }, ) } } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/SuspendableNotifier.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/SuspendableNotifier.kt index e62e7dd64d..7f758af61d 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/SuspendableNotifier.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/SuspendableNotifier.kt @@ -103,7 +103,7 @@ internal class SuspendableNotifier( // notifications on newer objects. realm.refresh() val observable = flowable.notifiable() - val lifeRef = observable.coreObservable(realm) + val lifeRef: CoreNotifiable? = observable.coreObservable(realm) val changeFlow = observable.changeFlow(this@callbackFlow) // Only emit events during registration if the observed entity is already deleted // (lifeRef == null) as there is no guarantee when the first callback is delivered diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/serializers/RealmKSerializers.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/serializers/RealmKSerializers.kt index ddf577f79b..664a3e2db6 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/serializers/RealmKSerializers.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/serializers/RealmKSerializers.kt @@ -512,4 +512,3 @@ public object MutableRealmIntKSerializer : KSerializer { encoder.encodeLong(value.toLong()) } } - diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt index 7d9831d999..3ab0203e78 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt @@ -67,23 +67,16 @@ class RealmAnyNestedCollectionTests { PlatformUtils.deleteTempDir(tmpDir) } - // Set - // - Import primitive values - // - Set primitive values - // - Notifications @Test fun setInRealmAny_copyToRealm() = runBlocking { val sample = Sample().apply { stringField = "SAMPLE" } realm.write { val instance = JsonStyleRealmObject().apply { - // Assigning set - // - How to prevent adding a set containing non-any elements? - // - Can we do this on the fly!? value = RealmAny.create( realmSetOf( RealmAny.create(5), RealmAny.create("Realm"), - RealmAny.create(sample) + RealmAny.create(sample), ) ) } @@ -91,27 +84,34 @@ class RealmAnyNestedCollectionTests { } val anyValue: RealmAny = realm.query().find().single().value!! assertEquals(RealmAny.Type.SET, anyValue.type) + anyValue.asSet().toMutableSet().let { embeddedSet -> + assertTrue { embeddedSet.remove(RealmAny.create(5)) } + assertTrue { embeddedSet.remove(RealmAny.create("Realm")) } + assertEquals("SAMPLE", embeddedSet.single()!!.asRealmObject().stringField) + } } @Test fun setInRealmAny_assignment() = runBlocking { + val sample = Sample().apply { stringField = "SAMPLE" } realm.write { - copyToRealm(Sample().apply { stringField = "SAMPLE" }) val instance = copyToRealm(JsonStyleRealmObject()) - // Assigning set - // - How to prevent adding a set containing non-any elements? - // - Can we do this on the fly!? instance.value = RealmAny.create( realmSetOf( RealmAny.create(5), - RealmAny.create(4), - RealmAny.create(6) + RealmAny.create("Realm"), + RealmAny.create(sample), ) ) instance } val anyValue: RealmAny = realm.query().find().single().value!! assertEquals(RealmAny.Type.SET, anyValue.type) + anyValue.asSet().toMutableSet().let { embeddedSet -> + assertTrue { embeddedSet.remove(RealmAny.create(5)) } + assertTrue { embeddedSet.remove(RealmAny.create("Realm")) } + assertEquals("SAMPLE", embeddedSet.single()!!.asRealmObject().stringField) + } } @Test @@ -164,11 +164,6 @@ class RealmAnyNestedCollectionTests { val sample = Sample().apply { stringField = "SAMPLE" } realm.write { JsonStyleRealmObject().apply { - // Assigning list - // - Quite verbose!? - // - How to prevent/support adding a set containing non-any elements? - // - Can we do this on the fly!? - // - Type system to allow only `RealmAny.create(list: RealmList)` value = RealmAny.create( realmListOf( RealmAny.create(5), @@ -176,13 +171,6 @@ class RealmAnyNestedCollectionTests { RealmAny.create(sample), ) ) - // - Could add: - // fun realmAnyListOf(vararg: Any): RealmList - // or - // fun Iterable.toRealmAnyList(): RealmList - // to allow convenience like - // realmAnyListOf(5, "Realm", realmAnyListOf()) - // listOf(3, "Realm", realmAnyListOf()).toRealmAnyList() }.let { copyToRealm(it) } @@ -190,7 +178,6 @@ class RealmAnyNestedCollectionTests { val instance = realm.query().find().single() val anyValue: RealmAny = instance.value!! assertEquals(RealmAny.Type.LIST, anyValue.type) -// TabbedStringBuilder().dumpRealmAny(anyValue) } @Test @@ -240,7 +227,6 @@ class RealmAnyNestedCollectionTests { // Assert structure anyValue.asList().let { - it.contains(realmAnyListOf()) assertEquals(RealmAny.create(5), it[0]) assertEquals(RealmAny.create("Realm"), it[1]) assertEquals("SAMPLE", it[2]!!.asRealmObject().stringField) @@ -422,7 +408,7 @@ class RealmAnyNestedCollectionTests { // Overwriting exact list with new list instance.value!!.asList()[0] = realmAnyListOf(7) assertFailsWithMessage("List is no longer valid") { - nestedList[0]!!.asInt() + nestedList[0] } nestedList = instance.value!!.asList()[0]!!.asList() @@ -431,37 +417,17 @@ class RealmAnyNestedCollectionTests { // Overwriting root entry instance.value = null assertFailsWithMessage("List is no longer valid") { - nestedList[0]!!.asInt() + nestedList[0] } // Recreating list doesn't bring things back to shape instance.value = realmAnyListOf(realmAnyListOf(8)) assertFailsWithMessage("List is no longer valid") { - nestedList[0]!!.asInt() + nestedList[0] } } } - // List - // - Notifications - // - Parent bound deletion - - // Dict - // - Import primitive values, SET, LIST, MAP - // - Put primitive values, SET, LIST, MAP - // - Deletes other lists - // - Notifications - // - Parent bound deletion - - // Others - // - Queries for nested elements?? - // - No collections as primary key arguments - RealmAny is not supported at all - // - No collections as query arguments - DONE - // - toJson/fromJson - // - Serialization - // - Dynamic API - // - Importing objects with cache through a setter for nested collections - @Test fun dictionaryInRealmAny_copyToRealm() = runBlocking { val sample = Sample().apply { stringField = "SAMPLE" } @@ -469,7 +435,7 @@ class RealmAnyNestedCollectionTests { realm.write { // Normal realm link/object reference JsonStyleRealmObject().apply { - // Assigning dictornary with nested lists and dictionaries + // Assigning dictionary with nested lists and dictionaries value = RealmAny.create( realmDictionaryOf( "keyInt" to RealmAny.create(5), @@ -613,7 +579,7 @@ class RealmAnyNestedCollectionTests { // Fails due to https://github.com/realm/realm-core/issues/6895 assertFailsWithMessage("List is no longer valid") { - nestedList[0]!!.asInt() + nestedList[0] } // Getting updated reference to embedded list @@ -623,7 +589,7 @@ class RealmAnyNestedCollectionTests { // Overwriting root entry instance.value = null assertFailsWithMessage("List is no longer valid") { - nestedList[0]!!.asInt() + nestedList[0] } } } @@ -643,34 +609,34 @@ class RealmAnyNestedCollectionTests { instance.value = realmAnyListOf(7) // Accessing original orphaned list return 7 from the new instance, but expected ILLEGAL_STATE_EXCEPTION["List is no longer valid"] assertFailsWithMessage("List is no longer valid") { - nestedList[0]!!.asInt() + nestedList[0] } // Overwriting with null value instance.value = null // Throws excepted ILLEGAL_STATE_EXCEPTION["List is no longer valid"] assertFailsWithMessage("List is no longer valid") { - nestedList[0]!!.asInt() + nestedList[0] } // Updating to a new list instance.value = realmAnyListOf(7) // Accessing original orphaned list return 7 from the new instance again, but expected ILLEGAL_STATE_EXCEPTION["List is no longer valid"] assertFailsWithMessage("List is no longer valid") { - nestedList[0]!!.asInt() + nestedList[0] } } } @Test fun query_ThrowsOnNestedCollectionArguments() { - assertFailsWithMessage("Cannot use nested collections as primary keys or query arguments") { + assertFailsWithMessage("Invalid query argument: Cannot pass unmanaged collections as input argument") { realm.query("value == $0", RealmAny.create(realmSetOf())) } - assertFailsWithMessage("Cannot use nested collections as primary keys or query arguments") { + assertFailsWithMessage("Invalid query argument: Cannot pass unmanaged collections as input argument") { realm.query("value == $0", RealmAny.create(realmListOf())) } - assertFailsWithMessage("Cannot use nested collections as primary keys or query arguments") { + assertFailsWithMessage("Invalid query argument: Cannot pass unmanaged collections as input argument") { realm.query("value == $0", RealmAny.create(realmDictionaryOf())) } } @@ -718,17 +684,6 @@ class RealmAnyNestedCollectionTests { assertEquals(4, realm.query().find().size) - // Matching sets -// realm.query("value[0] == 1").find().single().run { -// assertEquals("SET", id) -// } -// realm.query("value[*] == 1").find().single().run { -// assertEquals("SET", id) -// } - // Size - // [RLM_ERR_INVALID_QUERY]: Operation '@size' is not supported on property of type 'mixed' - // assertEquals(1, realm.query("value[*].@size == 3").find().size) - // Matching lists realm.query("value[0] == 4").find().single().run { assertEquals("LIST", id) @@ -736,9 +691,6 @@ class RealmAnyNestedCollectionTests { realm.query("value[*] == 4").find().single().run { assertEquals("LIST", id) } - // Size - // [RLM_ERR_INVALID_QUERY]: Operation '@size' is not supported on property of type 'mixed' - // assertEquals(1, realm.query("value[1].@size == 3").find().size) // Matching dictionaries assertEquals(1, realm.query("value.key1 == 7").find().size) @@ -751,9 +703,6 @@ class RealmAnyNestedCollectionTests { assertTrue { realm.query("value[*] == 10").find().isEmpty() } // Matching across all elements and in nested structures -// realm.query("value[*][*] == 1").find().single().run { -// assertEquals("EMBEDDED", id) -// } realm.query("value[*][*] == 4").find().single().run { assertEquals("EMBEDDED", id) } @@ -768,43 +717,3 @@ class RealmAnyNestedCollectionTests { } } } - -class TabbedStringBuilder { - private val builder = StringBuilder() - internal var indentation = 0 - internal fun append(s: String) = builder.append("\t".repeat(indentation) + s + "\n") - override fun toString(): String { - return builder.toString() - } -} - -fun TabbedStringBuilder.dumpRealmAny(value: RealmAny?) { - if (value == null) { - append("null") - return - } - when (value.type) { - RealmAny.Type.SET, RealmAny.Type.LIST -> { - val collection: Collection = - if (value.type == RealmAny.Type.SET) value.asSet() else value.asList() - append("[") - indentation += 1 - collection.map { dumpRealmAny(it) } - indentation -= 1 - append("]") - } - RealmAny.Type.DICTIONARY -> value.asDictionary().let { dictionary -> - append("{") - indentation += 1 - dictionary.map { (key, element) -> - append("$key:") - indentation += 1 - dumpRealmAny(element) - indentation -= 1 - } - indentation -= 1 - append("}") - } - else -> append(value.toString()) - } -} diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyTests.kt index 10f4e58326..fbf1e7d0a6 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyTests.kt @@ -26,7 +26,9 @@ import io.realm.kotlin.entities.embedded.EmbeddedParent import io.realm.kotlin.entities.embedded.embeddedSchema import io.realm.kotlin.ext.asRealmObject import io.realm.kotlin.ext.query +import io.realm.kotlin.ext.realmDictionaryOf import io.realm.kotlin.ext.realmListOf +import io.realm.kotlin.ext.realmSetOf import io.realm.kotlin.internal.platform.runBlocking import io.realm.kotlin.notifications.DeletedObject import io.realm.kotlin.notifications.InitialObject @@ -447,7 +449,9 @@ class RealmAnyTests { realm.write { val parent = Sample().apply { nullableRealmAnyField = RealmAny.create(child) + nullableRealmAnySetField = realmSetOf(RealmAny.create(child)) nullableRealmAnyListField = realmListOf(RealmAny.create(child)) + nullableRealmAnyDictionaryField = realmDictionaryOf("key" to RealmAny.create(child)) } copyToRealm(parent) } diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmDictionaryTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmDictionaryTests.kt index 83d6a5faca..b98e34baba 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmDictionaryTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmDictionaryTests.kt @@ -3078,10 +3078,9 @@ internal class RealmAnyDictionaryTester( assertEquals(expectedObj.stringField, assertNotNull(actualObj).stringField) } null -> assertNull(actualValue) - // FIXME Should we rather test nested collections somewhere else? - RealmAny.Type.SET -> TODO() - RealmAny.Type.LIST -> TODO() - RealmAny.Type.DICTIONARY -> TODO() + RealmAny.Type.SET, + RealmAny.Type.LIST, + RealmAny.Type.DICTIONARY -> {} // Tested separately in RealmAnyNestedCollectionTests } } } diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/SerializationTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/SerializationTests.kt index 0d15e87e9e..c7908e8bd8 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/SerializationTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/SerializationTests.kt @@ -288,7 +288,7 @@ class SerializationTests { RealmAny.Type.INT -> SerializableSample().apply { nullableRealmAnyField = RealmAny.create(longField) } - RealmAny.Type.FLOAT -> SerializableSample().apply { + RealmAny.Type.FLOAT -> SerializableSample().apply { nullableRealmAnyField = RealmAny.create(floatField) } RealmAny.Type.DOUBLE -> SerializableSample().apply { diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt index bb10e330a2..b465c366f4 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt @@ -1560,35 +1560,40 @@ class SyncedRealmTests { @Test fun cannotSyncCollectionsInMixed() = runBlocking { - val flexApp = TestApp( + TestApp( + "cannotSyncCollectionsInMixed", logLevel = LogLevel.ALL, appName = io.realm.kotlin.test.mongodb.TEST_APP_FLEX, builder = { it.syncRootDirectory(PlatformUtils.createTempDir("flx-sync-")) } - ) - val (email, password) = randomEmail() to "password1234" - val user = flexApp.createUserAndLogIn(email, password) - val local = createFlexibleSyncConfig(user = user, name = "local", schema = setOf(JsonStyleRealmObject::class)) { - initialSubscriptions { - this.add(it.query()) - } - } - Realm.open(local).use { - it.write { - val obj = copyToRealm(JsonStyleRealmObject()) - assertFailsWithMessage("Cannot sync nested set") { - obj.value = realmAnySetOf() - } - assertFailsWithMessage("Cannot sync nested list") { - obj.value = realmAnyListOf() + ).use { flexApp -> + val (email, password) = randomEmail() to "password1234" + val user = flexApp.createUserAndLogIn(email, password) + val local = createFlexibleSyncConfig( + user = user, + name = "local", + schema = setOf(JsonStyleRealmObject::class) + ) { + initialSubscriptions { + this.add(it.query()) } - assertFailsWithMessage("Cannot sync nested dictionary") { - obj.value = realmAnyDictionaryOf() + } + Realm.open(local).use { + it.write { + val obj = copyToRealm(JsonStyleRealmObject()) + assertFailsWithMessage("Cannot sync nested set") { + obj.value = realmAnySetOf() + } + assertFailsWithMessage("Cannot sync nested list") { + obj.value = realmAnyListOf() + } + assertFailsWithMessage("Cannot sync nested dictionary") { + obj.value = realmAnyDictionaryOf() + } } } } - flexApp.close() } // @Test From a61e9e22a523376d6edcb8e7ee90ad838404fecd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Mon, 4 Sep 2023 18:25:22 +0200 Subject: [PATCH 29/41] More updates according to review comments --- .../realm/kotlin/internal/RealmMapInternal.kt | 2 +- .../common/RealmAnyNestedCollectionTests.kt | 5 +++++ .../common/dynamic/DynamicRealmObjectTests.kt | 21 ++++++++++++++++--- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt index 73d23b9828..03baf91b2c 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt @@ -1151,7 +1151,7 @@ internal class RealmMapEntrySetImpl constructor( owner.version().version, RealmInterop.realm_object_get_key(parent.objectPointer).key ) - } ?: TODO() + } ?: Triple("null", operator.realmReference.owner.version(), "null") return "RealmDictionary.entries{size=$size,owner=$owner,objKey=$objKey,version=$version}" } diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt index 3ab0203e78..79f68dc4b8 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt @@ -178,6 +178,11 @@ class RealmAnyNestedCollectionTests { val instance = realm.query().find().single() val anyValue: RealmAny = instance.value!! assertEquals(RealmAny.Type.LIST, anyValue.type) + anyValue.asList().let { embeddedList -> + assertEquals(RealmAny.create(5), embeddedList[0]) + assertEquals(RealmAny.create("Realm"), embeddedList[1]) + assertEquals("SAMPLE", embeddedList[2]!!.asRealmObject().stringField) + } } @Test diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/dynamic/DynamicRealmObjectTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/dynamic/DynamicRealmObjectTests.kt index 695e431fa3..31195ebad0 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/dynamic/DynamicRealmObjectTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/dynamic/DynamicRealmObjectTests.kt @@ -1808,21 +1808,36 @@ class DynamicRealmObjectTests { )!!.asDictionary() actualDictionary["list"]!!.let { innerList -> - val actualSample = innerList.asList()[0]!!.asRealmObject() + val innerSample = innerList.asList()[0]!! + val actualSample = innerSample.asRealmObject() assertIs(actualSample) assertEquals("INNER_LIST", actualSample.getValue("stringField")) + + assertFailsWith { + innerSample.asRealmObject() + } } actualDictionary["set"]!!.let { innerSet -> + val innerSample = innerSet.asSet()!!.first()!! val actualSample = - innerSet.asSet()!!.first()!!.asRealmObject() + innerSample.asRealmObject() assertIs(actualSample) assertEquals("INNER_SET", actualSample.getValue("stringField")) + + assertFailsWith { + innerSample.asRealmObject() + } } actualDictionary["dict"]!!.let { innerDictionary -> + val innerSample = innerDictionary.asDictionary()!!["key"]!! val actualSample = - innerDictionary.asDictionary()!!["key"]!!.asRealmObject() + innerSample.asRealmObject() assertIs(actualSample) assertEquals("INNER_DICT", actualSample.getValue("stringField")) + + assertFailsWith { + innerSample.asRealmObject() + } } } } From d7a30b732509ec75a359091c73d391dff383dfc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 5 Sep 2023 10:56:23 +0200 Subject: [PATCH 30/41] Update CHANGELOG --- CHANGELOG.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52bf47e229..47be490c24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,12 @@ ## 1.12.0-SNAPSHOT (YYYY-MM-DD) +This release will bump the Realm file format from version 23 to 24. Opening a file with an older format will automatically upgrade it. Downgrading to a previous file format is not possible. + ### Breaking Changes * None. ### Enhancements -* None. +* Support for RealmLists, RealmSets and RealmDictionaries in `RealmAny`. This is only supported in the local database, Device Sync support will come in a future release. (Issue [#1434](https://github.com/realm/realm-kotlin/issues/1434)) ### Fixed * None. @@ -24,12 +26,11 @@ * Minimum Android SDK: 16. ### Internal -* None. +* Updated to Realm Core `next-major`, commit 596fc2b7667d86c62c151d0061ecf56cfaaf87b1. ## 1.11.0 (2023-09-01) -This release will bump the Realm file format from version 23 to 24. Opening a file with an older format will automatically upgrade it. Downgrading to a previous file format is not possible. ### Breaking Changes * `BaseRealmObject.equals()` has changed from being identity-based only (===) to instead return `true` if two objects come from the same Realm version. This e.g means that reading the same object property twice will now be identical. Note, two Realm objects, even with identical values will not be considered equal if they belong to different versions. @@ -58,7 +59,6 @@ childA == childC if the content is the same. Custom implementations of these methods will be respected if they are present. (Issue [#1097](https://github.com/realm/realm-kotlin/issues/1097)) * Support for performing geospatial queries using the new classes: `GeoPoint`, `GeoCircle`, `GeoBox`, and `GeoPolygon`. See `GeoPoint` documentation on how to persist locations. (Issue [#1403](https://github.com/realm/realm-kotlin/pull/1403)) * Support for automatic resolution of embedded object constraints during migration through `RealmConfiguration.Builder.migration(migration: AutomaticSchemaMigration, resolveEmbeddedObjectConstraints: Boolean)`. (Issue [#1464](https://github.com/realm/realm-kotlin/issues/1464) -* Support for RealmLists, RealmSets and RealmDictionaries in `RealmAny`. This is only supported in the local database, Device Sync support will come in a future release. (Issue [#1434](https://github.com/realm/realm-kotlin/issues/1434)) * [Sync] Add support for customizing authorization headers and adding additional custom headers to all Atlas App service requests with `AppConfiguration.Builder.authorizationHeaderName()` and `AppConfiguration.Builder.addCustomRequestHeader(...)`. (Issue [#1453](https://github.com/realm/realm-kotlin/pull/1453)) * [Sync] Added support for manually triggering a reconnect attempt for Device Sync. This is done through a new `App.Sync.reconnect()` method. This method is also now called automatically when a mobile device toggles off airplane mode. (Issue [#1479](https://github.com/realm/realm-kotlin/issues/1479)) @@ -69,7 +69,7 @@ if the content is the same. Custom implementations of these methods will be resp * [Sync] Changing a subscriptions query type or query itself will now trigger the `WaitForSync.FIRST_TIME` behaviour, rather than only checking changes to the name. (Issues [#1466](https://github.com/realm/realm-kotlin/issues/1466)) ### Compatibility -* File format: Generates Realms with file format v24. +* File format: Generates Realms with file format v23. * Realm Studio 13.0.0 or above is required to open Realms created by this version. * This release is compatible with the following Kotlin releases: * Kotlin 1.8.0 and above. The K2 compiler is not supported yet. From 7256beaeee9dbcbe9323093ea85daeb03dce0bd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 5 Sep 2023 10:57:53 +0200 Subject: [PATCH 31/41] Remove new line in CHANGELOG --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47be490c24..e27004e0d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,7 +31,6 @@ This release will bump the Realm file format from version 23 to 24. Opening a fi ## 1.11.0 (2023-09-01) - ### Breaking Changes * `BaseRealmObject.equals()` has changed from being identity-based only (===) to instead return `true` if two objects come from the same Realm version. This e.g means that reading the same object property twice will now be identical. Note, two Realm objects, even with identical values will not be considered equal if they belong to different versions. From 554d236da7f523e7ddf8befec7472bd3adcae7a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 5 Sep 2023 13:18:54 +0200 Subject: [PATCH 32/41] Add support for iterating collections from map values accessor --- .../kotlin/internal/interop/RealmInterop.kt | 3 + .../kotlin/internal/interop/RealmInterop.kt | 9 +++ .../kotlin/internal/interop/RealmInterop.kt | 9 +++ packages/external/core | 2 +- .../realm/kotlin/internal/RealmMapInternal.kt | 19 +++--- .../common/RealmAnyNestedCollectionTests.kt | 64 +++++++++++++++++++ 6 files changed, 95 insertions(+), 11 deletions(-) diff --git a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt index 181b820f70..776dd654c5 100644 --- a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt +++ b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt @@ -452,6 +452,9 @@ expect object RealmInterop { // FIXME OPTIMIZE Get many fun MemAllocator.realm_results_get(results: RealmResultsPointer, index: Long): RealmValue + fun realm_results_get_set(results: RealmResultsPointer, index: Long): RealmSetPointer + fun realm_results_get_list(results: RealmResultsPointer, index: Long): RealmListPointer + fun realm_results_get_dictionary(results: RealmResultsPointer, index: Long): RealmMapPointer fun realm_results_delete_all(results: RealmResultsPointer) fun realm_get_object(realm: RealmPointer, link: Link): RealmObjectPointer diff --git a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt index 992d1858d8..92ddaebeb9 100644 --- a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt +++ b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt @@ -1849,6 +1849,15 @@ actual object RealmInterop { return RealmValue(value) } + actual fun realm_results_get_set(results: RealmResultsPointer, index: Long): RealmSetPointer = + LongPointerWrapper(realmc.realm_results_get_set(results.cptr(), index)) + + actual fun realm_results_get_list(results: RealmResultsPointer, index: Long): RealmListPointer = + LongPointerWrapper(realmc.realm_results_get_list(results.cptr(), index)) + + actual fun realm_results_get_dictionary(results: RealmResultsPointer, index: Long): RealmMapPointer = + LongPointerWrapper(realmc.realm_results_get_dictionary(results.cptr(), index)) + actual fun realm_get_object(realm: RealmPointer, link: Link): RealmObjectPointer { return LongPointerWrapper(realmc.realm_get_object(realm.cptr(), link.classKey.key, link.objKey)) } diff --git a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt b/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt index 17cb406177..64f7d36ea3 100644 --- a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt +++ b/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt @@ -1669,6 +1669,15 @@ actual object RealmInterop { return RealmValue(value) } + actual fun realm_results_get_set(results: RealmResultsPointer, index: Long): RealmSetPointer = + CPointerWrapper(realm_wrapper.realm_results_get_set(results.cptr(), index.toULong())) + + actual fun realm_results_get_list(results: RealmResultsPointer, index: Long): RealmListPointer = + CPointerWrapper(realm_wrapper.realm_results_get_list(results.cptr(), index.toULong())) + + actual fun realm_results_get_dictionary(results: RealmResultsPointer, index: Long): RealmMapPointer = + CPointerWrapper(realm_wrapper.realm_results_get_dictionary(results.cptr(), index.toULong())) + actual fun realm_get_object(realm: RealmPointer, link: Link): RealmObjectPointer { val ptr = checkedPointerResult( realm_wrapper.realm_get_object( diff --git a/packages/external/core b/packages/external/core index 596fc2b766..0da737b699 160000 --- a/packages/external/core +++ b/packages/external/core @@ -1 +1 @@ -Subproject commit 596fc2b7667d86c62c151d0061ecf56cfaaf87b1 +Subproject commit 0da737b699bf4bcfc1a3772385cd49cd9eb9cad9 diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt index 03baf91b2c..d53191faa7 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt @@ -442,16 +442,15 @@ internal class RealmAnyMapOperator constructor( return getterScope { val transport = realm_results_get(resultsPointer, index.toLong()) realmValueToRealmAny( - transport, - null, - mediator, - realmReference, - issueDynamicObject, - issueDynamicMutableObject, - // Not supported by core yet. Tracked by https://github.com/realm/realm-core/issues/6936 - { TODO("Nested sets cannot be obtained from iterator") }, - { TODO("Nested lists cannot be obtained from iterator") }, - { TODO("Nested dictionaries cannot be obtained from iterator") }, + realmValue = transport, + parent = null, + mediator = mediator, + owner = realmReference, + issueDynamicObject = issueDynamicObject, + issueDynamicMutableObject = issueDynamicMutableObject, + getSetFunction = { RealmInterop.realm_results_get_set(resultsPointer, index.toLong()) }, + getListFunction = { RealmInterop.realm_results_get_list(resultsPointer, index.toLong()) }, + getDictionaryFunction = { RealmInterop.realm_results_get_dictionary(resultsPointer, index.toLong()) }, ) } } diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt index 79f68dc4b8..429e815f47 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt @@ -505,6 +505,70 @@ class RealmAnyNestedCollectionTests { } } } + @Test + fun dictionaryInRealmAny_values() = runBlocking { + val sample = Sample().apply { stringField = "SAMPLE" } + // Import + realm.write { + copyToRealm( + JsonStyleRealmObject().apply { + // Assigning dictionary with nested lists and dictionaries + value = realmAnyDictionaryOf( + "keySet" to realmAnySetOf(5, "Realm", sample), + "keyList" to realmAnyListOf(5, "Realm", sample), + "keyDictionary" to realmAnyDictionaryOf( + "keyInt" to 5, + "keyString" to "Realm", + "keyObject" to sample, + ), + ) + } + ) + } + + val jsonStyleRealmObject: JsonStyleRealmObject = + realm.query().find().single() + val anyValue: RealmAny = jsonStyleRealmObject.value!! + assertEquals(RealmAny.Type.DICTIONARY, anyValue.type) + anyValue.asDictionary().values.run { + assertEquals(3, size) + forEach { value -> + when (value?.type) { + RealmAny.Type.SET -> { + value.asSet().toMutableSet().let { embeddedSet -> + assertEquals(3, embeddedSet.size) + assertTrue { embeddedSet.remove(RealmAny.create(5)) } + assertTrue { embeddedSet.remove(RealmAny.create("Realm")) } + assertEquals( + "SAMPLE", + embeddedSet.single()!!.asRealmObject().stringField + ) + assertEquals(1, embeddedSet.size) + } + } + RealmAny.Type.LIST -> { + value.asList().let { embeddedList -> + assertEquals(RealmAny.create(5), embeddedList[0]) + assertEquals(RealmAny.create("Realm"), embeddedList[1]) + assertEquals("SAMPLE", embeddedList[2]!!.asRealmObject().stringField) + } + } + RealmAny.Type.DICTIONARY -> { + value.asDictionary().let { embeddedDict -> + assertEquals(RealmAny.create(5), embeddedDict["keyInt"]) + assertEquals(RealmAny.create("Realm"), embeddedDict["keyString"]) + assertEquals( + "SAMPLE", + embeddedDict["keyObject"]!!.asRealmObject().stringField + ) + } + } + else -> {} // NO-OP Only testing for nested collections in here + } + } + } + } + @Test fun dictionaryInRealmAny_put() = runBlocking { val sample = Sample().apply { stringField = "SAMPLE" } From a442cabf706787e856ff1204e0e6bf9a4abde277 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 5 Sep 2023 13:54:53 +0200 Subject: [PATCH 33/41] More review feedback --- CHANGELOG.md | 2 +- .../kotlin/io/realm/kotlin/internal/interop/ErrorCode.kt | 2 +- .../jvm/kotlin/io/realm/kotlin/internal/interop/ErrorCode.kt | 3 +-- .../kotlin/io/realm/kotlin/internal/interop/ErrorCode.kt | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e27004e0d0..4099b22016 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,7 @@ This release will bump the Realm file format from version 23 to 24. Opening a fi * Minimum Android SDK: 16. ### Internal -* Updated to Realm Core `next-major`, commit 596fc2b7667d86c62c151d0061ecf56cfaaf87b1. +* Updated to Realm Core `next-major`, commit 0da737b699bf4bcfc1a3772385cd49cd9eb9cad9. ## 1.11.0 (2023-09-01) diff --git a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/ErrorCode.kt b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/ErrorCode.kt index 31db69c4c8..1d00e55cae 100644 --- a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/ErrorCode.kt +++ b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/ErrorCode.kt @@ -179,7 +179,7 @@ expect enum class ErrorCode : CodeDescription { RLM_ERR_MAINTENANCE_IN_PROGRESS, RLM_ERR_USERPASS_TOKEN_INVALID, RLM_ERR_INVALID_SERVER_RESPONSE, - REALM_ERR_APP_SERVER_ERROR, + RLM_ERR_APP_SERVER_ERROR, RLM_ERR_CALLBACK, RLM_ERR_UNKNOWN; diff --git a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/ErrorCode.kt b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/ErrorCode.kt index 9b0761ee74..ec721b3ed4 100644 --- a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/ErrorCode.kt +++ b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/ErrorCode.kt @@ -175,9 +175,8 @@ actual enum class ErrorCode(override val description: String, override val nativ RLM_ERR_APP_UNKNOWN("Unknown", realm_errno_e.RLM_ERR_APP_UNKNOWN), RLM_ERR_MAINTENANCE_IN_PROGRESS("MaintenanceInProgress", realm_errno_e.RLM_ERR_MAINTENANCE_IN_PROGRESS), RLM_ERR_USERPASS_TOKEN_INVALID("UserpassTokenInvalid", realm_errno_e.RLM_ERR_USERPASS_TOKEN_INVALID), - RLM_ERR_APP_SERVER_ERROR("AppServerError", realm_errno_e.RLM_ERR_APP_SERVER_ERROR), RLM_ERR_INVALID_SERVER_RESPONSE("InvalidServerResponse", realm_errno_e.RLM_ERR_INVALID_SERVER_RESPONSE), - REALM_ERR_APP_SERVER_ERROR("AppServerError", realm_errno_e.RLM_ERR_APP_SERVER_ERROR), + RLM_ERR_APP_SERVER_ERROR("AppServerError", realm_errno_e.RLM_ERR_APP_SERVER_ERROR), RLM_ERR_CALLBACK("Callback", realm_errno_e.RLM_ERR_CALLBACK), RLM_ERR_UNKNOWN("Unknown", realm_errno_e.RLM_ERR_UNKNOWN); diff --git a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/ErrorCode.kt b/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/ErrorCode.kt index aadd8db464..644636308c 100644 --- a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/ErrorCode.kt +++ b/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/ErrorCode.kt @@ -180,7 +180,7 @@ actual enum class ErrorCode( RLM_ERR_MAINTENANCE_IN_PROGRESS("MaintenanceInProgress", realm_errno.RLM_ERR_MAINTENANCE_IN_PROGRESS), RLM_ERR_USERPASS_TOKEN_INVALID("UserpassTokenInvalid", realm_errno.RLM_ERR_USERPASS_TOKEN_INVALID), RLM_ERR_INVALID_SERVER_RESPONSE("InvalidServerResponse", realm_errno.RLM_ERR_INVALID_SERVER_RESPONSE), - REALM_ERR_APP_SERVER_ERROR("AppServerError", realm_errno.RLM_ERR_APP_SERVER_ERROR), + RLM_ERR_APP_SERVER_ERROR("AppServerError", realm_errno.RLM_ERR_APP_SERVER_ERROR), RLM_ERR_CALLBACK("Callback", realm_errno.RLM_ERR_CALLBACK), RLM_ERR_UNKNOWN("Unknown", realm_errno.RLM_ERR_UNKNOWN); From 14501d3e0b2741782760ae73de05c62c272ef717 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 5 Sep 2023 14:07:55 +0200 Subject: [PATCH 34/41] Fix macos tests --- .../kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt b/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt index 64f7d36ea3..2993e9e801 100644 --- a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt +++ b/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt @@ -947,15 +947,15 @@ actual object RealmInterop { } actual fun realm_set_set(obj: RealmObjectPointer, key: PropertyKey): RealmSetPointer { - realm_wrapper.realm_set_set(obj.cptr(), key.key) + checkedBooleanResult(realm_wrapper.realm_set_set(obj.cptr(), key.key)) return realm_get_set(obj, key) } actual fun realm_set_list(obj: RealmObjectPointer, key: PropertyKey): RealmListPointer { - realm_wrapper.realm_set_list(obj.cptr(), key.key) + checkedBooleanResult(realm_wrapper.realm_set_list(obj.cptr(), key.key)) return realm_get_list(obj, key) } actual fun realm_set_dictionary(obj: RealmObjectPointer, key: PropertyKey): RealmMapPointer { - realm_wrapper.realm_set_dictionary(obj.cptr(), key.key) + checkedBooleanResult(realm_wrapper.realm_set_dictionary(obj.cptr(), key.key)) return realm_get_dictionary(obj, key) } From b168fde57f107d881624de0aaadc3eef61701427 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Sat, 9 Sep 2023 00:40:10 +0200 Subject: [PATCH 35/41] Updates according to review comments --- .../interop/CollectionChangeSetBuilder.kt | 3 - .../common/RealmAnyNestedCollectionTests.kt | 113 +++++++++--------- .../realm/kotlin/test/common/RealmSetTests.kt | 14 +-- .../kotlin/test/common/SerializationTests.kt | 6 +- .../dynamic/DynamicMutableRealmObjectTests.kt | 8 +- ...ealmAnyNestedDictionaryNotificationTest.kt | 4 +- .../RealmAnyNestedSetNotificationTest.kt | 4 +- 7 files changed, 71 insertions(+), 81 deletions(-) diff --git a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/CollectionChangeSetBuilder.kt b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/CollectionChangeSetBuilder.kt index 3558aa166c..ece9fdd70b 100644 --- a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/CollectionChangeSetBuilder.kt +++ b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/CollectionChangeSetBuilder.kt @@ -32,9 +32,6 @@ abstract class CollectionChangeSetBuilder { var movesCount: Int = 0 - var isCleared: Boolean = false - var isDeleted: Boolean = false - fun isEmpty(): Boolean = insertionIndices.isEmpty() && modificationIndices.isEmpty() && deletionIndices.isEmpty() && diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt index 429e815f47..ab2c81471b 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt @@ -82,12 +82,14 @@ class RealmAnyNestedCollectionTests { } copyToRealm(instance) } + val managedSample: Sample = realm.query().find().single() val anyValue: RealmAny = realm.query().find().single().value!! assertEquals(RealmAny.Type.SET, anyValue.type) - anyValue.asSet().toMutableSet().let { embeddedSet -> - assertTrue { embeddedSet.remove(RealmAny.create(5)) } - assertTrue { embeddedSet.remove(RealmAny.create("Realm")) } - assertEquals("SAMPLE", embeddedSet.single()!!.asRealmObject().stringField) + anyValue.asSet().let { embeddedSet -> + assertEquals(3, embeddedSet.size) + assertTrue { embeddedSet.contains(RealmAny.create(5)) } + assertTrue { embeddedSet.contains(RealmAny.create("Realm")) } + assertTrue { embeddedSet.contains(RealmAny.create(managedSample)) } } } @@ -95,22 +97,22 @@ class RealmAnyNestedCollectionTests { fun setInRealmAny_assignment() = runBlocking { val sample = Sample().apply { stringField = "SAMPLE" } realm.write { - val instance = copyToRealm(JsonStyleRealmObject()) - instance.value = RealmAny.create( + val managedInstance = copyToRealm(JsonStyleRealmObject()) + managedInstance.value = RealmAny.create( realmSetOf( RealmAny.create(5), RealmAny.create("Realm"), RealmAny.create(sample), ) ) - instance } + val managedSample: Sample = realm.query().find().single() val anyValue: RealmAny = realm.query().find().single().value!! - assertEquals(RealmAny.Type.SET, anyValue.type) - anyValue.asSet().toMutableSet().let { embeddedSet -> - assertTrue { embeddedSet.remove(RealmAny.create(5)) } - assertTrue { embeddedSet.remove(RealmAny.create("Realm")) } - assertEquals("SAMPLE", embeddedSet.single()!!.asRealmObject().stringField) + anyValue.asSet().let { embeddedSet -> + assertEquals(3, embeddedSet.size) + assertTrue { embeddedSet.contains(RealmAny.create(5)) } + assertTrue { embeddedSet.contains(RealmAny.create("Realm")) } + assertTrue { embeddedSet.contains(RealmAny.create(managedSample)) } } } @@ -228,6 +230,7 @@ class RealmAnyNestedCollectionTests { } val instance = realm.query().find().single() val anyValue: RealmAny = instance.value!! + val managedSample: Sample = realm.query().find().single() assertEquals(RealmAny.Type.LIST, anyValue.type) // Assert structure @@ -240,10 +243,11 @@ class RealmAnyNestedCollectionTests { assertEquals(RealmAny.create("Realm"), embeddedList[1]) assertEquals("SAMPLE", embeddedList[2]!!.asRealmObject().stringField) } - it[4]!!.asSet().toMutableSet().let { embeddedSet -> - assertTrue { embeddedSet.remove(RealmAny.create(5)) } - assertTrue { embeddedSet.remove(RealmAny.create("Realm")) } - assertEquals("SAMPLE", embeddedSet.single()!!.asRealmObject().stringField) + it[4]!!.asSet().let { embeddedSet -> + assertEquals(3, embeddedSet.size) + assertTrue { embeddedSet.contains(RealmAny.create(5)) } + assertTrue { embeddedSet.contains(RealmAny.create("Realm")) } + assertTrue { embeddedSet.contains(RealmAny.create(managedSample)) } } it[5]!!.asDictionary().toMutableMap().let { embeddedDict -> assertEquals(RealmAny.create(5), embeddedDict["keyInt"]) @@ -299,6 +303,7 @@ class RealmAnyNestedCollectionTests { } } val anyList: RealmAny = realm.query().find().single().value!! + val managedSample: Sample = realm.query().find().single() anyList.asList().let { assertEquals(RealmAny.create(5), it[0]) assertEquals(RealmAny.create("Realm"), it[1]) @@ -308,10 +313,11 @@ class RealmAnyNestedCollectionTests { assertEquals(RealmAny.create("Realm"), embeddedList[1]) assertEquals("SAMPLE", embeddedList[2]!!.asRealmObject().stringField) } - it[4]!!.asSet().toMutableSet().let { embeddedSet -> - assertTrue { embeddedSet.remove(RealmAny.create(5)) } - assertTrue { embeddedSet.remove(RealmAny.create("Realm")) } - assertEquals("SAMPLE", embeddedSet.single()!!.asRealmObject().stringField) + it[4]!!.asSet().let { embeddedSet -> + assertEquals(3, embeddedSet.size) + assertTrue { embeddedSet.contains(RealmAny.create(5)) } + assertTrue { embeddedSet.contains(RealmAny.create("Realm")) } + assertTrue { embeddedSet.contains(RealmAny.create(managedSample)) } } it[5]!!.asDictionary().toMutableMap().let { embeddedDict -> assertEquals(RealmAny.create(5), embeddedDict["keyInt"]) @@ -378,15 +384,17 @@ class RealmAnyNestedCollectionTests { } val anyValue3: RealmAny = realm.query().find().single().value!! + val managedSample: Sample = realm.query().find().single() anyValue3.asList().let { it[0]!!.asList().let { embeddedList -> assertEquals(RealmAny.create(5), embeddedList[0]) assertEquals("SAMPLE", embeddedList[1]!!.asRealmObject().stringField) } - it[1]!!.asSet().toMutableSet().let { embeddedSet -> - assertTrue { embeddedSet.remove(RealmAny.create(5)) } - assertTrue { embeddedSet.remove(RealmAny.create("Realm")) } - assertEquals("SAMPLE", embeddedSet.single()!!.asRealmObject().stringField) + it[1]!!.asSet().let { embeddedSet -> + assertEquals(3, embeddedSet.size) + assertTrue { embeddedSet.contains(RealmAny.create(5)) } + assertTrue { embeddedSet.contains(RealmAny.create("Realm")) } + assertTrue { embeddedSet.contains(RealmAny.create(managedSample)) } } it[2]!!.asDictionary().toMutableMap().let { embeddedDict -> assertEquals(RealmAny.create(5), embeddedDict["keyInt"]) @@ -476,20 +484,16 @@ class RealmAnyNestedCollectionTests { realm.query().find().single() val anyValue: RealmAny = jsonStyleRealmObject.value!! assertEquals(RealmAny.Type.DICTIONARY, anyValue.type) + val managedSample: Sample = realm.query().find().single() anyValue.asDictionary().run { assertEquals(4, size) assertEquals(5, get("keyInt")!!.asInt()) - get("keySet")!!.asSet().toMutableSet().let { embeddedSet -> + get("keySet")!!.asSet().let { embeddedSet -> assertEquals(3, embeddedSet.size) - assertTrue { embeddedSet.remove(RealmAny.create(5)) } - assertTrue { embeddedSet.remove(RealmAny.create("Realm")) } - assertEquals( - "SAMPLE", - embeddedSet.single()!!.asRealmObject().stringField - ) - assertEquals(1, embeddedSet.size) + assertTrue { embeddedSet.contains(RealmAny.create(5)) } + assertTrue { embeddedSet.contains(RealmAny.create("Realm")) } + assertTrue { embeddedSet.contains(RealmAny.create(managedSample)) } } - get("keyList")!!.asList().let { embeddedList -> assertEquals(RealmAny.create(5), embeddedList[0]) assertEquals(RealmAny.create("Realm"), embeddedList[1]) @@ -526,6 +530,7 @@ class RealmAnyNestedCollectionTests { ) } + val managedSample: Sample = realm.query().find().single() val jsonStyleRealmObject: JsonStyleRealmObject = realm.query().find().single() val anyValue: RealmAny = jsonStyleRealmObject.value!! @@ -535,15 +540,11 @@ class RealmAnyNestedCollectionTests { forEach { value -> when (value?.type) { RealmAny.Type.SET -> { - value.asSet().toMutableSet().let { embeddedSet -> + value.asSet().let { embeddedSet -> assertEquals(3, embeddedSet.size) - assertTrue { embeddedSet.remove(RealmAny.create(5)) } - assertTrue { embeddedSet.remove(RealmAny.create("Realm")) } - assertEquals( - "SAMPLE", - embeddedSet.single()!!.asRealmObject().stringField - ) - assertEquals(1, embeddedSet.size) + assertTrue { embeddedSet.contains(RealmAny.create(5)) } + assertTrue { embeddedSet.contains(RealmAny.create("Realm")) } + assertTrue { embeddedSet.contains(RealmAny.create(managedSample)) } } } RealmAny.Type.LIST -> { @@ -595,6 +596,7 @@ class RealmAnyNestedCollectionTests { } } + val managedSample: Sample = realm.query().find().single() val jsonStyleRealmObject: JsonStyleRealmObject = realm.query().find().single() val anyValue: RealmAny = jsonStyleRealmObject.value!! @@ -602,17 +604,12 @@ class RealmAnyNestedCollectionTests { anyValue.asDictionary().run { assertEquals(4, size) assertEquals(5, get("keyInt")!!.asInt()) - get("keySet")!!.asSet().toMutableSet().let { embeddedSet -> + get("keySet")!!.asSet().let { embeddedSet -> assertEquals(3, embeddedSet.size) - assertTrue { embeddedSet.remove(RealmAny.create(5)) } - assertTrue { embeddedSet.remove(RealmAny.create("Realm")) } - assertEquals( - "SAMPLE", - embeddedSet.single()!!.asRealmObject().stringField - ) - assertEquals(1, embeddedSet.size) + assertTrue { embeddedSet.contains(RealmAny.create(5)) } + assertTrue { embeddedSet.contains(RealmAny.create("Realm")) } + assertTrue { embeddedSet.contains(RealmAny.create(managedSample)) } } - get("keyList")!!.asList().let { embeddedList -> assertEquals(RealmAny.create(5), embeddedList[0]) assertEquals(RealmAny.create("Realm"), embeddedList[1]) @@ -646,7 +643,6 @@ class RealmAnyNestedCollectionTests { // Overwriting exact list with new list instance.value!!.asDictionary()["key"] = realmAnyListOf(7) - // Fails due to https://github.com/realm/realm-core/issues/6895 assertFailsWithMessage("List is no longer valid") { nestedList[0] } @@ -762,10 +758,19 @@ class RealmAnyNestedCollectionTests { } // Matching dictionaries - assertEquals(1, realm.query("value.key1 == 7").find().size) - assertEquals(1, realm.query("value['key1'] == 7").find().size) + realm.query("value.key1 == 7").find().single().run { + assertEquals("DICT", id) + } + realm.query("value['key1'] == 7").find().single().run { + assertEquals("DICT", id) + } + realm.query("value[*] == 7").find().single().run { + assertEquals("DICT", id) + } assertEquals(0, realm.query("value.unknown == 3").find().size) - assertEquals(1, realm.query("value.@keys == 'key1'").find().size) + realm.query("value.@keys == 'key1'").find().single().run { + assertEquals("DICT", id) + } assertEquals(0, realm.query("value.@keys == 'unknown'").find().size) // None diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmSetTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmSetTests.kt index d42e3ebcae..225f18f490 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmSetTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmSetTests.kt @@ -638,24 +638,12 @@ class RealmSetTests : CollectionQueryTests { } @Test - fun contains_unmanagedArgs() = runBlocking { + fun dontImportUnmanagedArgsToNonImportingMethods() = runBlocking { val frozenObject = realm.write { val liveObject = copyToRealm(RealmSetContainer()) assertEquals(1, query().find().size) assertFalse(liveObject.objectSetField.contains(RealmSetContainer())) assertFalse(liveObject.nullableRealmAnySetField.contains(RealmAny.create(RealmSetContainer()))) - assertEquals(1, query().find().size) - liveObject - } - assertFalse(frozenObject.objectSetField.contains(RealmSetContainer())) - assertFalse(frozenObject.nullableRealmAnySetField.contains(RealmAny.create(RealmSetContainer()))) - } - - @Test - fun remove_unmanagedArgs() = runBlocking { - val frozenObject = realm.write { - val liveObject = copyToRealm(RealmSetContainer()) - assertEquals(1, query().find().size) assertFalse(liveObject.objectSetField.remove(RealmSetContainer())) assertFalse(liveObject.nullableRealmAnySetField.remove(RealmAny.create(RealmSetContainer()))) assertEquals(1, query().find().size) diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/SerializationTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/SerializationTests.kt index c7908e8bd8..300740f1a7 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/SerializationTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/SerializationTests.kt @@ -89,13 +89,13 @@ class SerializationTests { subclass(SerializableEmbeddedObject::class) } - contextual(RealmSet::class) { args -> + contextual(RealmSet::class) { _ -> RealmSetKSerializer(RealmAnyKSerializer.nullable) } - contextual(RealmList::class) { args -> + contextual(RealmList::class) { _ -> RealmListKSerializer(RealmAnyKSerializer.nullable) } - contextual(RealmDictionary::class) { args -> + contextual(RealmDictionary::class) { _ -> RealmDictionaryKSerializer(RealmAnyKSerializer.nullable) } } diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/dynamic/DynamicMutableRealmObjectTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/dynamic/DynamicMutableRealmObjectTests.kt index 0d5d9d743d..9f834b9372 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/dynamic/DynamicMutableRealmObjectTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/dynamic/DynamicMutableRealmObjectTests.kt @@ -1413,16 +1413,16 @@ class DynamicMutableRealmObjectTests { assertEquals("INNER", setObject.getValue("stringField")) setObject.set("stringField", "UPDATED_INNER") // Verify that we can add elements to the set - set.add(RealmAny.Companion.create(dynamicSampleInner)) + set.add(RealmAny.create(dynamicSampleInner)) // Verify that we cannot add nested collections assertFailsWithMessage("Sets cannot contain other collections") { - set.add(RealmAny.Companion.create(realmSetOf())) + set.add(RealmAny.create(realmSetOf())) } assertFailsWithMessage("Sets cannot contain other collections") { - set.add(RealmAny.Companion.create(realmListOf())) + set.add(RealmAny.create(realmListOf())) } assertFailsWithMessage("Sets cannot contain other collections") { - set.add(RealmAny.Companion.create(realmDictionaryOf())) + set.add(RealmAny.create(realmDictionaryOf())) } } } diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedDictionaryNotificationTest.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedDictionaryNotificationTest.kt index 5112d6d730..8ef99181fa 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedDictionaryNotificationTest.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedDictionaryNotificationTest.kt @@ -157,8 +157,8 @@ class RealmAnyNestedDictionaryNotificationTest : RealmEntityNotificationTests { } // Ignore first emission with empty sets - channel1.receiveOrFail() - channel2.receiveOrFail() + assertTrue { channel1.receiveOrFail(1.seconds).map.isEmpty() } + assertTrue { channel2.receiveOrFail(1.seconds).map.isEmpty() } // Trigger an update realm.write { diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedSetNotificationTest.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedSetNotificationTest.kt index debe60fb23..e000ad0a06 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedSetNotificationTest.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedSetNotificationTest.kt @@ -148,8 +148,8 @@ class RealmAnyNestedSetNotificationTest : RealmEntityNotificationTests { } // Ignore first emission with empty sets - channel1.receiveOrFail() - channel2.receiveOrFail() + assertTrue { channel1.receiveOrFail(1.seconds).set.isEmpty() } + assertTrue { channel2.receiveOrFail(1.seconds).set.isEmpty() } // Trigger an update realm.write { From 37b5b0f8519d56d8ca354fdbc282bccc8717ae0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Mon, 11 Sep 2023 09:40:44 +0200 Subject: [PATCH 36/41] Updates according to review comments --- .../kotlin/io/realm/kotlin/ext/RealmAnyExt.kt | 6 ++ .../kotlin/internal/RealmListInternal.kt | 5 ++ .../realm/kotlin/internal/RealmMapInternal.kt | 11 ++- .../realm/kotlin/internal/RealmSetInternal.kt | 5 ++ .../kotlin/io/realm/kotlin/types/RealmAny.kt | 67 +++++++++++++++++++ 5 files changed, 91 insertions(+), 3 deletions(-) diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmAnyExt.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmAnyExt.kt index d4c498e9f8..b40d27ae9b 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmAnyExt.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmAnyExt.kt @@ -64,6 +64,8 @@ public fun realmAnyOf(value: Any?): RealmAny? { /** * Create a [RealmAny] containing a [RealmSet] of all arguments wrapped as [RealmAny]s. * @param values elements of the set. + * + * See [RealmAny.create] for [RealmSet] constraints and examples of usage. */ public fun realmAnySetOf(vararg values: Any?): RealmAny = RealmAny.create(values.map { realmAnyOf(it) }.toRealmSet()) @@ -71,6 +73,8 @@ public fun realmAnySetOf(vararg values: Any?): RealmAny = /** * Create a [RealmAny] containing a [RealmList] of all arguments wrapped as [RealmAny]s. * @param values elements of the set. + * + * See [RealmAny.create] for [RealmList] constraints and examples of usage. */ public fun realmAnyListOf(vararg values: Any?): RealmAny = RealmAny.create(values.map { realmAnyOf(it) }.toRealmList()) @@ -79,6 +83,8 @@ public fun realmAnyListOf(vararg values: Any?): RealmAny = * Create a [RealmAny] containing a [RealmDictionary] with all argument values wrapped as * [RealmAnys]s. * @param values entries of the dictionary. + * + * See [RealmAny.create] for [RealmDictionaries] constraints and examples of usage. */ public fun realmAnyDictionaryOf(vararg values: Pair): RealmAny = RealmAny.create(values.map { (key, value) -> key to realmAnyOf(value) }.toRealmDictionary()) diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmListInternal.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmListInternal.kt index 4d329ec98f..b395db913e 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmListInternal.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmListInternal.kt @@ -184,6 +184,11 @@ internal fun ManagedRealmList.query( throw IllegalArgumentException(e.message, e.cause) } } + // parent is only available for lists with an object as an immediate parent (contrary to nested + // collections). + // Nested collections are only supported for RealmAny-values and are therefore + // outside of the BaseRealmObject bound for the generic type parameters, so we should never be + // able to reach here for nested collections of RealmAny. if (parent == null) error("Cannot perform subqueries on non-object lists") return ObjectBoundQuery( parent, diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt index d53191faa7..f68187d336 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmMapInternal.kt @@ -130,6 +130,11 @@ internal fun ManagedRealmMap.query( val mapValues = values as RealmMapValues<*, *> RealmInterop.realm_query_parse_for_results(mapValues.resultsPointer, query, queryArgs) } + // parent is only available for lists with an object as an immediate parent (contrary to nested + // collections). + // Nested collections are only supported for RealmAny-values and are therefore + // outside of the BaseRealmObject bound for the generic type parameters, so we should never be + // able to reach here for nested collections of RealmAny. if (parent == null) error("Cannot perform subqueries on non-object dictionaries") return ObjectBoundQuery( parent, @@ -817,9 +822,9 @@ internal class ManagedRealmDictionary constructor( Triple( className, owner.version().version, - RealmInterop.realm_object_get_key(parent.objectPointer).key + RealmInterop.realm_object_get_key(objectPointer).key ) - } ?: Triple("null", "null", -1) + } ?: Triple("null", operator.realmReference.version().version, "null") return "RealmDictionary{size=$size,owner=$owner,objKey=$objKey,version=$version}" } @@ -875,7 +880,7 @@ internal class KeySet constructor( owner.version().version, RealmInterop.realm_object_get_key(parent.objectPointer).key ) - } ?: TODO() + } ?: Triple("null", operator.realmReference.version().version, "null") return "RealmDictionary.keys{size=$size,owner=$owner,objKey=$objKey,version=$version}" } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmSetInternal.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmSetInternal.kt index 636a471684..a490ee89e5 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmSetInternal.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmSetInternal.kt @@ -213,6 +213,11 @@ internal fun ManagedRealmSet.query( queryArgs ) } + // parent is only available for lists with an object as an immediate parent (contrary to nested + // collections). + // Nested collections are only supported for RealmAny-values and are therefore + // outside of the BaseRealmObject bound for the generic type parameters, so we should never be + // able to reach here for nested collections of RealmAny. if (parent == null) error("Cannot perform subqueries on non-object sets") return ObjectBoundQuery( parent, diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmAny.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmAny.kt index 6cc1951e58..4135edc684 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmAny.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmAny.kt @@ -370,6 +370,25 @@ public interface RealmAny { * * To create a [RealmAny] containing a [RealmSet] of arbitrary values wrapped in [RealmAny]s * use the [io.realm.kotlin.ext.realmAnySetOf]. + * + * **NOTE:** Realm does not support to having other collections ([RealmSet], [RealmList] and + * [RealmDictionary]) in a [RealmSet]. These kind of structures can be built in a [RealmAny] + * but will fail if imported to realm. + * + * Example: + * ``` + * class SampleObject() : RealmObject { + * val realmAnyField: RealmAny? = null + * } + * val realmObject = copyToRealm(SampleObject()) + * + * // Sets with non-collection types can be built and imported into realm. + * realmObject.realmAnyField = realmAnySetOf(1, "Realm", realmObject) + * + * // Sets with collection types can be built but cannot be imported into realm. + * val setsWithCollections = realmAnySetOf(realmSetOf(), realmListOf(), realmDictionaryOf()) + * realmObject.realmAnyField = setsWithCollections // Will throw IllegalArgumentExcception + * ``` */ public fun create(value: RealmSet): RealmAny = RealmAnyImpl(Type.SET, RealmAny::class, value) @@ -379,6 +398,30 @@ public interface RealmAny { * * To create a [RealmAny] containing a [RealmList] of arbitrary values wrapped in [RealmAny]s * use the [io.realm.kotlin.ext.realmAnyListOf]. + * + * A `RealmList` can contain all [RealmAny] types, also other collection types: + * ``` + * class SampleObject() : RealmObject { + * val realmAnyField: RealmAny? = null + * } + * val realmObject = copyToRealm(SampleObject()) + * + * // Lists can contain other collection types, including [RealmSet]s. + * realmObject.realmAnyField = realmAnyListOf( + * // Sets are allowed but cannot contain nested collection types + * realmSetOf(1), + * // Lists and dictionaries can contain other collection types + * realmListOf( + * realmSetOf(), + * realmListOf(), + * realmDictionaryOf() + * ), + * realmDictionaryOf( + * "key1" to realmSetOf(), + * "key2" to realmListOf(), + * "key3" to realmDictioneryOf()) + * ) + * ``` */ public fun create(value: RealmList): RealmAny = RealmAnyImpl(Type.LIST, RealmAny::class, value) @@ -388,6 +431,30 @@ public interface RealmAny { * * To create a [RealmAny] containing a [RealmDictionary] of arbitrary values wrapped in * [RealmAny]s use the [io.realm.kotlin.ext.realmAnyDictionaryOf]. + * + * A `RealmDictionery` can contain all [RealmAny] types, also other collection types: + * ``` + * class SampleObject() : RealmObject { + * val realmAnyField: RealmAny? = null + * } + * val realmObject = copyToRealm(SampleObject()) + * + * // Dictionaries can contain other collection types, including [RealmSet]s. + * realmObjct.realmAnyField = realmAnyDictionaryOf( + * // Sets are allowed but cannot contain nested collection types + * "realmSetOf(1), + * // Lists and dictionaries can contain other nested collection types + * realmListOf( + * realmSetOf(), + * realmListOf(), + * realmDictionaryOf() + * ), + * realmDictionaryOf( + * "key1" to realmSetOf(), + * "key2" to realmListOf(), + * "key3" to realmDictionaryOf()) + * ) + * ``` */ public fun create(value: RealmDictionary): RealmAny = RealmAnyImpl(Type.DICTIONARY, RealmAny::class, value) From 0878d577caf380f7bcf9c507947e8b1153f93a63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Mon, 11 Sep 2023 10:33:22 +0200 Subject: [PATCH 37/41] More docs --- .../kotlin/io/realm/kotlin/types/RealmAny.kt | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmAny.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmAny.kt index 4135edc684..5d2bb28292 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmAny.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmAny.kt @@ -85,6 +85,26 @@ import kotlin.reflect.KClass * ``` * `RealmAny` cannot store [EmbeddedRealmObject]s. * + * `RealmAny` can contain other collections of [RealmAny]. This means that you can build nested + * collections inside a `RealmAny`-field. The only constraint is that sets cannot contain other + * collections, so must be at the leaf of such nested hierarchies. + * ``` + * realmObjct.realmAnyField = realmAnyDictionaryOf( + * // Sets are allowed but cannot contain nested collection types + * "realmSetOf(1), + * // Lists and dictionaries can contain other nested collection types + * realmListOf( + * realmSetOf(), + * realmListOf(), + * realmDictionaryOf() + * ), + * realmDictionaryOf( + * "key1" to realmSetOf(), + * "key2" to realmListOf(), + * "key3" to realmDictionaryOf()) + * ) + * ``` + * * [DynamicRealmObject]s and [DynamicMutableRealmObject]s can be used inside `RealmAny` with * the corresponding [create] function for `DynamicRealmObject`s and with [asRealmObject] using * either `DynamicRealmObject` or `DynamicMutableRealmObject` as the generic parameter. From 4981609b846302744cb72b91efcb75a811caa28f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Mon, 11 Sep 2023 13:00:48 +0200 Subject: [PATCH 38/41] Fix test --- .../realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt index ab2c81471b..84057002cc 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt @@ -572,7 +572,6 @@ class RealmAnyNestedCollectionTests { @Test fun dictionaryInRealmAny_put() = runBlocking { - val sample = Sample().apply { stringField = "SAMPLE" } // Import realm.write { copyToRealm( @@ -582,6 +581,7 @@ class RealmAnyNestedCollectionTests { } ) query().find().single().value!!.asDictionary().run { + val sample = copyToRealm(Sample().apply { stringField = "SAMPLE" }) put("keyInt", RealmAny.create(5)) put("keySet", realmAnySetOf(5, "Realm", sample)) put("keyList", realmAnyListOf(5, "Realm", sample)) From fb619c6c6d17f0d8c82a3ac4ac3b7f0683099c40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Mon, 11 Sep 2023 15:27:56 +0200 Subject: [PATCH 39/41] Updates according to review comments --- .../io/realm/kotlin/internal/RealmSetInternal.kt | 1 - .../kotlin/io/realm/kotlin/types/RealmAny.kt | 13 +++++++++---- .../RealmAnyNestedCollectionNotificationTest.kt | 13 +++++++++++-- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmSetInternal.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmSetInternal.kt index a490ee89e5..c9513db479 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmSetInternal.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmSetInternal.kt @@ -291,7 +291,6 @@ internal interface SetOperator : CollectionOperator { fun removeInternal(element: E): Boolean fun remove(element: E): Boolean { return removeInternal(element).also { - // FIXME Should this only be updated if above value is true? modCount++ } } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmAny.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmAny.kt index 5d2bb28292..6eccbd8016 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmAny.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmAny.kt @@ -89,7 +89,9 @@ import kotlin.reflect.KClass * collections inside a `RealmAny`-field. The only constraint is that sets cannot contain other * collections, so must be at the leaf of such nested hierarchies. * ``` - * realmObjct.realmAnyField = realmAnyDictionaryOf( + * realmObjct.realmAnyField = realmAnyListOf( + * // Primitive values can be added in collections + * 1, * // Sets are allowed but cannot contain nested collection types * "realmSetOf(1), * // Lists and dictionaries can contain other nested collection types @@ -428,6 +430,8 @@ public interface RealmAny { * * // Lists can contain other collection types, including [RealmSet]s. * realmObject.realmAnyField = realmAnyListOf( + * // Primitive values + * 1, * // Sets are allowed but cannot contain nested collection types * realmSetOf(1), * // Lists and dictionaries can contain other collection types @@ -461,15 +465,16 @@ public interface RealmAny { * * // Dictionaries can contain other collection types, including [RealmSet]s. * realmObjct.realmAnyField = realmAnyDictionaryOf( + * "int" to 5, * // Sets are allowed but cannot contain nested collection types - * "realmSetOf(1), + * "set" to "realmSetOf(1), * // Lists and dictionaries can contain other nested collection types - * realmListOf( + * "list" to realmListOf( * realmSetOf(), * realmListOf(), * realmDictionaryOf() * ), - * realmDictionaryOf( + * "dictionary" to realmDictionaryOf( * "key1" to realmSetOf(), * "key2" to realmListOf(), * "key3" to realmDictionaryOf()) diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedCollectionNotificationTest.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedCollectionNotificationTest.kt index 30a41c34cd..25a6d773b9 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedCollectionNotificationTest.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedCollectionNotificationTest.kt @@ -22,10 +22,12 @@ import io.realm.kotlin.entities.JsonStyleRealmObject import io.realm.kotlin.ext.asFlow import io.realm.kotlin.ext.realmAnyListOf import io.realm.kotlin.internal.platform.runBlocking +import io.realm.kotlin.notifications.DeletedObject import io.realm.kotlin.notifications.InitialObject import io.realm.kotlin.notifications.ObjectChange import io.realm.kotlin.notifications.UpdatedObject import io.realm.kotlin.test.platform.PlatformUtils +import io.realm.kotlin.test.util.receiveOrFail import io.realm.kotlin.types.RealmAny import kotlinx.coroutines.async import kotlinx.coroutines.channels.Channel @@ -79,13 +81,13 @@ class RealmAnyNestedCollectionNotificationTest { } } - assertIs>(channel.receive()) + assertIs>(channel.receiveOrFail()) realm.write { findLatest(o)!!.value!!.asList()[0]!!.asList()[1] = RealmAny.create(4) } - val objectUpdate = channel.receive() + val objectUpdate = channel.receiveOrFail() assertIs>(objectUpdate) objectUpdate.run { assertEquals(1, changedFields.size) @@ -93,6 +95,13 @@ class RealmAnyNestedCollectionNotificationTest { val nestedList = obj.value!!.asList().first()!!.asList() assertEquals(listOf(1, 4, 3), nestedList.map { it!!.asInt() }) } + + realm.write { + delete(findLatest(o)!!) + } + + assertIs>(channel.receiveOrFail()) + listener.cancel() channel.close() } From 801ff1d23a5b53f2d95b2c2d0f6a5ae2f80d3fae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Thu, 14 Sep 2023 12:01:00 +0200 Subject: [PATCH 40/41] Simple JSON serialization of RealmAny --- packages/library-base/build.gradle.kts | 1 + .../kotlin/io/realm/kotlin/ext/RealmAnyExt.kt | 30 +++++++ .../io/realm/kotlin/internal/RealmAnyImpl.kt | 84 +++++++++++++++++++ .../realm/kotlin/test/common/RealmAnyTests.kt | 17 ++++ 4 files changed, 132 insertions(+) diff --git a/packages/library-base/build.gradle.kts b/packages/library-base/build.gradle.kts index 6c9d007386..a1f76c3e7d 100644 --- a/packages/library-base/build.gradle.kts +++ b/packages/library-base/build.gradle.kts @@ -59,6 +59,7 @@ kotlin { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.coroutines}") implementation("org.jetbrains.kotlinx:atomicfu:${Versions.atomicfu}") implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:${Versions.serialization}") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:${Versions.serialization}") } } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmAnyExt.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmAnyExt.kt index b40d27ae9b..c47f76056c 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmAnyExt.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmAnyExt.kt @@ -1,11 +1,36 @@ package io.realm.kotlin.ext import io.realm.kotlin.dynamic.DynamicRealmObject +import io.realm.kotlin.internal.RealmAnyJsonSerializer +import io.realm.kotlin.internal.defaultJson +import io.realm.kotlin.internal.toRealmAny import io.realm.kotlin.types.BaseRealmObject import io.realm.kotlin.types.RealmAny +import io.realm.kotlin.types.RealmDictionary import io.realm.kotlin.types.RealmInstant +import io.realm.kotlin.types.RealmList import io.realm.kotlin.types.RealmObject +import io.realm.kotlin.types.RealmSet import io.realm.kotlin.types.RealmUUID +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonNull +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.boolean +import kotlinx.serialization.json.booleanOrNull +import kotlinx.serialization.json.contentOrNull +import kotlinx.serialization.json.doubleOrNull +import kotlinx.serialization.json.floatOrNull +import kotlinx.serialization.json.int +import kotlinx.serialization.json.intOrNull +import kotlinx.serialization.json.longOrNull +import kotlinx.serialization.modules.SerializersModule import org.mongodb.kbson.Decimal128 import org.mongodb.kbson.ObjectId @@ -88,3 +113,8 @@ public fun realmAnyListOf(vararg values: Any?): RealmAny = */ public fun realmAnyDictionaryOf(vararg values: Pair): RealmAny = RealmAny.create(values.map { (key, value) -> key to realmAnyOf(value) }.toRealmDictionary()) + + +public fun String.toRealmAny(json: Json = defaultJson): RealmAny? = json.parseToJsonElement(this).toRealmAny() +public fun RealmAny.toJson(json: Json = defaultJson): String = json.encodeToString( + RealmAnyJsonSerializer, this) diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmAnyImpl.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmAnyImpl.kt index 69fdb8c003..54d0dd6424 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmAnyImpl.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmAnyImpl.kt @@ -16,6 +16,8 @@ package io.realm.kotlin.internal +import io.realm.kotlin.ext.toRealmDictionary +import io.realm.kotlin.ext.toRealmList import io.realm.kotlin.types.BaseRealmObject import io.realm.kotlin.types.RealmAny import io.realm.kotlin.types.RealmDictionary @@ -24,6 +26,21 @@ import io.realm.kotlin.types.RealmList import io.realm.kotlin.types.RealmObject import io.realm.kotlin.types.RealmSet import io.realm.kotlin.types.RealmUUID +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonNull +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.booleanOrNull +import kotlinx.serialization.json.doubleOrNull +import kotlinx.serialization.json.floatOrNull +import kotlinx.serialization.json.longOrNull +import kotlinx.serialization.modules.SerializersModule import org.mongodb.kbson.BsonObjectId import org.mongodb.kbson.Decimal128 import kotlin.reflect.KClass @@ -170,3 +187,70 @@ internal class RealmAnyImpl constructor( override fun toString(): String = "RealmAny{type=$type, value=${getValue(type)}}" } + +internal fun realmAnyToJson(realmAny: RealmAny?): JsonElement { + val jsonElement = when(realmAny?.type) { + RealmAny.Type.INT -> JsonPrimitive(realmAny.asLong()) + RealmAny.Type.BOOL -> JsonPrimitive(realmAny.asBoolean()) + RealmAny.Type.STRING -> JsonPrimitive(realmAny.asString()) + RealmAny.Type.BINARY -> TODO() + RealmAny.Type.TIMESTAMP -> TODO() + RealmAny.Type.FLOAT -> JsonPrimitive(realmAny.asFloat()) + RealmAny.Type.DOUBLE -> JsonPrimitive(realmAny.asDouble()) + RealmAny.Type.DECIMAL128 -> TODO() + RealmAny.Type.OBJECT_ID -> TODO() + RealmAny.Type.UUID -> TODO() + RealmAny.Type.OBJECT -> TODO() + RealmAny.Type.SET -> JsonArray(realmAny.asSet().map { realmAnyToJson(it) }) + RealmAny.Type.LIST -> JsonArray(realmAny.asList().map { realmAnyToJson(it) }) + RealmAny.Type.DICTIONARY -> { + JsonObject(realmAny.asDictionary().mapValues { (k, v) -> realmAnyToJson(v) }) + } + null -> JsonNull + } + return jsonElement +} + +internal fun JsonElement.toRealmAny() : RealmAny? = when (this) { + is JsonArray -> { RealmAny.create(this.map { it.toRealmAny() }.toRealmList()) } + is JsonObject -> { RealmAny.create(this.map { (key, value) -> key to value.toRealmAny() }.toRealmDictionary()) } + is JsonPrimitive -> { + if (this.isString) { + RealmAny.create(this.content) + } else + this.longOrNull?.let { + RealmAny.create(it) + } ?: + this.doubleOrNull?.let { + RealmAny.create(it) + } ?: + this.floatOrNull?.let { + RealmAny.create(it) + } ?: + this.booleanOrNull?.let { + RealmAny.create(it) + } ?: + TODO("Cannot parse $this into a RealmAny") + } + JsonNull -> null +} + +internal val defaultJson = Json { + serializersModule = SerializersModule { + contextual(RealmAny::class) { _ -> RealmAnyJsonSerializer } + } +} + +public object RealmAnyJsonSerializer: KSerializer { + private val serializer = JsonElement.serializer() + override val descriptor: SerialDescriptor = serializer.descriptor + override fun deserialize(decoder: Decoder): RealmAny? { + return decoder.decodeSerializableValue(serializer).toRealmAny() + } + override fun serialize(encoder: Encoder, value: RealmAny?) { + encoder.encodeSerializableValue( + serializer = serializer, + value = realmAnyToJson(value) + ) + } +} diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyTests.kt index fbf1e7d0a6..cfa82c6118 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyTests.kt @@ -26,9 +26,12 @@ import io.realm.kotlin.entities.embedded.EmbeddedParent import io.realm.kotlin.entities.embedded.embeddedSchema import io.realm.kotlin.ext.asRealmObject import io.realm.kotlin.ext.query +import io.realm.kotlin.ext.realmAnyListOf import io.realm.kotlin.ext.realmDictionaryOf import io.realm.kotlin.ext.realmListOf import io.realm.kotlin.ext.realmSetOf +import io.realm.kotlin.ext.toJson +import io.realm.kotlin.ext.toRealmAny import io.realm.kotlin.internal.platform.runBlocking import io.realm.kotlin.notifications.DeletedObject import io.realm.kotlin.notifications.InitialObject @@ -94,6 +97,20 @@ class RealmAnyTests { PlatformUtils.deleteTempDir(tmpDir) } + @Test + fun json() { + RealmAny.create("Realn").toJson().toRealmAny().let { assertEquals(RealmAny.create("Realm"), it) } + + RealmAny.create(1).toJson().toRealmAny().let { assertEquals(RealmAny.create(1), it) } + + RealmAny.create(1.5).toJson().toRealmAny().let { assertEquals(RealmAny.create(1.5), it) } + +// RealmAny.create(1.5f).toJson().toRealmAny().let { assertEquals(RealmAny.create(1.5f), it) } // Will fail as json parser is parsing it as double + + realmAnyListOf(1, realmDictionaryOf("key" to realmListOf(1, 2))) .toJson().toRealmAny().let { + realmAnyListOf(1, realmDictionaryOf("key" to realmListOf(1, 2))) + } + } @Test fun missingClassFromSchema_unmanagedWorks() { val value = NotInSchema() From 2023660f8da2234e1f25331cc89fc2a891401668 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Thu, 14 Sep 2023 12:44:11 +0200 Subject: [PATCH 41/41] Remove unused imports --- .../kotlin/io/realm/kotlin/ext/RealmAnyExt.kt | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmAnyExt.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmAnyExt.kt index c47f76056c..e1accffffa 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmAnyExt.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmAnyExt.kt @@ -12,25 +12,7 @@ import io.realm.kotlin.types.RealmList import io.realm.kotlin.types.RealmObject import io.realm.kotlin.types.RealmSet import io.realm.kotlin.types.RealmUUID -import kotlinx.serialization.KSerializer -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonArray -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.JsonNull -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.JsonPrimitive -import kotlinx.serialization.json.boolean -import kotlinx.serialization.json.booleanOrNull -import kotlinx.serialization.json.contentOrNull -import kotlinx.serialization.json.doubleOrNull -import kotlinx.serialization.json.floatOrNull -import kotlinx.serialization.json.int -import kotlinx.serialization.json.intOrNull -import kotlinx.serialization.json.longOrNull -import kotlinx.serialization.modules.SerializersModule import org.mongodb.kbson.Decimal128 import org.mongodb.kbson.ObjectId