From b5c3a06694ae7f5e2eba5661c1ad271d944e544a Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 14 Aug 2023 09:52:20 +0200 Subject: [PATCH 01/21] Backup --- .../src/main/jni/realm_api_helpers.cpp | 6 +++- .../io/realm/kotlin/ext/BaseRealmObjectExt.kt | 4 +-- .../io/realm/kotlin/ext/RealmDictionaryExt.kt | 30 +++++++++++++++++++ .../io/realm/kotlin/ext/RealmListExt.kt | 14 +++++++++ .../kotlin/io/realm/kotlin/ext/RealmSetExt.kt | 16 ++++++++++ .../io/realm/kotlin/internal/BaseRealmImpl.kt | 2 +- .../io/realm/kotlin/internal/RealmImpl.kt | 4 +-- .../kotlin/internal/RealmListInternal.kt | 2 +- .../realm/kotlin/internal/RealmMapInternal.kt | 2 +- .../kotlin/internal/RealmObjectReference.kt | 4 +-- .../realm/kotlin/internal/RealmResultsImpl.kt | 2 +- .../realm/kotlin/internal/RealmSetInternal.kt | 2 +- .../kotlin/internal/SuspendableNotifier.kt | 2 +- .../kotlin/internal/query/ObjectQuery.kt | 2 +- .../kotlin/internal/query/SingleQuery.kt | 4 +-- .../io/realm/kotlin/query/RealmSingleQuery.kt | 3 +- .../RealmListNotificationsTests.kt | 3 +- .../RealmObjectNotificationsTests.kt | 27 +++++++++++++++++ 18 files changed, 111 insertions(+), 18 deletions(-) diff --git a/packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp b/packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp index 9848fc99c4..cba045bcd0 100644 --- a/packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp +++ b/packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp @@ -227,7 +227,11 @@ register_notification_cb(int64_t collection_ptr, realm_collection_type_e collect }; switch (collection_type) { - case RLM_COLLECTION_TYPE_NONE: return realm_object_add_notification_callback( + case RLM_COLLECTION_TYPE_NONE: + auto obj = reinterpret_cast(collection_ptr) + ob + + return realm_object_add_notification_callback( reinterpret_cast(collection_ptr), user_data, // Use the callback as user data user_data_free, diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/BaseRealmObjectExt.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/BaseRealmObjectExt.kt index 544a7d909b..bbd190d0a5 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/BaseRealmObjectExt.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/BaseRealmObjectExt.kt @@ -88,7 +88,7 @@ public fun BaseRealmObject.isValid(): Boolean = runIfManaged { * from a write transaction ([Realm.write]) or on a [DynamicRealmObject] inside a migration * ([AutomaticSchemaMigration.migrate]). */ -public fun T.asFlow(): Flow> = runIfManaged { +public fun T.asFlow(vararg keyPaths: String): Flow> = runIfManaged { checkNotificationsAvailable() - return owner.owner.registerObserver(this) as Flow> + return owner.owner.registerObserver(this, keyPaths) as Flow> } ?: throw IllegalStateException("Changes cannot be observed on unmanaged objects.") diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmDictionaryExt.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmDictionaryExt.kt index b3d48fa629..3f01a2d9b1 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmDictionaryExt.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmDictionaryExt.kt @@ -18,20 +18,26 @@ package io.realm.kotlin.ext import io.realm.kotlin.TypedRealm import io.realm.kotlin.internal.ManagedRealmDictionary +import io.realm.kotlin.internal.ManagedRealmList +import io.realm.kotlin.internal.ManagedRealmMap import io.realm.kotlin.internal.RealmMapMutableEntry import io.realm.kotlin.internal.UnmanagedRealmDictionary import io.realm.kotlin.internal.asRealmDictionary import io.realm.kotlin.internal.getRealm import io.realm.kotlin.internal.query import io.realm.kotlin.internal.realmMapEntryOf +import io.realm.kotlin.notifications.ListChange +import io.realm.kotlin.notifications.MapChange import io.realm.kotlin.query.RealmQuery import io.realm.kotlin.query.TRUE_PREDICATE import io.realm.kotlin.types.BaseRealmObject import io.realm.kotlin.types.RealmDictionary import io.realm.kotlin.types.RealmDictionaryMutableEntry 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 kotlinx.coroutines.flow.Flow /** * Instantiates an **unmanaged** [RealmDictionary] from a variable number of [Pair]s of [String] @@ -112,3 +118,27 @@ public fun RealmDictionary.query( } else { throw IllegalArgumentException("Unmanaged dictionary values cannot be queried.") } + +/** + * TODO + */ +public fun RealmMap.asFlow(vararg keyPaths: String): Flow> { + if (this is ManagedRealmMap) { + operator.realmReference.checkClosed() + return operator.realmReference.owner.registerObserver(this, keyPaths) + } else { + throw UnsupportedOperationException("Unmanaged maps cannot be observed.") + } +} + +/** + * TODO + */ +public fun RealmDictionary.asFlow(vararg keyPaths: String): Flow> { + if (this is ManagedRealmDictionary) { + operator.realmReference.checkClosed() + return operator.realmReference.owner.registerObserver(this, keyPaths) + } else { + throw UnsupportedOperationException("Unmanaged dictionaries cannot be observed.") + } +} \ No newline at end of file diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmListExt.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmListExt.kt index 81e4004a9e..576344793b 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmListExt.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmListExt.kt @@ -22,6 +22,7 @@ import io.realm.kotlin.internal.UnmanagedRealmList import io.realm.kotlin.internal.asRealmList import io.realm.kotlin.internal.getRealm import io.realm.kotlin.internal.query +import io.realm.kotlin.notifications.ListChange import io.realm.kotlin.query.RealmQuery import io.realm.kotlin.query.TRUE_PREDICATE import io.realm.kotlin.types.BaseRealmObject @@ -29,6 +30,7 @@ import io.realm.kotlin.types.RealmDictionary import io.realm.kotlin.types.RealmList import io.realm.kotlin.types.RealmSet import io.realm.kotlin.types.TypedRealmObject +import kotlinx.coroutines.flow.Flow /** * Instantiates an **unmanaged** [RealmList]. @@ -72,3 +74,15 @@ public fun RealmList.query( } else { throw IllegalArgumentException("Unmanaged list cannot be queried") } + +/** + * TODO + */ +public fun RealmList.asFlow(vararg keyPaths: String): Flow> { + if (this is ManagedRealmList) { + operator.realmReference.checkClosed() + return operator.realmReference.owner.registerObserver(this, keyPaths) + } else { + throw UnsupportedOperationException("Unmanaged lists cannot be observed.") + } +} \ No newline at end of file diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmSetExt.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmSetExt.kt index 6f34237352..f6a81ffe0a 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmSetExt.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmSetExt.kt @@ -17,11 +17,14 @@ package io.realm.kotlin.ext import io.realm.kotlin.TypedRealm +import io.realm.kotlin.internal.ManagedRealmList import io.realm.kotlin.internal.ManagedRealmSet import io.realm.kotlin.internal.UnmanagedRealmSet import io.realm.kotlin.internal.asRealmSet import io.realm.kotlin.internal.getRealm import io.realm.kotlin.internal.query +import io.realm.kotlin.notifications.ListChange +import io.realm.kotlin.notifications.SetChange import io.realm.kotlin.query.RealmQuery import io.realm.kotlin.query.TRUE_PREDICATE import io.realm.kotlin.types.BaseRealmObject @@ -29,6 +32,7 @@ import io.realm.kotlin.types.RealmDictionary import io.realm.kotlin.types.RealmList import io.realm.kotlin.types.RealmObject import io.realm.kotlin.types.RealmSet +import kotlinx.coroutines.flow.Flow /** * Instantiates an **unmanaged** [RealmSet]. @@ -70,3 +74,15 @@ public fun RealmSet.query( } else { throw IllegalArgumentException("Unmanaged set cannot be queried") } + +/** + * TODO + */ +public fun RealmSet.asFlow(vararg keyPaths: String): Flow> { + if (this is ManagedRealmSet) { + operator.realmReference.checkClosed() + return operator.realmReference.owner.registerObserver(this, keyPaths) + } else { + throw UnsupportedOperationException("Unmanaged sets cannot be observed.") + } +} \ No newline at end of file diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/BaseRealmImpl.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/BaseRealmImpl.kt index 44c401ba5b..024637ef50 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/BaseRealmImpl.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/BaseRealmImpl.kt @@ -64,7 +64,7 @@ public abstract class BaseRealmImpl internal constructor( return RealmInterop.realm_get_schema_version(realmReference.dbPointer) } - internal open fun , C> registerObserver(t: Observable): Flow { + internal open fun , C> registerObserver(t: Observable, keyPath: Array): Flow { throw UnsupportedOperationException(OBSERVABLE_NOT_SUPPORTED_MESSAGE) } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmImpl.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmImpl.kt index 2be2d196e5..4ddd236a02 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmImpl.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmImpl.kt @@ -215,8 +215,8 @@ public class RealmImpl private constructor( ) } - override fun , C> registerObserver(t: Observable): Flow { - return notifier.registerObserver(t) + override fun , C> registerObserver(t: Observable, keyPaths: Array): Flow { + return notifier.registerObserver(t, keyPaths) } public fun realmReference(): FrozenRealmReference { 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 2b4d12221b..8bd5c9cb7c 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 @@ -115,7 +115,7 @@ internal class ManagedRealmList( override fun asFlow(): Flow> { operator.realmReference.checkClosed() - return operator.realmReference.owner.registerObserver(this) + return operator.realmReference.owner.registerObserver(this, arrayOf()) } override fun freeze(frozenRealm: RealmReference): ManagedRealmList? { 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..1b971f2f2c 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 @@ -101,7 +101,7 @@ internal abstract class ManagedRealmMap constructor( override fun asFlow(): Flow> { operator.realmReference.checkClosed() - return operator.realmReference.owner.registerObserver(this) + return operator.realmReference.owner.registerObserver(this, arrayOf()) } override fun registerForNotification( 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..88590e1f09 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 @@ -124,8 +124,8 @@ public class RealmObjectReference( }.toTypedArray() } - override fun asFlow(): Flow> { - return this.owner.owner.registerObserver(this) + override fun asFlow(vararg keyPath: String): Flow> { + return this.owner.owner.registerObserver(this, keyPath) } override fun delete() { 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..9f925ff796 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 @@ -106,7 +106,7 @@ internal class RealmResultsImpl constructor( override fun asFlow(): Flow> { realm.checkClosed() - return realm.owner.registerObserver(this) + return realm.owner.registerObserver(this, arrayOf()) } override fun delete() { 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 c61098a768..c842a68ac8 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 @@ -160,7 +160,7 @@ internal class ManagedRealmSet constructor( } override fun asFlow(): Flow> { - return operator.realmReference.owner.registerObserver(this) + return operator.realmReference.owner.registerObserver(this, arrayOf()) } override fun freeze(frozenRealm: RealmReference): ManagedRealmSet? { 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..f26708e883 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 @@ -86,7 +86,7 @@ internal class SuspendableNotifier( return _realmChanged.asSharedFlow() } - internal fun , C> registerObserver(flowable: Observable): Flow { + internal fun , C> registerObserver(flowable: Observable, keyPaths: Array): Flow { return callbackFlow { val token: AtomicRef = kotlinx.atomicfu.atomic(NO_OP_NOTIFICATION_TOKEN) diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/ObjectQuery.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/ObjectQuery.kt index 348f8c0f0b..8f9d6ee4e8 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/ObjectQuery.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/ObjectQuery.kt @@ -172,7 +172,7 @@ internal class ObjectQuery constructor( override fun asFlow(): Flow> { return realmReference.owner - .registerObserver(this) + .registerObserver(this, arrayOf()) } override fun delete() { diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/SingleQuery.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/SingleQuery.kt index 197068935d..a6a3ec0ae4 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/SingleQuery.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/SingleQuery.kt @@ -56,9 +56,9 @@ internal class SingleQuery constructor( * new head if any. * If there is an update, we ignore it, as the object flow would automatically emit the event. */ - override fun asFlow(): Flow> { + override fun asFlow(vararg keyPaths: String): Flow> { var oldHead: E? = null - return realmReference.owner.registerObserver(this) + return realmReference.owner.registerObserver(this, keyPaths) // Convert into flow of result head .map { resultChange: ResultsChange -> resultChange.list.firstOrNull() } // Only react when head is changed diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/query/RealmSingleQuery.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/query/RealmSingleQuery.kt index 7ce8a28867..224afd2a5b 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/query/RealmSingleQuery.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/query/RealmSingleQuery.kt @@ -72,8 +72,9 @@ public interface RealmSingleQuery : Deleteable { * the elements in a timely manner the coroutine scope will be cancelled with a * [CancellationException]. * + * @param keyPaths TODO * @return a flow representing changes to the [RealmObject] or [EmbeddedRealmObject] resulting from * running this query. */ - public fun asFlow(): Flow> + public fun asFlow(vararg keyPaths: String): Flow> } 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..8ab8d6199a 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 @@ -20,6 +20,7 @@ import io.realm.kotlin.Realm import io.realm.kotlin.RealmConfiguration import io.realm.kotlin.entities.list.RealmListContainer import io.realm.kotlin.entities.list.listTestSchema +import io.realm.kotlin.ext.asFlow import io.realm.kotlin.notifications.DeletedList import io.realm.kotlin.notifications.InitialList import io.realm.kotlin.notifications.ListChange @@ -123,7 +124,7 @@ class RealmListNotificationsTests : RealmEntityNotificationTests { val channel = Channel>(capacity = 1) val observer = async { container.objectListField - .asFlow() + .asFlow("*") .collect { flowList -> channel.send(flowList) } 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..dd20740b59 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 @@ -301,4 +301,31 @@ class RealmObjectNotificationsTests : RealmEntityNotificationTests { c.close() } } + + @Test + fun keyPath_allTopLevelProperties() = runBlocking { + val c = Channel>(1) + val obj: Sample = realm.write { + copyToRealm(Sample().apply { stringField = "Foo" }) + } + val observer = async { + obj.asFlow("*").collect { + c.trySend(it) + } + fail("Flow should not be canceled.") + } + c.receiveOrFail().let { objectChange -> + assertIs>(objectChange) + assertEquals("Foo", objectChange.obj.stringField) + } + realm.close() + observer.cancel() + c.close() + } + + @Test + fun keyPath_singleTopLevelProperty() { + + } + } From 6885aa32d0d6d5dc1b48ae85d75d81ffea50d52b Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 13 Oct 2023 16:37:09 +0200 Subject: [PATCH 02/21] Backup --- .../io/realm/kotlin/internal/interop/RealmValue.kt | 4 ++++ packages/jni-swig-stub/realm.i | 1 + .../jni-swig-stub/src/main/jni/realm_api_helpers.cpp | 3 --- .../kotlin/io/realm/kotlin/ext/BaseRealmObjectExt.kt | 3 ++- .../kotlin/io/realm/kotlin/internal/BaseRealmImpl.kt | 2 +- .../internal/{Flowable.kt => KeyPathFlowable.kt} | 6 +++++- .../kotlin/io/realm/kotlin/internal/Notifiable.kt | 2 +- .../realm/kotlin/internal/ObjectBoundRealmResults.kt | 11 +++++++---- .../kotlin/io/realm/kotlin/internal/RealmImpl.kt | 3 +-- .../io/realm/kotlin/internal/RealmListInternal.kt | 6 +++--- .../io/realm/kotlin/internal/RealmMapInternal.kt | 2 +- .../io/realm/kotlin/internal/RealmObjectReference.kt | 2 +- .../kotlin/internal/query/ObjectBoundQueries.kt | 12 ++++++++---- .../io/realm/kotlin/internal/query/ObjectQuery.kt | 4 ++-- .../io/realm/kotlin/query/RealmElementQuery.kt | 3 ++- .../kotlin/io/realm/kotlin/query/RealmResults.kt | 3 ++- .../kotlin/io/realm/kotlin/query/RealmSingleQuery.kt | 2 +- .../kotlin/io/realm/kotlin/types/RealmList.kt | 3 ++- 18 files changed, 44 insertions(+), 28 deletions(-) rename packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/{Flowable.kt => KeyPathFlowable.kt} (85%) diff --git a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmValue.kt b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmValue.kt index 35aa518e54..1516f063d5 100644 --- a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmValue.kt +++ b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmValue.kt @@ -63,3 +63,7 @@ expect class RealmQueryArgumentList sealed interface RealmQueryArgument class RealmQuerySingleArgument(val argument: RealmValue) : RealmQueryArgument class RealmQueryListArgument(val arguments: RealmValueList) : RealmQueryArgument + +/** + * Inline class used for handling C-API `realm_key_path_array_t` structs when registering notifications + */ diff --git a/packages/jni-swig-stub/realm.i b/packages/jni-swig-stub/realm.i index 02880c1df5..adfe7f0ccf 100644 --- a/packages/jni-swig-stub/realm.i +++ b/packages/jni-swig-stub/realm.i @@ -349,6 +349,7 @@ bool realm_object_is_valid(const realm_object_t*); %array_functions(realm_query_arg_t, queryArgArray); %array_functions(realm_user_identity_t, identityArray); %array_functions(realm_app_user_apikey_t, apiKeyArray); +%array_functions(realm_key_path_array_t, keyPathArray); // Work around issues with realm_size_t on Windows https://jira.mongodb.org/browse/RKOTLIN-332 %apply int64_t[] { size_t* }; diff --git a/packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp b/packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp index ffd4988f26..e450a510f3 100644 --- a/packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp +++ b/packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp @@ -228,9 +228,6 @@ register_notification_cb(int64_t collection_ptr, realm_collection_type_e collect switch (collection_type) { case RLM_COLLECTION_TYPE_NONE: - auto obj = reinterpret_cast(collection_ptr) - ob - return realm_object_add_notification_callback( reinterpret_cast(collection_ptr), user_data, // Use the callback as user data diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/BaseRealmObjectExt.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/BaseRealmObjectExt.kt index bbd190d0a5..493c6536a7 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/BaseRealmObjectExt.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/BaseRealmObjectExt.kt @@ -83,12 +83,13 @@ public fun BaseRealmObject.isValid(): Boolean = runIfManaged { * the elements in a timely manner the coroutine scope will be cancelled with a * [CancellationException]. * + * @param keyPaths TODO * @return a flow representing changes to the object. * @throws UnsupportedOperationException if called on a live [RealmObject] or [EmbeddedRealmObject] * from a write transaction ([Realm.write]) or on a [DynamicRealmObject] inside a migration * ([AutomaticSchemaMigration.migrate]). */ -public fun T.asFlow(vararg keyPaths: String): Flow> = runIfManaged { +public fun T.asFlow(keyPaths: List? = null): Flow> = runIfManaged { checkNotificationsAvailable() return owner.owner.registerObserver(this, keyPaths) as Flow> } ?: throw IllegalStateException("Changes cannot be observed on unmanaged objects.") diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/BaseRealmImpl.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/BaseRealmImpl.kt index 024637ef50..c0d7276a3f 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/BaseRealmImpl.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/BaseRealmImpl.kt @@ -64,7 +64,7 @@ public abstract class BaseRealmImpl internal constructor( return RealmInterop.realm_get_schema_version(realmReference.dbPointer) } - internal open fun , C> registerObserver(t: Observable, keyPath: Array): Flow { + internal open fun , C> registerObserver(t: Observable, keyPaths: List?): Flow { throw UnsupportedOperationException(OBSERVABLE_NOT_SUPPORTED_MESSAGE) } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/Flowable.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/KeyPathFlowable.kt similarity index 85% rename from packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/Flowable.kt rename to packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/KeyPathFlowable.kt index 0bc81a0c15..c36239298e 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/Flowable.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/KeyPathFlowable.kt @@ -22,6 +22,10 @@ import kotlinx.coroutines.flow.Flow * A __flowable__ is an internal entity that supports listening to changes on the type [T] as a * [Flow]. */ -internal interface Flowable { +internal interface KeyPathFlowable { + fun asFlow(keyPaths: List? = null): Flow +} + +internal interface SimpleFlowable { fun asFlow(): Flow } 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..9f85967957 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 @@ -126,7 +126,7 @@ public abstract class ChangeFlow(private val producerScope: ProducerScope< * @param T the type of entity that is observed. * @param C the type of change events emitted for the T entity. */ -internal interface CoreNotifiable : Notifiable, Observable, Versioned, Flowable +internal interface CoreNotifiable : Notifiable, Observable, Versioned, KeyPathFlowable where T : CoreNotifiable { public fun thaw(liveRealm: RealmReference): T? public fun registerForNotification(callback: Callback): RealmNotificationTokenPointer diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/ObjectBoundRealmResults.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/ObjectBoundRealmResults.kt index 62c847dee8..6c3310831e 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/ObjectBoundRealmResults.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/ObjectBoundRealmResults.kt @@ -33,7 +33,7 @@ import kotlinx.coroutines.flow.Flow internal class ObjectBoundRealmResults( val targetObject: RealmObjectReference<*>, val realmResults: RealmResults, -) : RealmResults by realmResults, InternalDeleteable, Flowable> { +) : RealmResults by realmResults, InternalDeleteable, KeyPathFlowable> { override val size: Int by realmResults::size @@ -61,7 +61,7 @@ internal class ObjectBoundRealmResults( */ override fun asFlow(): Flow> { - return realmResults.asFlow().bind(targetObject) + return realmResults.asFlow().bind(targetObject, keyPaths) } override fun delete() { @@ -78,5 +78,8 @@ internal class ObjectBoundRealmResults( * Binds a flow to an object lifecycle. It allows flows on queries to complete once the object gets * deleted. It is used on sub-queries and backlinks. */ -internal fun Flow.bind(reference: RealmObjectReference): Flow = - this.terminateWhen(reference.asFlow()) { it is DeletedObject } +internal fun Flow.bind( + reference: RealmObjectReference, + keyPaths: List? +): Flow = + this.terminateWhen(reference.asFlow(keyPaths)) { it is DeletedObject } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmImpl.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmImpl.kt index 90a0e2b730..1d47c2213e 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmImpl.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmImpl.kt @@ -46,14 +46,13 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.launch -import kotlinx.coroutines.sync.withLock import kotlin.reflect.KClass // TODO API-PUBLIC Document platform specific internals (RealmInitializer, etc.) // TODO Public due to being accessed from `SyncedRealmContext` public class RealmImpl private constructor( configuration: InternalConfiguration, -) : BaseRealmImpl(configuration), Realm, InternalTypedRealm, Flowable> { +) : BaseRealmImpl(configuration), Realm, InternalTypedRealm, KeyPathFlowable> { public val notificationScheduler: LiveRealmContext = configuration.notificationDispatcherFactory.createLiveRealmContext() 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 8bd5c9cb7c..3f64f45655 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 @@ -49,7 +49,7 @@ import kotlin.reflect.KClass internal class UnmanagedRealmList( private val backingList: MutableList = mutableListOf() ) : RealmList, InternalDeleteable, MutableList by backingList { - override fun asFlow(): Flow> = + override fun asFlow(keyPaths: List?): Flow> = throw UnsupportedOperationException("Unmanaged lists cannot be observed.") override fun delete() { @@ -113,9 +113,9 @@ internal class ManagedRealmList( return operator.set(index, element) } - override fun asFlow(): Flow> { + override fun asFlow(keyPaths: List?): Flow> { operator.realmReference.checkClosed() - return operator.realmReference.owner.registerObserver(this, arrayOf()) + return operator.realmReference.owner.registerObserver(this, keyPaths) } override fun freeze(frozenRealm: RealmReference): ManagedRealmList? { 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 1b971f2f2c..9cad190cf1 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 @@ -61,7 +61,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>, KeyPathFlowable> { private val keysPointer by lazy { RealmInterop.realm_dictionary_get_keys(nativePointer) } private val valuesPointer by lazy { RealmInterop.realm_dictionary_to_results(nativePointer) } 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 88590e1f09..417d3ed393 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 @@ -124,7 +124,7 @@ public class RealmObjectReference( }.toTypedArray() } - override fun asFlow(vararg keyPath: String): Flow> { + override fun asFlow(keyPath: List?): Flow> { return this.owner.owner.registerObserver(this, keyPath) } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/ObjectBoundQueries.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/ObjectBoundQueries.kt index 35da179879..6ac0258867 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/ObjectBoundQueries.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/ObjectBoundQueries.kt @@ -48,7 +48,11 @@ internal class ObjectBoundQuery( realmQuery.query(filter, *arguments) ) - override fun asFlow(): Flow> = realmQuery.asFlow().bind(targetObject) + // TODO + override fun asFlow(keyPath: List?): Flow> = realmQuery.asFlow().bind( + targetObject, + keyPaths + ) override fun sort(property: String, sortOrder: Sort): RealmQuery = ObjectBoundQuery( targetObject, @@ -107,19 +111,19 @@ internal class ObjectBoundRealmSingleQuery( val targetObject: RealmObjectReference<*>, val realmQuery: RealmSingleQuery ) : RealmSingleQuery by realmQuery { - override fun asFlow(): Flow> = realmQuery.asFlow().bind(targetObject) + override fun asFlow(keyPaths: List?): Flow> = realmQuery.asFlow(keyPaths).bind(targetObject, keyPaths) } internal class ObjectBoundRealmScalarNullableQuery( val targetObject: RealmObjectReference<*>, val realmQuery: RealmScalarNullableQuery ) : RealmScalarNullableQuery by realmQuery { - override fun asFlow(): Flow = realmQuery.asFlow().bind(targetObject) + override fun asFlow(keyPaths: List?): Flow = realmQuery.asFlow().bind(targetObject, keyPaths) } internal class ObjectBoundRealmScalarQuery( val targetObject: RealmObjectReference<*>, val realmQuery: RealmScalarQuery ) : RealmScalarQuery by realmQuery { - override fun asFlow(): Flow = realmQuery.asFlow().bind(targetObject) + override fun asFlow(): Flow = realmQuery.asFlow().bind(targetObject, keyPaths) } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/ObjectQuery.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/ObjectQuery.kt index 8f9d6ee4e8..b29990a5ec 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/ObjectQuery.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/ObjectQuery.kt @@ -170,9 +170,9 @@ internal class ObjectQuery constructor( override fun notifiable(): Notifiable, ResultsChange> = QueryResultNotifiable(resultsPointer, classKey, clazz, mediator) - override fun asFlow(): Flow> { + override fun asFlow(keyPath: List?): Flow> { return realmReference.owner - .registerObserver(this, arrayOf()) + .registerObserver(this, keyPath) } override fun delete() { diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/query/RealmElementQuery.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/query/RealmElementQuery.kt index b8aaf74de6..b2f61ede7c 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/query/RealmElementQuery.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/query/RealmElementQuery.kt @@ -60,7 +60,8 @@ public interface RealmElementQuery : Deleteable { * * **It is not allowed to call [asFlow] on queries generated from a [MutableRealm].** * + * @param keyPath TODO * @return a flow representing changes to the [RealmResults] resulting from running this query. */ - public fun asFlow(): Flow> + public fun asFlow(keyPath: List? = null): Flow> } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/query/RealmResults.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/query/RealmResults.kt index f9d7d80732..92121f5ecd 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/query/RealmResults.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/query/RealmResults.kt @@ -69,7 +69,8 @@ public interface RealmResults : List, Deleteable, Versio * the elements in a timely manner the coroutine scope will be cancelled with a * [CancellationException]. * + * @param keyPath TODO * @return a flow representing changes to the RealmResults. */ - public fun asFlow(): Flow> + public fun asFlow(keyPath: List?): Flow> } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/query/RealmSingleQuery.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/query/RealmSingleQuery.kt index 224afd2a5b..8d730f14d2 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/query/RealmSingleQuery.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/query/RealmSingleQuery.kt @@ -76,5 +76,5 @@ public interface RealmSingleQuery : Deleteable { * @return a flow representing changes to the [RealmObject] or [EmbeddedRealmObject] resulting from * running this query. */ - public fun asFlow(vararg keyPaths: String): Flow> + public fun asFlow(keyPaths: List? = null): Flow> } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmList.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmList.kt index 5dcfca308a..eacde0b351 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmList.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmList.kt @@ -72,9 +72,10 @@ public interface RealmList : MutableList, Deleteable { * the elements in a timely manner the coroutine scope will be cancelled with a * [CancellationException]. * + * @param keyPaths TODO * @return a flow representing changes to the list. * @throws CancellationException if the stream produces changes faster than the consumer can * consume them and results in a buffer overflow. */ - public fun asFlow(): Flow> + public fun asFlow(keyPaths: List?): Flow> } From 3a880cdb1fa134417715db67fd5b42a4e2f00789 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 27 Oct 2023 15:44:13 +0200 Subject: [PATCH 03/21] Actually getting something to work. Added handling of char** to String[] --- .../kotlin/internal/interop/RealmInterop.kt | 12 +++ .../kotlin/internal/interop/RealmValue.kt | 4 - .../kotlin/internal/interop/RealmInterop.kt | 17 +++++ .../kotlin/internal/interop/RealmInterop.kt | 9 ++- packages/external/core | 2 +- packages/jni-swig-stub/realm.i | 73 ++++++++++++++++++- .../src/main/jni/realm_api_helpers.cpp | 22 ++++-- .../src/main/jni/realm_api_helpers.h | 12 ++- .../io/realm/kotlin/ext/RealmDictionaryExt.kt | 4 +- .../io/realm/kotlin/ext/RealmListExt.kt | 2 +- .../kotlin/io/realm/kotlin/ext/RealmSetExt.kt | 2 +- .../io/realm/kotlin/internal/Notifiable.kt | 3 +- .../internal/ObjectBoundRealmResults.kt | 6 +- .../io/realm/kotlin/internal/RealmImpl.kt | 8 +- .../kotlin/internal/RealmListInternal.kt | 4 +- .../realm/kotlin/internal/RealmMapInternal.kt | 12 +-- .../kotlin/internal/RealmObjectReference.kt | 7 +- .../realm/kotlin/internal/RealmResultsImpl.kt | 10 ++- .../realm/kotlin/internal/RealmSetInternal.kt | 10 ++- .../kotlin/internal/SuspendableNotifier.kt | 5 +- .../internal/query/ObjectBoundQueries.kt | 8 +- .../kotlin/internal/query/ScalarQuery.kt | 6 +- .../kotlin/internal/query/SingleQuery.kt | 2 +- .../io/realm/kotlin/query/RealmResults.kt | 2 +- .../kotlin/io/realm/kotlin/types/RealmList.kt | 2 +- .../kotlin/io/realm/kotlin/types/RealmMap.kt | 3 +- .../kotlin/io/realm/kotlin/types/RealmSet.kt | 3 +- .../RealmListNotificationsTests.kt | 2 +- .../RealmObjectNotificationsTests.kt | 21 +++++- 29 files changed, 211 insertions(+), 62 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 293190fe4d..1e1a44ecd5 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 @@ -56,6 +56,7 @@ const val UUID_BYTES_SIZE = 16 interface CapiT interface RealmConfigT : CapiT interface RealmSchemaT : CapiT +interface RealmObjectSchemaT : CapiT interface RealmT : CapiT interface LiveRealmT : RealmT interface FrozenRealmT : RealmT @@ -69,6 +70,7 @@ interface RealmCallbackTokenT : CapiT interface RealmNotificationTokenT : CapiT interface RealmChangesT : CapiT interface RealmSchedulerT : CapiT +interface RealmKeyPathArrayT : CapiT // Public type aliases binding to internal verbose type safe type definitions. This should allow us // to easily change implementation details later on. @@ -88,6 +90,7 @@ typealias RealmCallbackTokenPointer = NativePointer typealias RealmNotificationTokenPointer = NativePointer typealias RealmChangesPointer = NativePointer typealias RealmSchedulerPointer = NativePointer +typealias RealmKeyPathArrayPointer = NativePointer // Sync types // Pure marker interfaces corresponding to the C-API realm_x_t struct types @@ -120,6 +123,8 @@ typealias RealmBaseSubscriptionSetPointer = NativePointer typealias RealmMutableSubscriptionSetPointer = NativePointer +typealias RealmKeyPathArray = List + /** * Class for grouping and normalizing values we want to send as part of * logging in Sync Users. @@ -436,23 +441,30 @@ expect object RealmInterop { fun realm_object_delete(obj: RealmObjectPointer) fun realm_object_add_notification_callback( + realm: RealmPointer, + clazz: ClassKey, obj: RealmObjectPointer, + keyPaths: RealmKeyPathArray?, callback: Callback ): RealmNotificationTokenPointer fun realm_results_add_notification_callback( results: RealmResultsPointer, + keyPaths: RealmKeyPathArray?, callback: Callback ): RealmNotificationTokenPointer fun realm_list_add_notification_callback( list: RealmListPointer, + keyPaths: RealmKeyPathArray?, callback: Callback ): RealmNotificationTokenPointer fun realm_set_add_notification_callback( set: RealmSetPointer, + keyPaths: RealmKeyPathArray?, callback: Callback ): RealmNotificationTokenPointer fun realm_dictionary_add_notification_callback( map: RealmMapPointer, + keyPaths: RealmKeyPathArray?, callback: Callback ): RealmNotificationTokenPointer fun realm_object_changes_get_modified_properties( diff --git a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmValue.kt b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmValue.kt index 1516f063d5..35aa518e54 100644 --- a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmValue.kt +++ b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmValue.kt @@ -63,7 +63,3 @@ expect class RealmQueryArgumentList sealed interface RealmQueryArgument class RealmQuerySingleArgument(val argument: RealmValue) : RealmQueryArgument class RealmQueryListArgument(val arguments: RealmValueList) : RealmQueryArgument - -/** - * Inline class used for handling C-API `realm_key_path_array_t` structs when registering notifications - */ 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 cd48ae8854..db7d7f89da 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 @@ -781,13 +781,22 @@ actual object RealmInterop { } actual fun realm_object_add_notification_callback( + realm: RealmPointer, + clazz: ClassKey, obj: RealmObjectPointer, + keyPaths: RealmKeyPathArray?, callback: Callback ): RealmNotificationTokenPointer { + + val keyPathPtr: Long? = keyPaths?.let { + realmc.realm_create_key_path_array(realm.cptr(), clazz.key, keyPaths.size, keyPaths.toTypedArray()) + } + return LongPointerWrapper( realmc.register_notification_cb( obj.cptr(), CollectionType.RLM_COLLECTION_TYPE_NONE.nativeValue, + keyPathPtr ?: 0, object : NotificationCallback { override fun onChange(pointer: Long) { callback.onChange(LongPointerWrapper(realmc.realm_clone(pointer), true)) @@ -800,11 +809,13 @@ actual object RealmInterop { actual fun realm_results_add_notification_callback( results: RealmResultsPointer, + keyPaths: RealmKeyPathArray?, callback: Callback ): RealmNotificationTokenPointer { return LongPointerWrapper( realmc.register_results_notification_cb( results.cptr(), + 0, object : NotificationCallback { override fun onChange(pointer: Long) { callback.onChange(LongPointerWrapper(realmc.realm_clone(pointer), true)) @@ -817,12 +828,14 @@ actual object RealmInterop { actual fun realm_list_add_notification_callback( list: RealmListPointer, + keyPaths: RealmKeyPathArray?, callback: Callback ): RealmNotificationTokenPointer { return LongPointerWrapper( realmc.register_notification_cb( list.cptr(), CollectionType.RLM_COLLECTION_TYPE_LIST.nativeValue, + 0, object : NotificationCallback { override fun onChange(pointer: Long) { callback.onChange(LongPointerWrapper(realmc.realm_clone(pointer), true)) @@ -835,12 +848,14 @@ actual object RealmInterop { actual fun realm_set_add_notification_callback( set: RealmSetPointer, + keyPaths: RealmKeyPathArray?, callback: Callback ): RealmNotificationTokenPointer { return LongPointerWrapper( realmc.register_notification_cb( set.cptr(), CollectionType.RLM_COLLECTION_TYPE_SET.nativeValue, + 0, object : NotificationCallback { override fun onChange(pointer: Long) { callback.onChange(LongPointerWrapper(realmc.realm_clone(pointer), true)) @@ -853,12 +868,14 @@ actual object RealmInterop { actual fun realm_dictionary_add_notification_callback( map: RealmMapPointer, + keyPaths: RealmKeyPathArray?, callback: Callback ): RealmNotificationTokenPointer { return LongPointerWrapper( realmc.register_notification_cb( map.cptr(), CollectionType.RLM_COLLECTION_TYPE_DICTIONARY.nativeValue, + 0, object : NotificationCallback { override fun onChange(pointer: Long) { callback.onChange(LongPointerWrapper(realmc.realm_clone(pointer), true)) 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 54feccf235..ea9085fb69 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 @@ -1636,7 +1636,10 @@ actual object RealmInterop { } actual fun realm_object_add_notification_callback( + realm: RealmPointer, + clazz: ClassKey, obj: RealmObjectPointer, + keyPaths: RealmKeyPathArray?, callback: Callback ): RealmNotificationTokenPointer { return CPointerWrapper( @@ -1649,7 +1652,7 @@ actual object RealmInterop { ?.dispose() ?: error("Notification callback data should never be null") }, - null, // See https://github.com/realm/realm-kotlin/issues/661 + null, // TODO staticCFunction { userdata, change -> // Change callback try { userdata?.asStableRef>() @@ -1670,6 +1673,7 @@ actual object RealmInterop { actual fun realm_results_add_notification_callback( results: RealmResultsPointer, + keyPaths: RealmKeyPathArray?, callback: Callback ): RealmNotificationTokenPointer { return CPointerWrapper( @@ -1703,6 +1707,7 @@ actual object RealmInterop { actual fun realm_list_add_notification_callback( list: RealmListPointer, + keyPaths: RealmKeyPathArray?, callback: Callback ): RealmNotificationTokenPointer { return CPointerWrapper( @@ -1735,6 +1740,7 @@ actual object RealmInterop { actual fun realm_set_add_notification_callback( set: RealmSetPointer, + keyPaths: RealmKeyPathArray?, callback: Callback ): RealmNotificationTokenPointer { return CPointerWrapper( @@ -1768,6 +1774,7 @@ actual object RealmInterop { actual fun realm_dictionary_add_notification_callback( map: RealmMapPointer, + keyPaths: RealmKeyPathArray?, callback: Callback ): RealmNotificationTokenPointer { return CPointerWrapper( diff --git a/packages/external/core b/packages/external/core index 3618b2e9d6..edf0369ad9 160000 --- a/packages/external/core +++ b/packages/external/core @@ -1 +1 @@ -Subproject commit 3618b2e9d679cd2880be8df17b79d4cc6d71ff76 +Subproject commit edf0369ad93dd33af6361e8f19a6ed792768ca4d diff --git a/packages/jni-swig-stub/realm.i b/packages/jni-swig-stub/realm.i index adfe7f0ccf..7e0fca83f3 100644 --- a/packages/jni-swig-stub/realm.i +++ b/packages/jni-swig-stub/realm.i @@ -301,7 +301,7 @@ return $jnicall; realm_flx_sync_mutable_subscription_set_t*, realm_flx_sync_subscription_desc_t*, realm_set_t*, realm_async_open_task_t*, realm_dictionary_t*, realm_sync_session_connection_state_notification_token_t*, - realm_dictionary_changes_t*, realm_scheduler_t* }; + realm_dictionary_changes_t*, realm_scheduler_t*, realm_key_path_array_t* }; // For all functions returning a pointer or bool, check for null/false and throw an error if // realm_get_last_error returns true. @@ -349,7 +349,6 @@ bool realm_object_is_valid(const realm_object_t*); %array_functions(realm_query_arg_t, queryArgArray); %array_functions(realm_user_identity_t, identityArray); %array_functions(realm_app_user_apikey_t, apiKeyArray); -%array_functions(realm_key_path_array_t, keyPathArray); // Work around issues with realm_size_t on Windows https://jira.mongodb.org/browse/RKOTLIN-332 %apply int64_t[] { size_t* }; @@ -393,6 +392,70 @@ bool realm_object_is_valid(const realm_object_t*); %include "enumtypeunsafe.swg" %javaconst(1); +// Add support for String[] vs char** conversion +// See https://www.swig.org/Doc4.0/Java.html#Java_converting_java_string_arrays +// Begin -- + +/* This tells SWIG to treat char ** as a special case when used as a parameter + in a function call */ +%typemap(in) char ** (jint size) { + int i = 0; + size = jenv->GetArrayLength($input); + $1 = (char **) malloc((size+1)*sizeof(char *)); + /* make a copy of each string */ + for (i = 0; iGetObjectArrayElement($input, i); + const char * c_string = jenv->GetStringUTFChars(j_string, 0); + $1[i] = (char*) malloc((strlen(c_string)+1)*sizeof(char)); + strcpy($1[i], c_string); + jenv->ReleaseStringUTFChars(j_string, c_string); + jenv->DeleteLocalRef(j_string); + } + $1[i] = 0; +} + +/* This cleans up the memory we malloc'd before the function call */ +%typemap(freearg) char ** { + int i; + for (i=0; iFindClass("java/lang/String"); + + while ($1[len]) len++; + jresult = jenv->NewObjectArray(len, clazz, NULL); + /* exception checking omitted */ + for (i=0; iNewStringUTF(*result++); + jenv->SetObjectArrayElement(jresult, i, temp_string); + jenv->DeleteLocalRef(temp_string); + } +} +// -- End + + +/* These 3 typemaps tell SWIG what JNI and Java types to use */ +%typemap(jni) char ** "jobjectArray" +%typemap(jtype) char ** "String[]" +%typemap(jstype) char ** "String[]" + +/* These 2 typemaps handle the conversion of the jtype to jstype typemap type + and vice versa */ +%typemap(javain) char ** "$javainput" +%typemap(javaout) char ** { +return $jnicall; +} + + + + // FIXME OPTIMIZE Support getting/setting multiple attributes. Ignored for now due to incorrect // type cast in Swig-generated wrapper for "const realm_property_key_t*" which is not cast @@ -426,6 +489,12 @@ bool realm_object_is_valid(const realm_object_t*); // realm_convert_with_path. %ignore realm_convert_with_path; +%ignore "realm_object_add_notification_callback"; +%ignore "realm_list_add_notification_callback"; +%ignore "realm_set_add_notification_callback"; +%ignore "realm_dictionary_add_notification_callback"; +%ignore "realm_results_add_notification_callback"; + // Swig doesn't understand __attribute__ so eliminate it #define __attribute__(x) diff --git a/packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp b/packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp index e450a510f3..bfa56cf6b9 100644 --- a/packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp +++ b/packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp @@ -143,7 +143,9 @@ bool migration_callback(void *userdata, realm_t *old_realm, realm_t *new_realm, // TODO OPTIMIZE Abstract pattern for all notification registrations for collections that receives // changes as realm_collection_changes_t. realm_notification_token_t * -register_results_notification_cb(realm_results_t *results, jobject callback) { +register_results_notification_cb(realm_results_t *results, + int64_t key_path_array_ptr, + jobject callback) { auto jenv = get_env(); static jclass notification_class = jenv->FindClass("io/realm/kotlin/internal/interop/NotificationCallback"); static jmethodID on_change_method = jenv->GetMethodID(notification_class, "onChange", "(J)V"); @@ -155,7 +157,7 @@ register_results_notification_cb(realm_results_t *results, jobject callback) { [](void *userdata) { get_env(true)->DeleteGlobalRef(static_cast(userdata)); }, - NULL, // See https://github.com/realm/realm-kotlin/issues/661 + reinterpret_cast(key_path_array_ptr), // change callback [](void *userdata, const realm_collection_changes_t *changes) { // TODO API-NOTIFICATION Consider catching errors and propagate to error callback @@ -219,8 +221,12 @@ realm_on_dictionary_change_func_t get_on_dictionary_change() { } realm_notification_token_t * -register_notification_cb(int64_t collection_ptr, realm_collection_type_e collection_type, - jobject callback) { +register_notification_cb( + int64_t collection_ptr, + realm_collection_type_e collection_type, + int64_t key_path_array_ptr, + jobject callback +) { auto user_data = static_cast(get_env()->NewGlobalRef(callback)); auto user_data_free = [](void *userdata) { get_env(true)->DeleteGlobalRef(static_cast(userdata)); @@ -232,28 +238,28 @@ register_notification_cb(int64_t collection_ptr, realm_collection_type_e collect reinterpret_cast(collection_ptr), user_data, // Use the callback as user data user_data_free, - NULL, // See https://github.com/realm/realm-kotlin/issues/661 + reinterpret_cast(key_path_array_ptr), get_on_object_change() ); case RLM_COLLECTION_TYPE_LIST: return realm_list_add_notification_callback( reinterpret_cast(collection_ptr), user_data, // Use the callback as user data user_data_free, - NULL, // See https://github.com/realm/realm-kotlin/issues/661 + reinterpret_cast(key_path_array_ptr), get_on_collection_change() ); case RLM_COLLECTION_TYPE_SET: return realm_set_add_notification_callback( reinterpret_cast(collection_ptr), user_data, // Use the callback as user data user_data_free, - NULL, // See https://github.com/realm/realm-kotlin/issues/661 + reinterpret_cast(key_path_array_ptr), get_on_collection_change() ); case RLM_COLLECTION_TYPE_DICTIONARY: return realm_dictionary_add_notification_callback( reinterpret_cast(collection_ptr), user_data, // Use the callback as user data user_data_free, - NULL, // See https://github.com/realm/realm-kotlin/issues/661 + reinterpret_cast(key_path_array_ptr), get_on_dictionary_change() ); } diff --git a/packages/jni-swig-stub/src/main/jni/realm_api_helpers.h b/packages/jni-swig-stub/src/main/jni/realm_api_helpers.h index 210202a6c9..38e1957eba 100644 --- a/packages/jni-swig-stub/src/main/jni/realm_api_helpers.h +++ b/packages/jni-swig-stub/src/main/jni/realm_api_helpers.h @@ -35,11 +35,17 @@ migration_callback(void* userdata, realm_t* old_realm, realm_t* new_realm, const realm_schema_t* schema); realm_notification_token_t* -register_results_notification_cb(realm_results_t *results, jobject callback); +register_results_notification_cb( + realm_results_t *results, + int64_t key_path_array_ptr, + jobject callback); realm_notification_token_t * -register_notification_cb(int64_t collection_ptr, realm_collection_type_e collection_type, - jobject callback); +register_notification_cb( + int64_t collection_ptr, + realm_collection_type_e collection_type, + int64_t key_path_array_ptr, + jobject callback); realm_http_transport_t* realm_network_transport_new(jobject network_transport); diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmDictionaryExt.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmDictionaryExt.kt index 3f01a2d9b1..0bdbd377e5 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmDictionaryExt.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmDictionaryExt.kt @@ -122,7 +122,7 @@ public fun RealmDictionary.query( /** * TODO */ -public fun RealmMap.asFlow(vararg keyPaths: String): Flow> { +public fun RealmMap.asFlow(keyPaths: List? = null): Flow> { if (this is ManagedRealmMap) { operator.realmReference.checkClosed() return operator.realmReference.owner.registerObserver(this, keyPaths) @@ -134,7 +134,7 @@ public fun RealmMap.asFlow(vararg keyPaths /** * TODO */ -public fun RealmDictionary.asFlow(vararg keyPaths: String): Flow> { +public fun RealmDictionary.asFlow(keyPaths: List? = null): Flow> { if (this is ManagedRealmDictionary) { operator.realmReference.checkClosed() return operator.realmReference.owner.registerObserver(this, keyPaths) diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmListExt.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmListExt.kt index 576344793b..422e2955d6 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmListExt.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmListExt.kt @@ -78,7 +78,7 @@ public fun RealmList.query( /** * TODO */ -public fun RealmList.asFlow(vararg keyPaths: String): Flow> { +public fun RealmList.asFlow(keyPaths: List? = null): Flow> { if (this is ManagedRealmList) { operator.realmReference.checkClosed() return operator.realmReference.owner.registerObserver(this, keyPaths) diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmSetExt.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmSetExt.kt index f6a81ffe0a..4110a5cdc1 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmSetExt.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmSetExt.kt @@ -78,7 +78,7 @@ public fun RealmSet.query( /** * TODO */ -public fun RealmSet.asFlow(vararg keyPaths: String): Flow> { +public fun RealmSet.asFlow(keyPaths: List? = null): Flow> { if (this is ManagedRealmSet) { operator.realmReference.checkClosed() return operator.realmReference.owner.registerObserver(this, keyPaths) 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 9f85967957..4a7f45e367 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 @@ -18,6 +18,7 @@ package io.realm.kotlin.internal import io.realm.kotlin.Versioned import io.realm.kotlin.internal.interop.Callback import io.realm.kotlin.internal.interop.RealmChangesPointer +import io.realm.kotlin.internal.interop.RealmKeyPathArray import io.realm.kotlin.internal.interop.RealmNotificationTokenPointer import io.realm.kotlin.internal.util.Validation.sdkError import io.realm.kotlin.internal.util.trySendWithBufferOverflowCheck @@ -129,7 +130,7 @@ public abstract class ChangeFlow(private val producerScope: ProducerScope< internal interface CoreNotifiable : Notifiable, Observable, Versioned, KeyPathFlowable where T : CoreNotifiable { public fun thaw(liveRealm: RealmReference): T? - public fun registerForNotification(callback: Callback): RealmNotificationTokenPointer + public fun registerForNotification(keyPaths: RealmKeyPathArray?, callback: Callback): RealmNotificationTokenPointer public fun freeze(frozenRealm: RealmReference): T? // Default implementation as all Observables are just thawing themselves. diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/ObjectBoundRealmResults.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/ObjectBoundRealmResults.kt index 6c3310831e..a7228b2c8a 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/ObjectBoundRealmResults.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/ObjectBoundRealmResults.kt @@ -33,7 +33,7 @@ import kotlinx.coroutines.flow.Flow internal class ObjectBoundRealmResults( val targetObject: RealmObjectReference<*>, val realmResults: RealmResults, -) : RealmResults by realmResults, InternalDeleteable, KeyPathFlowable> { +) : RealmResults by realmResults, InternalDeleteable { override val size: Int by realmResults::size @@ -60,8 +60,8 @@ internal class ObjectBoundRealmResults( * values, if the object has not been deleted, if not it closes cancels the flow. */ - override fun asFlow(): Flow> { - return realmResults.asFlow().bind(targetObject, keyPaths) + override fun asFlow(keyPaths: List?): Flow> { + return realmResults.asFlow(keyPaths).bind(targetObject, keyPaths) } override fun delete() { diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmImpl.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmImpl.kt index 1d47c2213e..5b156f6ebf 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmImpl.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmImpl.kt @@ -22,6 +22,7 @@ import io.realm.kotlin.Realm import io.realm.kotlin.dynamic.DynamicRealm import io.realm.kotlin.internal.dynamic.DynamicRealmImpl import io.realm.kotlin.internal.interop.RealmInterop +import io.realm.kotlin.internal.interop.RealmKeyPathArray import io.realm.kotlin.internal.interop.SynchronizableObject import io.realm.kotlin.internal.platform.copyAssetFile import io.realm.kotlin.internal.platform.fileExists @@ -52,7 +53,7 @@ import kotlin.reflect.KClass // TODO Public due to being accessed from `SyncedRealmContext` public class RealmImpl private constructor( configuration: InternalConfiguration, -) : BaseRealmImpl(configuration), Realm, InternalTypedRealm, KeyPathFlowable> { +) : BaseRealmImpl(configuration), Realm, InternalTypedRealm, SimpleFlowable> { public val notificationScheduler: LiveRealmContext = configuration.notificationDispatcherFactory.createLiveRealmContext() @@ -220,7 +221,10 @@ public class RealmImpl private constructor( ) } - override fun , C> registerObserver(t: Observable, keyPaths: Array): Flow { + override fun , C> registerObserver(t: Observable, keyPaths: List?): Flow { +// val splitKeyPath: RealmKeyPathArray? = keyPaths?.map { +// it.split(".") +// } return notifier.registerObserver(t, keyPaths) } 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 3f64f45655..53bba5038d 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.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_set_embedded +import io.realm.kotlin.internal.interop.RealmKeyPathArray import io.realm.kotlin.internal.interop.RealmListPointer import io.realm.kotlin.internal.interop.RealmNotificationTokenPointer import io.realm.kotlin.internal.interop.RealmObjectInterop @@ -131,9 +132,10 @@ internal class ManagedRealmList( } override fun registerForNotification( + keyPaths: RealmKeyPathArray?, callback: Callback ): RealmNotificationTokenPointer { - return RealmInterop.realm_list_add_notification_callback(nativePointer, callback) + return RealmInterop.realm_list_add_notification_callback(nativePointer, keyPaths, callback) } override fun changeFlow(scope: ProducerScope>): ChangeFlow, ListChange> = 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 9cad190cf1..61daa6b52a 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 @@ -31,6 +31,7 @@ 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_results_get +import io.realm.kotlin.internal.interop.RealmKeyPathArray import io.realm.kotlin.internal.interop.RealmMapPointer import io.realm.kotlin.internal.interop.RealmNotificationTokenPointer import io.realm.kotlin.internal.interop.RealmObjectInterop @@ -61,7 +62,7 @@ internal abstract class ManagedRealmMap constructor( internal val parent: RealmObjectReference<*>, internal val nativePointer: RealmMapPointer, val operator: MapOperator -) : AbstractMutableMap(), RealmMap, CoreNotifiable, MapChange>, KeyPathFlowable> { +) : AbstractMutableMap(), RealmMap, CoreNotifiable, MapChange> { private val keysPointer by lazy { RealmInterop.realm_dictionary_get_keys(nativePointer) } private val valuesPointer by lazy { RealmInterop.realm_dictionary_to_results(nativePointer) } @@ -99,15 +100,16 @@ internal abstract class ManagedRealmMap constructor( override fun remove(key: K): V? = operator.remove(key) - override fun asFlow(): Flow> { + override fun asFlow(keyPaths: List?): Flow> { operator.realmReference.checkClosed() - return operator.realmReference.owner.registerObserver(this, arrayOf()) + return operator.realmReference.owner.registerObserver(this, keyPaths) } override fun registerForNotification( + keyPaths: RealmKeyPathArray?, callback: Callback ): RealmNotificationTokenPointer = - RealmInterop.realm_dictionary_add_notification_callback(nativePointer, callback) + RealmInterop.realm_dictionary_add_notification_callback(nativePointer, keyPaths, callback) internal fun isValid(): Boolean = !nativePointer.isReleased() && RealmInterop.realm_dictionary_is_valid(nativePointer) @@ -618,7 +620,7 @@ internal class EmbeddedRealmObjectMapOperator constructo internal class UnmanagedRealmDictionary( dictionary: Map = mutableMapOf() ) : RealmDictionary, MutableMap by dictionary.toMutableMap() { - override fun asFlow(): Flow> = + override fun asFlow(keyPaths: List?): Flow> = throw UnsupportedOperationException("Unmanaged dictionaries cannot be observed.") override fun toString(): String = entries.joinToString { (key, value) -> "[$key,$value]" } 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 417d3ed393..823d7c43aa 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 @@ -20,6 +20,7 @@ import io.realm.kotlin.internal.interop.Callback import io.realm.kotlin.internal.interop.PropertyKey import io.realm.kotlin.internal.interop.RealmChangesPointer import io.realm.kotlin.internal.interop.RealmInterop +import io.realm.kotlin.internal.interop.RealmKeyPathArray import io.realm.kotlin.internal.interop.RealmNotificationTokenPointer import io.realm.kotlin.internal.interop.RealmObjectInterop import io.realm.kotlin.internal.interop.RealmObjectPointer @@ -103,10 +104,14 @@ public class RealmObjectReference( } as RealmObjectReference? } - override fun registerForNotification(callback: Callback): RealmNotificationTokenPointer { + override fun registerForNotification(keyPaths: RealmKeyPathArray?, + callback: Callback): RealmNotificationTokenPointer { // We should never get here unless it is a managed object as unmanaged doesn't support observing return RealmInterop.realm_object_add_notification_callback( + this.owner.dbPointer, + this.metadata.classKey, this.objectPointer, + keyPaths, callback ) } 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 9f925ff796..5219de810e 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 @@ -22,6 +22,7 @@ import io.realm.kotlin.internal.interop.ClassKey import io.realm.kotlin.internal.interop.RealmChangesPointer import io.realm.kotlin.internal.interop.RealmInterop import io.realm.kotlin.internal.interop.RealmInterop.realm_results_get +import io.realm.kotlin.internal.interop.RealmKeyPathArray import io.realm.kotlin.internal.interop.RealmNotificationTokenPointer import io.realm.kotlin.internal.interop.RealmResultsPointer import io.realm.kotlin.internal.interop.getterScope @@ -104,9 +105,9 @@ internal class RealmResultsImpl constructor( ) } - override fun asFlow(): Flow> { + override fun asFlow(keyPaths: List?): Flow> { realm.checkClosed() - return realm.owner.registerObserver(this, arrayOf()) + return realm.owner.registerObserver(this, keyPaths) } override fun delete() { @@ -134,8 +135,9 @@ internal class RealmResultsImpl constructor( return RealmResultsImpl(liveRealm, liveResultPtr, classKey, clazz, mediator) } - override fun registerForNotification(callback: Callback): RealmNotificationTokenPointer { - return RealmInterop.realm_results_add_notification_callback(nativePointer, callback) + override fun registerForNotification(keyPaths: RealmKeyPathArray?, + callback: Callback): RealmNotificationTokenPointer { + return RealmInterop.realm_results_add_notification_callback(nativePointer, keyPaths, callback) } override fun changeFlow(scope: ProducerScope>): ChangeFlow, ResultsChange> = 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 c842a68ac8..d1224bba06 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 @@ -24,6 +24,7 @@ import io.realm.kotlin.internal.interop.ClassKey import io.realm.kotlin.internal.interop.RealmChangesPointer import io.realm.kotlin.internal.interop.RealmInterop import io.realm.kotlin.internal.interop.RealmInterop.realm_set_get +import io.realm.kotlin.internal.interop.RealmKeyPathArray import io.realm.kotlin.internal.interop.RealmNotificationTokenPointer import io.realm.kotlin.internal.interop.RealmObjectInterop import io.realm.kotlin.internal.interop.RealmSetPointer @@ -49,7 +50,7 @@ import kotlin.reflect.KClass internal class UnmanagedRealmSet( private val backingSet: MutableSet = mutableSetOf() ) : RealmSet, InternalDeleteable, MutableSet by backingSet { - override fun asFlow(): Flow> { + override fun asFlow(keyPaths: List?): Flow> { throw UnsupportedOperationException("Unmanaged sets cannot be observed.") } @@ -159,8 +160,8 @@ internal class ManagedRealmSet constructor( } } - override fun asFlow(): Flow> { - return operator.realmReference.owner.registerObserver(this, arrayOf()) + override fun asFlow(keyPaths: List?): Flow> { + return operator.realmReference.owner.registerObserver(this, keyPaths) } override fun freeze(frozenRealm: RealmReference): ManagedRealmSet? { @@ -176,9 +177,10 @@ internal class ManagedRealmSet constructor( } override fun registerForNotification( + keyPaths: RealmKeyPathArray?, callback: Callback ): RealmNotificationTokenPointer { - return RealmInterop.realm_set_add_notification_callback(nativePointer, callback) + return RealmInterop.realm_set_add_notification_callback(nativePointer, keyPaths, callback) } override fun changeFlow(scope: ProducerScope>): ChangeFlow, SetChange> = 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 bcee667dea..ef764e3a0d 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 @@ -4,6 +4,7 @@ import io.realm.kotlin.VersionId import io.realm.kotlin.internal.interop.Callback import io.realm.kotlin.internal.interop.RealmChangesPointer import io.realm.kotlin.internal.interop.RealmInterop +import io.realm.kotlin.internal.interop.RealmKeyPathArray import io.realm.kotlin.internal.platform.runBlocking import io.realm.kotlin.internal.schema.RealmSchemaImpl import io.realm.kotlin.internal.util.LiveRealmContext @@ -93,7 +94,7 @@ internal class SuspendableNotifier( return _realmChanged.asSharedFlow() } - internal fun , C> registerObserver(flowable: Observable, keyPaths: Array): Flow { + internal fun , C> registerObserver(flowable: Observable, keyPaths: RealmKeyPathArray?): Flow { return callbackFlow { val token: AtomicRef = kotlinx.atomicfu.atomic(NO_OP_NOTIFICATION_TOKEN) @@ -122,7 +123,7 @@ internal class SuspendableNotifier( changeFlow.emit(frozenObservable, change) } } - token.value = NotificationToken(lifeRef.registerForNotification(interopCallback)) + token.value = NotificationToken(lifeRef.registerForNotification(keyPaths, interopCallback)) } else { changeFlow.emit(null) } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/ObjectBoundQueries.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/ObjectBoundQueries.kt index 6ac0258867..97b07ed053 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/ObjectBoundQueries.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/ObjectBoundQueries.kt @@ -49,7 +49,7 @@ internal class ObjectBoundQuery( ) // TODO - override fun asFlow(keyPath: List?): Flow> = realmQuery.asFlow().bind( + override fun asFlow(keyPaths: List?): Flow> = realmQuery.asFlow().bind( targetObject, keyPaths ) @@ -118,12 +118,14 @@ internal class ObjectBoundRealmScalarNullableQuery( val targetObject: RealmObjectReference<*>, val realmQuery: RealmScalarNullableQuery ) : RealmScalarNullableQuery by realmQuery { - override fun asFlow(keyPaths: List?): Flow = realmQuery.asFlow().bind(targetObject, keyPaths) + // TODO is it correct to set keyPaths to null? + override fun asFlow(): Flow = realmQuery.asFlow().bind(targetObject, null) } internal class ObjectBoundRealmScalarQuery( val targetObject: RealmObjectReference<*>, val realmQuery: RealmScalarQuery ) : RealmScalarQuery by realmQuery { - override fun asFlow(): Flow = realmQuery.asFlow().bind(targetObject, keyPaths) + // TODO is it correct to set keyPaths to null? + override fun asFlow(): Flow = realmQuery.asFlow().bind(targetObject, null) } 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..bba1c4f58f 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 @@ -96,7 +96,7 @@ internal class CountQuery constructor( override fun asFlow(): Flow { realmReference.checkClosed() return realmReference.owner - .registerObserver(this) + .registerObserver(this, null) .map { it.list.size.toLong() }.distinctUntilChanged() @@ -148,7 +148,7 @@ internal class MinMaxQuery constructor( override fun asFlow(): Flow { realmReference.checkClosed() return realmReference.owner - .registerObserver(this) + .registerObserver(this, null) .map { val realmResults = it.list as RealmResultsImpl<*> findFromResults(realmResults.nativePointer, realmResults.realm) @@ -216,7 +216,7 @@ internal class SumQuery constructor( override fun asFlow(): Flow { realmReference.checkClosed() return realmReference.owner - .registerObserver(this) + .registerObserver(this, null) .map { findFromResults((it.list as RealmResultsImpl<*>).nativePointer) } .distinctUntilChanged() } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/SingleQuery.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/SingleQuery.kt index a6a3ec0ae4..40c231ce10 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/SingleQuery.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/SingleQuery.kt @@ -56,7 +56,7 @@ internal class SingleQuery constructor( * new head if any. * If there is an update, we ignore it, as the object flow would automatically emit the event. */ - override fun asFlow(vararg keyPaths: String): Flow> { + override fun asFlow(keyPaths: List?): Flow> { var oldHead: E? = null return realmReference.owner.registerObserver(this, keyPaths) // Convert into flow of result head diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/query/RealmResults.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/query/RealmResults.kt index 92121f5ecd..2da94c8938 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/query/RealmResults.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/query/RealmResults.kt @@ -72,5 +72,5 @@ public interface RealmResults : List, Deleteable, Versio * @param keyPath TODO * @return a flow representing changes to the RealmResults. */ - public fun asFlow(keyPath: List?): Flow> + public fun asFlow(keyPaths: List? = null): Flow> } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmList.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmList.kt index eacde0b351..c82a1acb8c 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmList.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmList.kt @@ -77,5 +77,5 @@ public interface RealmList : MutableList, Deleteable { * @throws CancellationException if the stream produces changes faster than the consumer can * consume them and results in a buffer overflow. */ - public fun asFlow(keyPaths: List?): Flow> + public fun asFlow(keyPaths: List? = null): Flow> } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmMap.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmMap.kt index 2e45c4598b..672b67104f 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmMap.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmMap.kt @@ -55,11 +55,12 @@ public interface RealmMap : MutableMap { * the elements in a timely manner the coroutine scope will be cancelled with a * [CancellationException]. * + * @param keyPaths TODO * @return a flow representing changes to the dictionary. * @throws CancellationException if the stream produces changes faster than the consumer can * consume them and results in a buffer overflow. */ - public fun asFlow(): Flow> + public fun asFlow(keyPaths: List? = null): Flow> } /** diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmSet.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmSet.kt index d7366f453e..bfc140b450 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmSet.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmSet.kt @@ -52,9 +52,10 @@ public interface RealmSet : MutableSet, Deleteable { * the elements in a timely manner the coroutine scope will be cancelled with a * [CancellationException]. * + * @param keyPaths TODO Keypaths only make sense for sets with objects? Should we capture this in the type system? * @return a flow representing changes to the set. * @throws CancellationException if the stream produces changes faster than the consumer can * consume them and results in a buffer overflow. */ - public fun asFlow(): Flow> + public fun asFlow(keyPaths: List? = null): Flow> } 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 8ab8d6199a..d38eb09c61 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 @@ -124,7 +124,7 @@ class RealmListNotificationsTests : RealmEntityNotificationTests { val channel = Channel>(capacity = 1) val observer = async { container.objectListField - .asFlow("*") + .asFlow() .collect { flowList -> channel.send(flowList) } 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 dd20740b59..ff35bf9efd 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 @@ -306,26 +306,39 @@ class RealmObjectNotificationsTests : RealmEntityNotificationTests { fun keyPath_allTopLevelProperties() = runBlocking { val c = Channel>(1) val obj: Sample = realm.write { - copyToRealm(Sample().apply { stringField = "Foo" }) + copyToRealm(Sample()) } val observer = async { - obj.asFlow("*").collect { + obj.asFlow(listOf("stringField")).collect { c.trySend(it) } fail("Flow should not be canceled.") } c.receiveOrFail().let { objectChange -> assertIs>(objectChange) - assertEquals("Foo", objectChange.obj.stringField) + } + realm.write { + findLatest(obj)!!.nullableStringField = "Bar" + } + c.receiveOrFail().let { objectChange -> + assertIs>(objectChange) + when(objectChange) { + is UpdatedObject -> { + assertEquals(1, objectChange.changedFields.size) + assertEquals("stringField", objectChange.changedFields.first()) + } + else -> TODO() + } + assertEquals("Bar", objectChange.obj.stringField) } realm.close() observer.cancel() c.close() + Unit } @Test fun keyPath_singleTopLevelProperty() { } - } From 81bd9392ec2406c60250bc1b172b0874fd6ba908 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Sun, 5 Nov 2023 20:47:15 +0100 Subject: [PATCH 04/21] Add first step of tests --- .../kotlin/internal/interop/RealmInterop.kt | 13 +- .../kotlin/internal/interop/RealmInterop.kt | 34 +++-- .../kotlin/internal/interop/RealmInterop.kt | 16 +- packages/external/core | 2 +- .../src/main/jni/realm_api_helpers.cpp | 21 ++- .../src/main/jni/realm_api_helpers.h | 7 + .../io/realm/kotlin/ext/BaseRealmObjectExt.kt | 3 +- .../io/realm/kotlin/ext/RealmDictionaryExt.kt | 7 +- .../io/realm/kotlin/ext/RealmListExt.kt | 4 +- .../kotlin/io/realm/kotlin/ext/RealmSetExt.kt | 4 +- .../io/realm/kotlin/internal/BaseRealmImpl.kt | 3 +- .../io/realm/kotlin/internal/Notifiable.kt | 3 +- .../io/realm/kotlin/internal/RealmImpl.kt | 6 +- .../kotlin/internal/RealmListInternal.kt | 6 +- .../realm/kotlin/internal/RealmMapInternal.kt | 6 +- .../kotlin/internal/RealmObjectReference.kt | 9 +- .../realm/kotlin/internal/RealmResultsImpl.kt | 6 +- .../realm/kotlin/internal/RealmSetInternal.kt | 7 +- .../kotlin/internal/SuspendableNotifier.kt | 9 +- .../kotlin/internal/query/ObjectQuery.kt | 3 +- .../kotlin/internal/query/ScalarQuery.kt | 9 +- .../kotlin/internal/query/SingleQuery.kt | 3 +- .../BacklinksNotificationsTests.kt | 30 ++++ .../RealmDictionaryNotificationsTests.kt | 30 ++++ .../RealmListNotificationsTests.kt | 30 ++++ .../notifications/RealmNotificationsTests.kt | 40 ++++- .../RealmObjectNotificationsTests.kt | 139 ++++++++++++++++-- .../RealmResultsNotificationsTests.kt | 30 ++++ .../RealmSetNotificationsTests.kt | 30 ++++ .../kotlin/test/common/utils/FlowableTests.kt | 18 +++ 30 files changed, 451 insertions(+), 77 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 1e1a44ecd5..23f470fc62 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 @@ -440,31 +440,30 @@ expect object RealmInterop { ): RealmObjectPointer? fun realm_object_delete(obj: RealmObjectPointer) + fun create_key_paths_array(realm: RealmPointer, clazz: ClassKey, keyPaths: RealmKeyPathArray?): RealmKeyPathArrayPointer? fun realm_object_add_notification_callback( - realm: RealmPointer, - clazz: ClassKey, obj: RealmObjectPointer, - keyPaths: RealmKeyPathArray?, + keyPaths: RealmKeyPathArrayPointer?, callback: Callback ): RealmNotificationTokenPointer fun realm_results_add_notification_callback( results: RealmResultsPointer, - keyPaths: RealmKeyPathArray?, + keyPaths: RealmKeyPathArrayPointer?, callback: Callback ): RealmNotificationTokenPointer fun realm_list_add_notification_callback( list: RealmListPointer, - keyPaths: RealmKeyPathArray?, + keyPaths: RealmKeyPathArrayPointer?, callback: Callback ): RealmNotificationTokenPointer fun realm_set_add_notification_callback( set: RealmSetPointer, - keyPaths: RealmKeyPathArray?, + keyPaths: RealmKeyPathArrayPointer?, callback: Callback ): RealmNotificationTokenPointer fun realm_dictionary_add_notification_callback( map: RealmMapPointer, - keyPaths: RealmKeyPathArray?, + keyPaths: RealmKeyPathArrayPointer?, callback: Callback ): RealmNotificationTokenPointer fun realm_object_changes_get_modified_properties( 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 db7d7f89da..9e3978bfa5 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 @@ -780,23 +781,24 @@ actual object RealmInterop { return realmc.realm_dictionary_is_valid(dictionary.cptr()) } + actual fun create_key_paths_array(realm: RealmPointer, clazz: ClassKey, keyPaths: RealmKeyPathArray?): RealmKeyPathArrayPointer? { + return keyPaths?.let { + val ptr = realmc.jni_realm_create_key_path_array(realm.cptr(), clazz.key, keyPaths.size, keyPaths.toTypedArray()) + return LongPointerWrapper(ptr) + } + } + actual fun realm_object_add_notification_callback( - realm: RealmPointer, - clazz: ClassKey, obj: RealmObjectPointer, - keyPaths: RealmKeyPathArray?, + keyPaths: RealmKeyPathArrayPointer?, callback: Callback ): RealmNotificationTokenPointer { - val keyPathPtr: Long? = keyPaths?.let { - realmc.realm_create_key_path_array(realm.cptr(), clazz.key, keyPaths.size, keyPaths.toTypedArray()) - } - return LongPointerWrapper( realmc.register_notification_cb( obj.cptr(), CollectionType.RLM_COLLECTION_TYPE_NONE.nativeValue, - keyPathPtr ?: 0, + keyPaths?.cptr() ?: 0, object : NotificationCallback { override fun onChange(pointer: Long) { callback.onChange(LongPointerWrapper(realmc.realm_clone(pointer), true)) @@ -809,13 +811,13 @@ actual object RealmInterop { actual fun realm_results_add_notification_callback( results: RealmResultsPointer, - keyPaths: RealmKeyPathArray?, + keyPaths: RealmKeyPathArrayPointer?, callback: Callback ): RealmNotificationTokenPointer { return LongPointerWrapper( realmc.register_results_notification_cb( results.cptr(), - 0, + keyPaths?.cptr() ?: 0, object : NotificationCallback { override fun onChange(pointer: Long) { callback.onChange(LongPointerWrapper(realmc.realm_clone(pointer), true)) @@ -828,14 +830,14 @@ actual object RealmInterop { actual fun realm_list_add_notification_callback( list: RealmListPointer, - keyPaths: RealmKeyPathArray?, + keyPaths: RealmKeyPathArrayPointer?, callback: Callback ): RealmNotificationTokenPointer { return LongPointerWrapper( realmc.register_notification_cb( list.cptr(), CollectionType.RLM_COLLECTION_TYPE_LIST.nativeValue, - 0, + keyPaths?.cptr() ?: 0, object : NotificationCallback { override fun onChange(pointer: Long) { callback.onChange(LongPointerWrapper(realmc.realm_clone(pointer), true)) @@ -848,14 +850,14 @@ actual object RealmInterop { actual fun realm_set_add_notification_callback( set: RealmSetPointer, - keyPaths: RealmKeyPathArray?, + keyPaths: RealmKeyPathArrayPointer?, callback: Callback ): RealmNotificationTokenPointer { return LongPointerWrapper( realmc.register_notification_cb( set.cptr(), CollectionType.RLM_COLLECTION_TYPE_SET.nativeValue, - 0, + keyPaths?.cptr() ?: 0, object : NotificationCallback { override fun onChange(pointer: Long) { callback.onChange(LongPointerWrapper(realmc.realm_clone(pointer), true)) @@ -868,14 +870,14 @@ actual object RealmInterop { actual fun realm_dictionary_add_notification_callback( map: RealmMapPointer, - keyPaths: RealmKeyPathArray?, + keyPaths: RealmKeyPathArrayPointer?, callback: Callback ): RealmNotificationTokenPointer { return LongPointerWrapper( realmc.register_notification_cb( map.cptr(), CollectionType.RLM_COLLECTION_TYPE_DICTIONARY.nativeValue, - 0, + keyPaths?.cptr() ?: 0, object : NotificationCallback { override fun onChange(pointer: Long) { callback.onChange(LongPointerWrapper(realmc.realm_clone(pointer), true)) 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 ea9085fb69..44de95a554 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 @@ -1635,11 +1635,13 @@ actual object RealmInterop { checkedBooleanResult(realm_wrapper.realm_object_delete(obj.cptr())) } + actual fun create_key_paths_array(realm: RealmPointer, clazz: ClassKey, keyPaths: RealmKeyPathArray?): RealmKeyPathArrayPointer? { + TODO() + } + actual fun realm_object_add_notification_callback( - realm: RealmPointer, - clazz: ClassKey, obj: RealmObjectPointer, - keyPaths: RealmKeyPathArray?, + keyPaths: RealmKeyPathArrayPointer?, callback: Callback ): RealmNotificationTokenPointer { return CPointerWrapper( @@ -1673,7 +1675,7 @@ actual object RealmInterop { actual fun realm_results_add_notification_callback( results: RealmResultsPointer, - keyPaths: RealmKeyPathArray?, + keyPaths: RealmKeyPathArrayPointer?, callback: Callback ): RealmNotificationTokenPointer { return CPointerWrapper( @@ -1707,7 +1709,7 @@ actual object RealmInterop { actual fun realm_list_add_notification_callback( list: RealmListPointer, - keyPaths: RealmKeyPathArray?, + keyPaths: RealmKeyPathArrayPointer?, callback: Callback ): RealmNotificationTokenPointer { return CPointerWrapper( @@ -1740,7 +1742,7 @@ actual object RealmInterop { actual fun realm_set_add_notification_callback( set: RealmSetPointer, - keyPaths: RealmKeyPathArray?, + keyPaths: RealmKeyPathArrayPointer?, callback: Callback ): RealmNotificationTokenPointer { return CPointerWrapper( @@ -1774,7 +1776,7 @@ actual object RealmInterop { actual fun realm_dictionary_add_notification_callback( map: RealmMapPointer, - keyPaths: RealmKeyPathArray?, + keyPaths: RealmKeyPathArrayPointer?, callback: Callback ): RealmNotificationTokenPointer { return CPointerWrapper( diff --git a/packages/external/core b/packages/external/core index edf0369ad9..df70e2a69d 160000 --- a/packages/external/core +++ b/packages/external/core @@ -1 +1 @@ -Subproject commit edf0369ad93dd33af6361e8f19a6ed792768ca4d +Subproject commit df70e2a69dd2a8d1af2528e57eafa4eb201b9d9e diff --git a/packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp b/packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp index bfa56cf6b9..c0b0f886c1 100644 --- a/packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp +++ b/packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp @@ -238,7 +238,7 @@ register_notification_cb( reinterpret_cast(collection_ptr), user_data, // Use the callback as user data user_data_free, - reinterpret_cast(key_path_array_ptr), + (key_path_array_ptr == 0) ? NULL : reinterpret_cast(key_path_array_ptr), get_on_object_change() ); case RLM_COLLECTION_TYPE_LIST: return realm_list_add_notification_callback( @@ -1060,3 +1060,22 @@ realm_scheduler_t* realm_create_generic_scheduler() { return new realm_scheduler_t { realm::util::Scheduler::make_dummy() }; } + +realm_key_path_array_t* +jni_realm_create_key_path_array(const realm_t* realm, + const realm_class_key_t object_class_key, + int user_key_paths_count, + const char** user_key_paths) +{ + realm_key_path_array_t* out = nullptr; + bool result = realm_create_key_path_array(realm, object_class_key, user_key_paths_count, user_key_paths, &out); + if (result) { + return out; + } else { + delete(out); + auto env = get_env(); + throw_last_error_as_java_exception(env); + return nullptr; + } +} + diff --git a/packages/jni-swig-stub/src/main/jni/realm_api_helpers.h b/packages/jni-swig-stub/src/main/jni/realm_api_helpers.h index 38e1957eba..eaf5b16bf4 100644 --- a/packages/jni-swig-stub/src/main/jni/realm_api_helpers.h +++ b/packages/jni-swig-stub/src/main/jni/realm_api_helpers.h @@ -143,4 +143,11 @@ realm_sync_thread_error(realm_userdata_t userdata, const char* error); realm_scheduler_t* realm_create_generic_scheduler(); +realm_key_path_array_t* +jni_realm_create_key_path_array(const realm_t* realm, + const realm_class_key_t object_class_key, + int user_key_paths_count, + const char** user_key_paths +); + #endif //TEST_REALM_API_HELPERS_H diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/BaseRealmObjectExt.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/BaseRealmObjectExt.kt index 493c6536a7..e67c3a4e88 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/BaseRealmObjectExt.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/BaseRealmObjectExt.kt @@ -21,6 +21,7 @@ import io.realm.kotlin.VersionId import io.realm.kotlin.dynamic.DynamicRealmObject import io.realm.kotlin.internal.UnmanagedState import io.realm.kotlin.internal.checkNotificationsAvailable +import io.realm.kotlin.internal.interop.ClassKey import io.realm.kotlin.internal.interop.RealmInterop import io.realm.kotlin.internal.realmObjectReference import io.realm.kotlin.internal.runIfManaged @@ -91,5 +92,5 @@ public fun BaseRealmObject.isValid(): Boolean = runIfManaged { */ public fun T.asFlow(keyPaths: List? = null): Flow> = runIfManaged { checkNotificationsAvailable() - return owner.owner.registerObserver(this, keyPaths) as Flow> + return owner.owner.registerObserver(this, Pair(this.metadata.classKey, keyPaths)) as Flow> } ?: throw IllegalStateException("Changes cannot be observed on unmanaged objects.") diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmDictionaryExt.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmDictionaryExt.kt index 0bdbd377e5..4586e9c850 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmDictionaryExt.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmDictionaryExt.kt @@ -24,6 +24,7 @@ import io.realm.kotlin.internal.RealmMapMutableEntry import io.realm.kotlin.internal.UnmanagedRealmDictionary import io.realm.kotlin.internal.asRealmDictionary import io.realm.kotlin.internal.getRealm +import io.realm.kotlin.internal.interop.ClassKey import io.realm.kotlin.internal.query import io.realm.kotlin.internal.realmMapEntryOf import io.realm.kotlin.notifications.ListChange @@ -125,7 +126,8 @@ public fun RealmDictionary.query( public fun RealmMap.asFlow(keyPaths: List? = null): Flow> { if (this is ManagedRealmMap) { operator.realmReference.checkClosed() - return operator.realmReference.owner.registerObserver(this, keyPaths) + // TODO Find Class Key + return operator.realmReference.owner.registerObserver(this, Pair(ClassKey(0), keyPaths)) } else { throw UnsupportedOperationException("Unmanaged maps cannot be observed.") } @@ -137,7 +139,8 @@ public fun RealmMap.asFlow(keyPaths: List< public fun RealmDictionary.asFlow(keyPaths: List? = null): Flow> { if (this is ManagedRealmDictionary) { operator.realmReference.checkClosed() - return operator.realmReference.owner.registerObserver(this, keyPaths) + // TODO Find Class Key + return operator.realmReference.owner.registerObserver(this, Pair(ClassKey(0), keyPaths)) } else { throw UnsupportedOperationException("Unmanaged dictionaries cannot be observed.") } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmListExt.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmListExt.kt index 422e2955d6..ce75d4f808 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmListExt.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmListExt.kt @@ -21,6 +21,7 @@ import io.realm.kotlin.internal.ManagedRealmList import io.realm.kotlin.internal.UnmanagedRealmList import io.realm.kotlin.internal.asRealmList import io.realm.kotlin.internal.getRealm +import io.realm.kotlin.internal.interop.ClassKey import io.realm.kotlin.internal.query import io.realm.kotlin.notifications.ListChange import io.realm.kotlin.query.RealmQuery @@ -81,7 +82,8 @@ public fun RealmList.query( public fun RealmList.asFlow(keyPaths: List? = null): Flow> { if (this is ManagedRealmList) { operator.realmReference.checkClosed() - return operator.realmReference.owner.registerObserver(this, keyPaths) + // TODO + return operator.realmReference.owner.registerObserver(this, Pair(ClassKey(0), keyPaths)) } else { throw UnsupportedOperationException("Unmanaged lists cannot be observed.") } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmSetExt.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmSetExt.kt index 4110a5cdc1..1ac1e6ef01 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmSetExt.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmSetExt.kt @@ -22,6 +22,7 @@ import io.realm.kotlin.internal.ManagedRealmSet import io.realm.kotlin.internal.UnmanagedRealmSet import io.realm.kotlin.internal.asRealmSet import io.realm.kotlin.internal.getRealm +import io.realm.kotlin.internal.interop.ClassKey import io.realm.kotlin.internal.query import io.realm.kotlin.notifications.ListChange import io.realm.kotlin.notifications.SetChange @@ -81,7 +82,8 @@ public fun RealmSet.query( public fun RealmSet.asFlow(keyPaths: List? = null): Flow> { if (this is ManagedRealmSet) { operator.realmReference.checkClosed() - return operator.realmReference.owner.registerObserver(this, keyPaths) + // TODO + return operator.realmReference.owner.registerObserver(this, Pair(ClassKey(0), keyPaths)) } else { throw UnsupportedOperationException("Unmanaged sets cannot be observed.") } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/BaseRealmImpl.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/BaseRealmImpl.kt index c0d7276a3f..35f3508872 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/BaseRealmImpl.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/BaseRealmImpl.kt @@ -16,6 +16,7 @@ package io.realm.kotlin.internal import io.realm.kotlin.BaseRealm +import io.realm.kotlin.internal.interop.ClassKey import io.realm.kotlin.internal.interop.RealmInterop import io.realm.kotlin.notifications.internal.Callback import io.realm.kotlin.notifications.internal.Cancellable @@ -64,7 +65,7 @@ public abstract class BaseRealmImpl internal constructor( return RealmInterop.realm_get_schema_version(realmReference.dbPointer) } - internal open fun , C> registerObserver(t: Observable, keyPaths: List?): Flow { + internal open fun , C> registerObserver(t: Observable, keyPaths: Pair?>): Flow { throw UnsupportedOperationException(OBSERVABLE_NOT_SUPPORTED_MESSAGE) } 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 4a7f45e367..ab0cf3469f 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 @@ -19,6 +19,7 @@ import io.realm.kotlin.Versioned import io.realm.kotlin.internal.interop.Callback import io.realm.kotlin.internal.interop.RealmChangesPointer import io.realm.kotlin.internal.interop.RealmKeyPathArray +import io.realm.kotlin.internal.interop.RealmKeyPathArrayPointer import io.realm.kotlin.internal.interop.RealmNotificationTokenPointer import io.realm.kotlin.internal.util.Validation.sdkError import io.realm.kotlin.internal.util.trySendWithBufferOverflowCheck @@ -130,7 +131,7 @@ public abstract class ChangeFlow(private val producerScope: ProducerScope< internal interface CoreNotifiable : Notifiable, Observable, Versioned, KeyPathFlowable where T : CoreNotifiable { public fun thaw(liveRealm: RealmReference): T? - public fun registerForNotification(keyPaths: RealmKeyPathArray?, callback: Callback): RealmNotificationTokenPointer + public fun registerForNotification(keyPaths: RealmKeyPathArrayPointer?, callback: Callback): RealmNotificationTokenPointer public fun freeze(frozenRealm: RealmReference): T? // Default implementation as all Observables are just thawing themselves. diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmImpl.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmImpl.kt index 5b156f6ebf..bec7e7d37e 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmImpl.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmImpl.kt @@ -21,6 +21,7 @@ import io.realm.kotlin.MutableRealm import io.realm.kotlin.Realm import io.realm.kotlin.dynamic.DynamicRealm import io.realm.kotlin.internal.dynamic.DynamicRealmImpl +import io.realm.kotlin.internal.interop.ClassKey import io.realm.kotlin.internal.interop.RealmInterop import io.realm.kotlin.internal.interop.RealmKeyPathArray import io.realm.kotlin.internal.interop.SynchronizableObject @@ -221,10 +222,7 @@ public class RealmImpl private constructor( ) } - override fun , C> registerObserver(t: Observable, keyPaths: List?): Flow { -// val splitKeyPath: RealmKeyPathArray? = keyPaths?.map { -// it.split(".") -// } + override fun , C> registerObserver(t: Observable, keyPaths: Pair?>): Flow { return notifier.registerObserver(t, keyPaths) } 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 53bba5038d..299caf4f7a 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 @@ -26,6 +26,7 @@ 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_set_embedded import io.realm.kotlin.internal.interop.RealmKeyPathArray +import io.realm.kotlin.internal.interop.RealmKeyPathArrayPointer import io.realm.kotlin.internal.interop.RealmListPointer import io.realm.kotlin.internal.interop.RealmNotificationTokenPointer import io.realm.kotlin.internal.interop.RealmObjectInterop @@ -116,7 +117,8 @@ internal class ManagedRealmList( override fun asFlow(keyPaths: List?): Flow> { operator.realmReference.checkClosed() - return operator.realmReference.owner.registerObserver(this, keyPaths) + // TODO + return operator.realmReference.owner.registerObserver(this, Pair(ClassKey(0), keyPaths)) } override fun freeze(frozenRealm: RealmReference): ManagedRealmList? { @@ -132,7 +134,7 @@ internal class ManagedRealmList( } override fun registerForNotification( - keyPaths: RealmKeyPathArray?, + keyPaths: RealmKeyPathArrayPointer?, callback: Callback ): RealmNotificationTokenPointer { return RealmInterop.realm_list_add_notification_callback(nativePointer, keyPaths, callback) 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 61daa6b52a..5124246fa0 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 @@ -32,6 +32,7 @@ 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_results_get import io.realm.kotlin.internal.interop.RealmKeyPathArray +import io.realm.kotlin.internal.interop.RealmKeyPathArrayPointer import io.realm.kotlin.internal.interop.RealmMapPointer import io.realm.kotlin.internal.interop.RealmNotificationTokenPointer import io.realm.kotlin.internal.interop.RealmObjectInterop @@ -102,11 +103,12 @@ internal abstract class ManagedRealmMap constructor( override fun asFlow(keyPaths: List?): Flow> { operator.realmReference.checkClosed() - return operator.realmReference.owner.registerObserver(this, keyPaths) + // TODO + return operator.realmReference.owner.registerObserver(this, Pair(ClassKey(0), keyPaths)) } override fun registerForNotification( - keyPaths: RealmKeyPathArray?, + keyPaths: RealmKeyPathArrayPointer?, callback: Callback ): RealmNotificationTokenPointer = RealmInterop.realm_dictionary_add_notification_callback(nativePointer, keyPaths, callback) 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 823d7c43aa..5fc259fcee 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 @@ -17,10 +17,12 @@ package io.realm.kotlin.internal import io.realm.kotlin.internal.interop.Callback +import io.realm.kotlin.internal.interop.ClassKey import io.realm.kotlin.internal.interop.PropertyKey import io.realm.kotlin.internal.interop.RealmChangesPointer import io.realm.kotlin.internal.interop.RealmInterop import io.realm.kotlin.internal.interop.RealmKeyPathArray +import io.realm.kotlin.internal.interop.RealmKeyPathArrayPointer import io.realm.kotlin.internal.interop.RealmNotificationTokenPointer import io.realm.kotlin.internal.interop.RealmObjectInterop import io.realm.kotlin.internal.interop.RealmObjectPointer @@ -104,12 +106,10 @@ public class RealmObjectReference( } as RealmObjectReference? } - override fun registerForNotification(keyPaths: RealmKeyPathArray?, + override fun registerForNotification(keyPaths: RealmKeyPathArrayPointer?, callback: Callback): RealmNotificationTokenPointer { // We should never get here unless it is a managed object as unmanaged doesn't support observing return RealmInterop.realm_object_add_notification_callback( - this.owner.dbPointer, - this.metadata.classKey, this.objectPointer, keyPaths, callback @@ -130,7 +130,8 @@ public class RealmObjectReference( } override fun asFlow(keyPath: List?): Flow> { - return this.owner.owner.registerObserver(this, keyPath) + // TODO + return this.owner.owner.registerObserver(this, Pair(ClassKey(0), keyPath)) } override fun delete() { 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 5219de810e..112d6cead8 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 @@ -23,6 +23,7 @@ import io.realm.kotlin.internal.interop.RealmChangesPointer import io.realm.kotlin.internal.interop.RealmInterop import io.realm.kotlin.internal.interop.RealmInterop.realm_results_get import io.realm.kotlin.internal.interop.RealmKeyPathArray +import io.realm.kotlin.internal.interop.RealmKeyPathArrayPointer import io.realm.kotlin.internal.interop.RealmNotificationTokenPointer import io.realm.kotlin.internal.interop.RealmResultsPointer import io.realm.kotlin.internal.interop.getterScope @@ -107,7 +108,8 @@ internal class RealmResultsImpl constructor( override fun asFlow(keyPaths: List?): Flow> { realm.checkClosed() - return realm.owner.registerObserver(this, keyPaths) + // TODO + return realm.owner.registerObserver(this, Pair(ClassKey(0), keyPaths)) } override fun delete() { @@ -135,7 +137,7 @@ internal class RealmResultsImpl constructor( return RealmResultsImpl(liveRealm, liveResultPtr, classKey, clazz, mediator) } - override fun registerForNotification(keyPaths: RealmKeyPathArray?, + override fun registerForNotification(keyPaths: RealmKeyPathArrayPointer?, callback: Callback): RealmNotificationTokenPointer { return RealmInterop.realm_results_add_notification_callback(nativePointer, keyPaths, callback) } 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 d1224bba06..5046fb729d 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 @@ -24,7 +24,7 @@ import io.realm.kotlin.internal.interop.ClassKey import io.realm.kotlin.internal.interop.RealmChangesPointer import io.realm.kotlin.internal.interop.RealmInterop import io.realm.kotlin.internal.interop.RealmInterop.realm_set_get -import io.realm.kotlin.internal.interop.RealmKeyPathArray +import io.realm.kotlin.internal.interop.RealmKeyPathArrayPointer import io.realm.kotlin.internal.interop.RealmNotificationTokenPointer import io.realm.kotlin.internal.interop.RealmObjectInterop import io.realm.kotlin.internal.interop.RealmSetPointer @@ -161,7 +161,8 @@ internal class ManagedRealmSet constructor( } override fun asFlow(keyPaths: List?): Flow> { - return operator.realmReference.owner.registerObserver(this, keyPaths) + // TODO + return operator.realmReference.owner.registerObserver(this, Pair(ClassKey(0), keyPaths)) } override fun freeze(frozenRealm: RealmReference): ManagedRealmSet? { @@ -177,7 +178,7 @@ internal class ManagedRealmSet constructor( } override fun registerForNotification( - keyPaths: RealmKeyPathArray?, + keyPaths: RealmKeyPathArrayPointer?, callback: Callback ): RealmNotificationTokenPointer { return RealmInterop.realm_set_add_notification_callback(nativePointer, keyPaths, callback) 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 ef764e3a0d..be2c762cb7 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 @@ -2,9 +2,13 @@ package io.realm.kotlin.internal import io.realm.kotlin.VersionId import io.realm.kotlin.internal.interop.Callback +import io.realm.kotlin.internal.interop.ClassKey +import io.realm.kotlin.internal.interop.NativePointer import io.realm.kotlin.internal.interop.RealmChangesPointer import io.realm.kotlin.internal.interop.RealmInterop import io.realm.kotlin.internal.interop.RealmKeyPathArray +import io.realm.kotlin.internal.interop.RealmKeyPathArrayPointer +import io.realm.kotlin.internal.interop.RealmKeyPathArrayT import io.realm.kotlin.internal.platform.runBlocking import io.realm.kotlin.internal.schema.RealmSchemaImpl import io.realm.kotlin.internal.util.LiveRealmContext @@ -94,7 +98,8 @@ internal class SuspendableNotifier( return _realmChanged.asSharedFlow() } - internal fun , C> registerObserver(flowable: Observable, keyPaths: RealmKeyPathArray?): Flow { + internal fun , C> registerObserver(flowable: Observable, keyPaths: Pair): Flow { + val keypathsPtr: RealmKeyPathArrayPointer? = keyPaths?.let { RealmInterop.create_key_paths_array(realm.owner.realmReference.dbPointer, keyPaths.first, keyPaths.second) } return callbackFlow { val token: AtomicRef = kotlinx.atomicfu.atomic(NO_OP_NOTIFICATION_TOKEN) @@ -123,7 +128,7 @@ internal class SuspendableNotifier( changeFlow.emit(frozenObservable, change) } } - token.value = NotificationToken(lifeRef.registerForNotification(keyPaths, interopCallback)) + token.value = NotificationToken(lifeRef.registerForNotification(keypathsPtr, interopCallback)) } else { changeFlow.emit(null) } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/ObjectQuery.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/ObjectQuery.kt index b29990a5ec..c91f95e35a 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/ObjectQuery.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/ObjectQuery.kt @@ -172,7 +172,8 @@ internal class ObjectQuery constructor( override fun asFlow(keyPath: List?): Flow> { return realmReference.owner - .registerObserver(this, keyPath) + // TODO + .registerObserver(this, Pair(ClassKey(0), keyPath)) } override fun delete() { 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 bba1c4f58f..8c7ce07a31 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 @@ -96,7 +96,8 @@ internal class CountQuery constructor( override fun asFlow(): Flow { realmReference.checkClosed() return realmReference.owner - .registerObserver(this, null) + // TODO + .registerObserver(this, Pair(ClassKey(0), null)) .map { it.list.size.toLong() }.distinctUntilChanged() @@ -148,7 +149,8 @@ internal class MinMaxQuery constructor( override fun asFlow(): Flow { realmReference.checkClosed() return realmReference.owner - .registerObserver(this, null) + // TODO + .registerObserver(this, Pair(ClassKey(0), null)) .map { val realmResults = it.list as RealmResultsImpl<*> findFromResults(realmResults.nativePointer, realmResults.realm) @@ -216,7 +218,8 @@ internal class SumQuery constructor( override fun asFlow(): Flow { realmReference.checkClosed() return realmReference.owner - .registerObserver(this, null) + // TODO + .registerObserver(this, Pair(ClassKey(0), null)) .map { findFromResults((it.list as RealmResultsImpl<*>).nativePointer) } .distinctUntilChanged() } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/SingleQuery.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/SingleQuery.kt index 40c231ce10..6a1fc41e64 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/SingleQuery.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/SingleQuery.kt @@ -58,7 +58,8 @@ internal class SingleQuery constructor( */ override fun asFlow(keyPaths: List?): Flow> { var oldHead: E? = null - return realmReference.owner.registerObserver(this, keyPaths) + // TODO + return realmReference.owner.registerObserver(this, Pair(ClassKey(0), keyPaths)) // Convert into flow of result head .map { resultChange: ResultsChange -> resultChange.list.firstOrNull() } // Only react when head is changed 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..e33130ea57 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 @@ -408,4 +408,34 @@ class BacklinksNotificationsTests : RealmEntityNotificationTests { c.close() } } + + @Test + override fun keyPath_topLevelProperty() { + TODO("Not yet implemented") + } + + @Test + override fun keyPath_nestedProperty() { + TODO("Not yet implemented") + } + + @Test + override fun keyPath_propertyBelowDefaultLimit() { + TODO("Not yet implemented") + } + + @Test + override fun keyPath_unknownTopLevelProperty() { + TODO("Not yet implemented") + } + + @Test + override fun keyPath_unknownNestedProperty() { + TODO("Not yet implemented") + } + + @Test + override fun keyPath_invalidNestedProperty() { + 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..032f885baf 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 @@ -428,4 +428,34 @@ class RealmDictionaryNotificationsTests : RealmEntityNotificationTests { channel.close() } } + + @Test + override fun keyPath_topLevelProperty() { + TODO("Not yet implemented") + } + + @Test + override fun keyPath_nestedProperty() { + TODO("Not yet implemented") + } + + @Test + override fun keyPath_propertyBelowDefaultLimit() { + TODO("Not yet implemented") + } + + @Test + override fun keyPath_unknownTopLevelProperty() { + TODO("Not yet implemented") + } + + @Test + override fun keyPath_unknownNestedProperty() { + TODO("Not yet implemented") + } + + @Test + override fun keyPath_invalidNestedProperty() { + TODO("Not yet implemented") + } } 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 d38eb09c61..5303b16845 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 @@ -470,6 +470,36 @@ class RealmListNotificationsTests : RealmEntityNotificationTests { } } + @Test + override fun keyPath_topLevelProperty() { + TODO("Not yet implemented") + } + + @Test + override fun keyPath_nestedProperty() { + TODO("Not yet implemented") + } + + @Test + override fun keyPath_propertyBelowDefaultLimit() { + TODO("Not yet implemented") + } + + @Test + override fun keyPath_unknownTopLevelProperty() { + TODO("Not yet implemented") + } + + @Test + override fun keyPath_unknownNestedProperty() { + TODO("Not yet implemented") + } + + @Test + override fun keyPath_invalidNestedProperty() { + TODO("Not yet implemented") + } + fun RealmList<*>.removeRange(range: IntRange) { range.reversed().forEach { index -> removeAt(index) } } diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmNotificationsTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmNotificationsTests.kt index 26f85807f2..b42a5391b2 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmNotificationsTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmNotificationsTests.kt @@ -189,13 +189,49 @@ class RealmNotificationsTests : FlowableTests { @Test @Ignore override fun closeRealmInsideFlowThrows() { - TODO("Wait for a Global change listener to become available") + // Keypaths not supported by Realm notifications } @Test @Ignore override fun closingRealmDoesNotCancelFlows() { - TODO("Wait for a Global change listener to become available") + // Keypaths not supported by Realm notifications + } + + @Test + @Ignore + override fun keyPath_topLevelProperty() { + // Keypaths not supported by Realm notifications + } + + @Test + @Ignore + override fun keyPath_nestedProperty() { + // Keypaths not supported by Realm notifications + } + + @Test + @Ignore + override fun keyPath_propertyBelowDefaultLimit() { + // Keypaths not supported by Realm notifications + } + + @Test + @Ignore + override fun keyPath_unknownTopLevelProperty() { + // Keypaths not supported by Realm notifications + } + + @Test + @Ignore + override fun keyPath_unknownNestedProperty() { + // Keypaths not supported by Realm notifications + } + + @Test + @Ignore + override fun keyPath_invalidNestedProperty() { + // Keypaths not supported by Realm notifications } @Test 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 ff35bf9efd..e0c3e42193 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 @@ -42,6 +42,7 @@ import kotlin.test.Ignore import kotlin.test.Test import kotlin.test.assertContains import kotlin.test.assertEquals +import kotlin.test.assertFailsWith import kotlin.test.assertIs import kotlin.test.assertNotNull import kotlin.test.assertNull @@ -303,42 +304,156 @@ class RealmObjectNotificationsTests : RealmEntityNotificationTests { } @Test - fun keyPath_allTopLevelProperties() = runBlocking { + override fun keyPath_topLevelProperty() = runBlocking { + val c = Channel>(1) + val obj: Sample = realm.write { copyToRealm(Sample()) } + val observer = async { + obj.asFlow(listOf("stringField")).collect { + c.trySend(it) + } + } + assertIs>(c.receiveOrFail()) + realm.write { + // Update field that should not trigger a notification + findLatest(obj)!!.intField = 42 + } + realm.write { + // Update field that should trigger a notification + findLatest(obj)!!.stringField = "Bar" + } + c.receiveOrFail().let { objectChange -> + assertIs>(objectChange) + when(objectChange) { + is UpdatedObject -> { + assertEquals(1, objectChange.changedFields.size) + assertEquals("stringField", objectChange.changedFields.first()) + assertEquals("Bar", objectChange.obj.stringField) + } + else -> fail("Unexpected change: $objectChange") + } + } + observer.cancel() + c.close() + } + + @Test + override fun keyPath_nestedProperty() = runBlocking { val c = Channel>(1) val obj: Sample = realm.write { - copyToRealm(Sample()) + copyToRealm(Sample().apply { + this.stringField = "parent" + this.nullableObject = Sample().apply { + this.stringField = "child" + } + }) } val observer = async { - obj.asFlow(listOf("stringField")).collect { + obj.asFlow(listOf("nullableObject.stringField")).collect { c.trySend(it) } - fail("Flow should not be canceled.") + } + assertIs>(c.receiveOrFail()) + realm.write { + // Update field that should not trigger a notification + findLatest(obj)!!.stringField = "Parent change" + } + realm.write { + // Update field that should trigger a notification + findLatest(obj)!!.nullableObject!!.stringField = "Bar" } c.receiveOrFail().let { objectChange -> - assertIs>(objectChange) + assertIs>(objectChange) + when(objectChange) { + is UpdatedObject -> { + // Core will only report something changed to the top-level property. + assertEquals(1, objectChange.changedFields.size) + assertEquals("nullableObject", objectChange.changedFields.first()) + assertEquals("Bar", objectChange.obj.nullableObject!!.stringField) + } + else -> fail("Unexpected change: $objectChange") + } + } + observer.cancel() + c.close() + } + + @Test + override fun keyPath_propertyBelowDefaultLimit() = runBlocking { + val c = Channel>(1) + val obj: Sample = realm.write { + copyToRealm(Sample().apply { + this.stringField = "parent" + this.nullableObject = Sample().apply { + this.stringField = "child" + this.nullableObject = Sample().apply { + this.stringField = "child-child" + this.nullableObject = Sample().apply { + this.stringField = "child-child-child" + this.nullableObject = Sample().apply { + this.stringField = "child-child-child" + this.nullableObject = Sample().apply { + this.stringField = "BottomChild" + } + } + } + } + } + }) + } + val observer = async { + obj.asFlow(listOf("nullableObject.nullableObject.nullableObject.nullableObject.nullableObject.stringField")).collect { + c.trySend(it) + } + } + assertIs>(c.receiveOrFail()) + realm.write { + // Update field that should not trigger a notification + findLatest(obj)!!.stringField = "Parent change" } realm.write { - findLatest(obj)!!.nullableStringField = "Bar" + // Update field that should trigger a notification + findLatest(obj)!!.nullableObject!!.nullableObject!!.nullableObject!!.nullableObject!!.nullableObject!!.stringField = "Bar" } c.receiveOrFail().let { objectChange -> assertIs>(objectChange) when(objectChange) { is UpdatedObject -> { + // Core will only report something changed to the top-level property. assertEquals(1, objectChange.changedFields.size) - assertEquals("stringField", objectChange.changedFields.first()) + assertEquals("nullableObject", objectChange.changedFields.first()) + assertEquals("Bar", objectChange.obj.nullableObject!!.nullableObject!!.nullableObject!!.nullableObject!!.nullableObject!!.stringField) } - else -> TODO() + else -> fail("Unexpected change: $objectChange") } - assertEquals("Bar", objectChange.obj.stringField) } - realm.close() observer.cancel() c.close() - Unit } @Test - fun keyPath_singleTopLevelProperty() { + override fun keyPath_unknownTopLevelProperty() = runBlocking { + val obj: Sample = realm.write { copyToRealm(Sample()) } + assertFailsWith() { + obj.asFlow(listOf("foo")) + } + } + @Test + override fun keyPath_unknownNestedProperty() = runBlocking { + val obj: Sample = realm.write { copyToRealm(Sample()) } + assertFailsWith() { + obj.asFlow(listOf("nullableObject.foo")) + } + } + + @Test + override fun keyPath_invalidNestedProperty() = runBlocking { + val obj: Sample = realm.write { copyToRealm(Sample()) } + assertFailsWith { + obj.asFlow(listOf("nullableObject.intField.foo")) + } + assertFailsWith { + obj.asFlow(listOf("nullableObject.intListField.foo")) + } } } diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmResultsNotificationsTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmResultsNotificationsTests.kt index 4ed048123f..37b96ad733 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmResultsNotificationsTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmResultsNotificationsTests.kt @@ -387,4 +387,34 @@ class RealmResultsNotificationsTests : FlowableTests { c.close() } } + + @Test + override fun keyPath_topLevelProperty() { + TODO("Not yet implemented") + } + + @Test + override fun keyPath_nestedProperty() { + TODO("Not yet implemented") + } + + @Test + override fun keyPath_propertyBelowDefaultLimit() { + TODO("Not yet implemented") + } + + @Test + override fun keyPath_unknownTopLevelProperty() { + TODO("Not yet implemented") + } + + @Test + override fun keyPath_unknownNestedProperty() { + TODO("Not yet implemented") + } + + @Test + override fun keyPath_invalidNestedProperty() { + TODO("Not yet implemented") + } } 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..e6005fb61a 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 @@ -342,4 +342,34 @@ class RealmSetNotificationsTests : RealmEntityNotificationTests { channel.close() } } + + @Test + override fun keyPath_topLevelProperty() { + TODO("Not yet implemented") + } + + @Test + override fun keyPath_nestedProperty() { + TODO("Not yet implemented") + } + + @Test + override fun keyPath_propertyBelowDefaultLimit() { + TODO("Not yet implemented") + } + + @Test + override fun keyPath_unknownTopLevelProperty() { + TODO("Not yet implemented") + } + + @Test + override fun keyPath_unknownNestedProperty() { + TODO("Not yet implemented") + } + + @Test + override fun keyPath_invalidNestedProperty() { + TODO("Not yet implemented") + } } diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/utils/FlowableTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/utils/FlowableTests.kt index 1b43a545b5..7556b641ef 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/utils/FlowableTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/utils/FlowableTests.kt @@ -49,6 +49,24 @@ interface FlowableTests { @Test fun closingRealmDoesNotCancelFlows() + @Test + fun keyPath_topLevelProperty() + + @Test + fun keyPath_nestedProperty() + + @Test + fun keyPath_propertyBelowDefaultLimit() + + @Test + fun keyPath_unknownTopLevelProperty() + + @Test + fun keyPath_unknownNestedProperty() + + @Test + fun keyPath_invalidNestedProperty() + // @Test // fun addChangeListener_emitOnProvidedDispatcher() { // // FIXME Implement in another PR From 0d4874991930c3840d6cc26f1d3912e19b742f6a Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 6 Nov 2023 10:17:01 +0100 Subject: [PATCH 05/21] Results tests --- .../realm/kotlin/internal/RealmResultsImpl.kt | 3 +- .../RealmResultsNotificationsTests.kt | 155 ++++++++++++++++-- 2 files changed, 144 insertions(+), 14 deletions(-) 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 112d6cead8..644cfb871e 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 @@ -108,8 +108,7 @@ internal class RealmResultsImpl constructor( override fun asFlow(keyPaths: List?): Flow> { realm.checkClosed() - // TODO - return realm.owner.registerObserver(this, Pair(ClassKey(0), keyPaths)) + return realm.owner.registerObserver(this, Pair(classKey, keyPaths)) } override fun delete() { diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmResultsNotificationsTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmResultsNotificationsTests.kt index 37b96ad733..70af412bcc 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmResultsNotificationsTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmResultsNotificationsTests.kt @@ -22,10 +22,14 @@ import io.realm.kotlin.RealmConfiguration import io.realm.kotlin.entities.Sample import io.realm.kotlin.entities.list.RealmListContainer import io.realm.kotlin.entities.list.listTestSchema +import io.realm.kotlin.ext.asFlow import io.realm.kotlin.ext.query +import io.realm.kotlin.notifications.InitialObject import io.realm.kotlin.notifications.InitialResults import io.realm.kotlin.notifications.ListChangeSet.Range +import io.realm.kotlin.notifications.ObjectChange import io.realm.kotlin.notifications.ResultsChange +import io.realm.kotlin.notifications.UpdatedObject import io.realm.kotlin.notifications.UpdatedResults import io.realm.kotlin.query.find import io.realm.kotlin.test.common.OBJECT_VALUES @@ -44,6 +48,7 @@ import kotlin.test.BeforeTest import kotlin.test.Ignore import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertFailsWith import kotlin.test.assertIs import kotlin.test.assertNotNull import kotlin.test.assertTrue @@ -389,32 +394,158 @@ class RealmResultsNotificationsTests : FlowableTests { } @Test - override fun keyPath_topLevelProperty() { - TODO("Not yet implemented") + override fun keyPath_topLevelProperty() = runBlocking { + val c = Channel>(1) + realm.write { copyToRealm(Sample()) } + val results = realm.query().find() + val observer = async { + results.asFlow(listOf("stringField")).collect { + c.trySend(it) + } + } + assertIs>(c.receiveOrFail()) + realm.write { + // Update field that should not trigger a notification + findLatest(results.first())!!.intField = 42 + } + realm.write { + // Update field that should trigger a notification + findLatest(results.first())!!.stringField = "Bar" + } + c.receiveOrFail().let { resultsChange -> + assertIs>(resultsChange) + when(resultsChange) { + is UpdatedResults -> { + assertEquals(1, resultsChange.changes.size) + } + else -> fail("Unexpected change: $resultsChange") + } + } + observer.cancel() + c.close() } @Test - override fun keyPath_nestedProperty() { - TODO("Not yet implemented") + override fun keyPath_nestedProperty() = runBlocking { + val c = Channel>(1) + realm.write { + copyToRealm(Sample().apply { + this.stringField = "parent" + this.nullableObject = Sample().apply { + this.stringField = "child" + } + }) + } + val results = realm.query("stringField != 'child'").find() + assertEquals(1, results.size) + val observer = async { + results.asFlow(listOf("nullableObject.stringField")).collect { + c.trySend(it) + } + } + assertIs>(c.receiveOrFail()) + realm.write { + // Update field that should not trigger a notification + findLatest(results.first())!!.stringField = "Parent change" + } + realm.write { + // Update field that should trigger a notification + findLatest(results.first())!!.nullableObject!!.stringField = "Bar" + } + c.receiveOrFail().let { resultsChange -> + assertIs>(resultsChange) + when(resultsChange) { + is UpdatedResults -> { + assertEquals(1, resultsChange.changes.size) + } + else -> fail("Unexpected change: $resultsChange") + } + } + observer.cancel() + c.close() } @Test - override fun keyPath_propertyBelowDefaultLimit() { - TODO("Not yet implemented") + override fun keyPath_propertyBelowDefaultLimit() = runBlocking { + val c = Channel>(1) + realm.write { + copyToRealm(Sample().apply { + this.intField = 1 + this.stringField = "parent" + this.nullableObject = Sample().apply { + this.stringField = "child" + this.nullableObject = Sample().apply { + this.stringField = "child-child" + this.nullableObject = Sample().apply { + this.stringField = "child-child-child" + this.nullableObject = Sample().apply { + this.stringField = "child-child-child" + this.nullableObject = Sample().apply { + this.stringField = "BottomChild" + } + } + } + } + } + }) + } + val results = realm.query("intField = 1").find() + assertEquals(1, results.size) + val observer = async { + results.asFlow(listOf("nullableObject.nullableObject.nullableObject.nullableObject.nullableObject.stringField")).collect { + c.trySend(it) + } + } + assertIs>(c.receiveOrFail()) + realm.write { + // Update field that should not trigger a notification + findLatest(results.first())!!.stringField = "Parent change" + } + realm.write { + // Update field that should trigger a notification + findLatest(results.first())!!.nullableObject!!.nullableObject!!.nullableObject!!.nullableObject!!.nullableObject!!.stringField = "Bar" + } + c.receiveOrFail().let { resultsChange -> + assertIs>(resultsChange) + when(resultsChange) { + is ResultsChange<*> -> { + // Core will only report something changed to the top-level property. + assertEquals(1, resultsChange.changes.size) + } + else -> fail("Unexpected change: $resultsChange") + } + } + observer.cancel() + c.close() } @Test - override fun keyPath_unknownTopLevelProperty() { - TODO("Not yet implemented") + override fun keyPath_unknownTopLevelProperty() = runBlocking { + realm.write { copyToRealm(Sample()) } + val results = realm.query() + assertFailsWith() { + results.asFlow(listOf("foo")) + } } @Test - override fun keyPath_unknownNestedProperty() { - TODO("Not yet implemented") + override fun keyPath_unknownNestedProperty() = runBlocking { + realm.write { copyToRealm(Sample()) } + val results = realm.query() + assertFailsWith() { + results.asFlow(listOf("nullableObject.foo")) + } } @Test - override fun keyPath_invalidNestedProperty() { - TODO("Not yet implemented") + override fun keyPath_invalidNestedProperty() = runBlocking { + realm.write { copyToRealm(Sample()) } + val results = realm.query() + assertFailsWith { + results.asFlow(listOf("nullableObject.intField.foo")) + } + assertFailsWith { + results.asFlow(listOf("nullableObject.intListField.foo")) + } } } From 813aa34423488bcebe565001c7e4236e6a140091 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 6 Nov 2023 15:38:56 +0100 Subject: [PATCH 06/21] Add tests for sets and lists --- .../kotlin/internal/RealmListInternal.kt | 10 +- .../realm/kotlin/internal/RealmSetInternal.kt | 10 +- .../RealmListNotificationsTests.kt | 167 ++++++++++++++++-- .../RealmSetNotificationsTests.kt | 165 +++++++++++++++-- 4 files changed, 323 insertions(+), 29 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 299caf4f7a..0ebc0af893 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 @@ -117,8 +117,14 @@ internal class ManagedRealmList( override fun asFlow(keyPaths: List?): Flow> { operator.realmReference.checkClosed() - // TODO - return operator.realmReference.owner.registerObserver(this, Pair(ClassKey(0), keyPaths)) + val classKey: ClassKey = keyPaths?.let { + if (operator is RealmObjectListOperator) { + operator.classKey + } else { + throw IllegalArgumentException("Keypaths are only supported for lists of objects.") + } + } ?: ClassKey(0) + return operator.realmReference.owner.registerObserver(this, Pair(classKey, keyPaths)) } override fun freeze(frozenRealm: RealmReference): ManagedRealmList? { 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 5046fb729d..7509b3d383 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 @@ -161,8 +161,14 @@ internal class ManagedRealmSet constructor( } override fun asFlow(keyPaths: List?): Flow> { - // TODO - return operator.realmReference.owner.registerObserver(this, Pair(ClassKey(0), keyPaths)) + val classKey: ClassKey = keyPaths?.let { + if (operator is RealmObjectSetOperator) { + operator.classKey + } else { + throw IllegalArgumentException("Keypaths are only supported for sets of objects.") + } + } ?: ClassKey(0) + return operator.realmReference.owner.registerObserver(this, Pair(classKey, keyPaths)) } override fun freeze(frozenRealm: RealmReference): ManagedRealmSet? { 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 5303b16845..dfcf1f651f 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 @@ -18,14 +18,19 @@ package io.realm.kotlin.test.common.notifications import io.realm.kotlin.Realm import io.realm.kotlin.RealmConfiguration +import io.realm.kotlin.entities.Sample import io.realm.kotlin.entities.list.RealmListContainer import io.realm.kotlin.entities.list.listTestSchema -import io.realm.kotlin.ext.asFlow +import io.realm.kotlin.ext.query +import io.realm.kotlin.ext.realmListOf import io.realm.kotlin.notifications.DeletedList import io.realm.kotlin.notifications.InitialList +import io.realm.kotlin.notifications.InitialResults import io.realm.kotlin.notifications.ListChange import io.realm.kotlin.notifications.ListChangeSet.Range +import io.realm.kotlin.notifications.ResultsChange import io.realm.kotlin.notifications.UpdatedList +import io.realm.kotlin.notifications.UpdatedResults import io.realm.kotlin.test.common.OBJECT_VALUES import io.realm.kotlin.test.common.OBJECT_VALUES2 import io.realm.kotlin.test.common.OBJECT_VALUES3 @@ -46,6 +51,7 @@ import kotlin.test.BeforeTest import kotlin.test.Ignore import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertFailsWith import kotlin.test.assertIs import kotlin.test.assertNotNull import kotlin.test.assertTrue @@ -471,33 +477,168 @@ class RealmListNotificationsTests : RealmEntityNotificationTests { } @Test - override fun keyPath_topLevelProperty() { - TODO("Not yet implemented") + override fun keyPath_topLevelProperty() = runBlocking { + val c = Channel>(1) + val obj = realm.write { copyToRealm(RealmListContainer().apply { + this.objectListField = realmListOf( + RealmListContainer().apply { this.stringField = "list-item-1" }, + RealmListContainer().apply { this.stringField = "list-item-2" } + ) + }) + } + val list: RealmList = obj.objectListField + val observer = async { + list.asFlow(listOf("stringField")).collect { + c.trySend(it) + } + } + assertIs>(c.receiveOrFail()) + realm.write { + // Update field that should not trigger a notification + findLatest(list.first())!!.id = 42 + } + realm.write { + // Update field that should trigger a notification + findLatest(list.first())!!.stringField = "Foo" + } + c.receiveOrFail().let { listChange -> + assertIs>(listChange) + when(listChange) { + is UpdatedList -> { + assertEquals(1, listChange.changes.size) + } + else -> fail("Unexpected change: $listChange") + } + } + observer.cancel() + c.close() } @Test - override fun keyPath_nestedProperty() { - TODO("Not yet implemented") + override fun keyPath_nestedProperty() = runBlocking { + val c = Channel>(1) + val list = realm.write { + copyToRealm(RealmListContainer().apply { + this.stringField = "parent" + this.objectListField = realmListOf( + RealmListContainer().apply { + this.stringField = "child" + this.objectListField = realmListOf( + RealmListContainer().apply { this.stringField = "list-item-1" } + ) + } + ) + }) + }.objectListField + assertEquals(1, list.size) + val observer = async { + list.asFlow(listOf("objectListField.stringField")).collect { + c.trySend(it) + } + } + assertIs>(c.receiveOrFail()) + realm.write { + // Update field that should not trigger a notification + findLatest(list.first())!!.id = 1 + } + realm.write { + // Update field that should trigger a notification + findLatest(list.first())!!.objectListField.first().stringField = "Bar" + } + c.receiveOrFail().let { listChange -> + assertIs>(listChange) + when(listChange) { + is UpdatedList -> { + assertEquals(1, listChange.changes.size) + } + else -> fail("Unexpected change: $listChange") + } + } + observer.cancel() + c.close() } @Test - override fun keyPath_propertyBelowDefaultLimit() { - TODO("Not yet implemented") + override fun keyPath_propertyBelowDefaultLimit() = runBlocking { + val c = Channel>(1) + val list = realm.write { + copyToRealm(RealmListContainer().apply { + this.id = 1 + this.stringField = "parent" + this.objectListField = realmListOf(RealmListContainer().apply { + this.stringField = "child" + this.objectListField = realmListOf(RealmListContainer().apply { + this.stringField = "child-child" + this.objectListField = realmListOf(RealmListContainer().apply { + this.stringField = "child-child-child" + this.objectListField = realmListOf(RealmListContainer().apply { + this.stringField = "child-child-child-child" + this.objectListField = realmListOf(RealmListContainer().apply { + this.stringField = "child-child-child-child-child" + this.objectListField = realmListOf(RealmListContainer().apply { + this.stringField = "BottomChild" + }) + }) + }) + }) + }) + }) + }) + }.objectListField + val observer = async { + list.asFlow(listOf("objectListField.objectListField.objectListField.objectListField.objectListField.stringField")).collect { + c.trySend(it) + } + } + assertIs>(c.receiveOrFail()) + realm.write { + // Update field that should not trigger a notification + findLatest(list.first())!!.stringField = "Parent change" + } + realm.write { + // Update field that should trigger a notification + val obj = findLatest(list.first())!!.objectListField.first().objectListField.first().objectListField.first().objectListField.first().objectListField.first() + obj.stringField = "Bar" + } + c.receiveOrFail().let { listChange -> + assertIs>(listChange) + when(listChange) { + is ListChange -> { + // Core will only report something changed to the top-level property. + assertEquals(1, listChange.changes.size) + } + else -> fail("Unexpected change: $listChange") + } + } + observer.cancel() + c.close() } @Test - override fun keyPath_unknownTopLevelProperty() { - TODO("Not yet implemented") + override fun keyPath_unknownTopLevelProperty() = runBlocking { + val list = realm.write { copyToRealm(RealmListContainer()) }.objectListField + assertFailsWith() { + list.asFlow(listOf("foo")) + } } @Test - override fun keyPath_unknownNestedProperty() { - TODO("Not yet implemented") + override fun keyPath_unknownNestedProperty() = runBlocking { + val list = realm.write { copyToRealm(RealmListContainer()) }.objectListField + assertFailsWith() { + list.asFlow(listOf("objectListField.foo")) + } } @Test - override fun keyPath_invalidNestedProperty() { - TODO("Not yet implemented") + override fun keyPath_invalidNestedProperty() = runBlocking { + val list = realm.write { copyToRealm(RealmListContainer()) }.objectListField + assertFailsWith { + list.asFlow(listOf("intField.foo")) + } + assertFailsWith { + list.asFlow(listOf("objectListField.intListField.foo")) + } } fun RealmList<*>.removeRange(range: IntRange) { 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 e6005fb61a..00bcd64019 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 @@ -19,6 +19,7 @@ package io.realm.kotlin.test.common.notifications import io.realm.kotlin.Realm import io.realm.kotlin.RealmConfiguration import io.realm.kotlin.entities.set.RealmSetContainer +import io.realm.kotlin.ext.realmSetOf import io.realm.kotlin.notifications.DeletedSet import io.realm.kotlin.notifications.InitialSet import io.realm.kotlin.notifications.SetChange @@ -29,6 +30,7 @@ import io.realm.kotlin.test.common.SET_OBJECT_VALUES3 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.RealmSet import kotlinx.coroutines.async import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.first @@ -41,6 +43,7 @@ import kotlin.test.BeforeTest import kotlin.test.Ignore import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertFailsWith import kotlin.test.assertIs import kotlin.test.assertNotNull import kotlin.test.assertTrue @@ -344,32 +347,170 @@ class RealmSetNotificationsTests : RealmEntityNotificationTests { } @Test - override fun keyPath_topLevelProperty() { - TODO("Not yet implemented") + override fun keyPath_topLevelProperty() = runBlocking { + val c = Channel>(1) + val obj = realm.write { copyToRealm(RealmSetContainer().apply { + this.objectSetField = realmSetOf( + RealmSetContainer().apply { this.stringField = "list-item-1" }, + RealmSetContainer().apply { this.stringField = "list-item-2" } + ) + }) + } + val set: RealmSet = obj.objectSetField + val observer = async { + set.asFlow(listOf("stringField")).collect { + c.trySend(it) + } + } + assertIs>(c.receiveOrFail()) + realm.write { + // Update field that should not trigger a notification + findLatest(set.first())!!.id = 42 + } + realm.write { + // Update field that should trigger a notification + findLatest(set.first())!!.stringField = "Foo" + } + c.receiveOrFail().let { listChange -> + assertIs>(listChange) + when(listChange) { + is UpdatedSet -> { + assertEquals(0, listChange.deletions) + assertEquals(0, listChange.insertions) + } + else -> fail("Unexpected change: $listChange") + } + } + observer.cancel() + c.close() } @Test - override fun keyPath_nestedProperty() { - TODO("Not yet implemented") + override fun keyPath_nestedProperty() = runBlocking { + val c = Channel>(1) + val set = realm.write { + copyToRealm(RealmSetContainer().apply { + this.stringField = "parent" + this.objectSetField = realmSetOf( + RealmSetContainer().apply { + this.stringField = "child" + this.objectSetField = realmSetOf( + RealmSetContainer().apply { this.stringField = "list-item-1" } + ) + } + ) + }) + }.objectSetField + assertEquals(1, set.size) + val observer = async { + set.asFlow(listOf("objectSetField.stringField")).collect { + c.trySend(it) + } + } + assertIs>(c.receiveOrFail()) + realm.write { + // Update field that should not trigger a notification + findLatest(set.first())!!.id = 1 + } + realm.write { + // Update field that should trigger a notification + findLatest(set.first())!!.objectSetField.first().stringField = "Bar" + } + c.receiveOrFail().let { setChange -> + assertIs>(setChange) + when(setChange) { + is UpdatedSet -> { + assertEquals(0, setChange.insertions) + assertEquals(0, setChange.deletions) + } + else -> fail("Unexpected change: $setChange") + } + } + observer.cancel() + c.close() } @Test - override fun keyPath_propertyBelowDefaultLimit() { - TODO("Not yet implemented") + override fun keyPath_propertyBelowDefaultLimit() = runBlocking { + val c = Channel>(1) + val list = realm.write { + copyToRealm(RealmSetContainer().apply { + this.id = 1 + this.stringField = "parent" + this.objectSetField = realmSetOf(RealmSetContainer().apply { + this.stringField = "child" + this.objectSetField = realmSetOf(RealmSetContainer().apply { + this.stringField = "child-child" + this.objectSetField = realmSetOf(RealmSetContainer().apply { + this.stringField = "child-child-child" + this.objectSetField = realmSetOf(RealmSetContainer().apply { + this.stringField = "child-child-child-child" + this.objectSetField = realmSetOf(RealmSetContainer().apply { + this.stringField = "child-child-child-child-child" + this.objectSetField = realmSetOf(RealmSetContainer().apply { + this.stringField = "BottomChild" + }) + }) + }) + }) + }) + }) + }) + }.objectSetField + val observer = async { + list.asFlow(listOf("objectSetField.objectSetField.objectSetField.objectSetField.objectSetField.stringField")).collect { + c.trySend(it) + } + } + assertIs>(c.receiveOrFail()) + realm.write { + // Update field that should not trigger a notification + findLatest(list.first())!!.stringField = "Parent change" + } + realm.write { + // Update field that should trigger a notification + val obj = findLatest(list.first())!!.objectSetField.first().objectSetField.first().objectSetField.first().objectSetField.first().objectSetField.first() + obj.stringField = "Bar" + } + c.receiveOrFail().let { listChange -> + assertIs>(listChange) + when(listChange) { + is SetChange -> { + // Core will only report something changed to the top-level property. + assertEquals(0, listChange.insertions) + assertEquals(0, listChange.deletions) + } + else -> fail("Unexpected change: $listChange") + } + } + observer.cancel() + c.close() } @Test - override fun keyPath_unknownTopLevelProperty() { - TODO("Not yet implemented") + override fun keyPath_unknownTopLevelProperty() = runBlocking { + val set = realm.write { copyToRealm(RealmSetContainer()) }.objectSetField + assertFailsWith() { + set.asFlow(listOf("foo")) + } } @Test - override fun keyPath_unknownNestedProperty() { - TODO("Not yet implemented") + override fun keyPath_unknownNestedProperty() = runBlocking { + val set = realm.write { copyToRealm(RealmSetContainer()) }.objectSetField + assertFailsWith() { + set.asFlow(listOf("objectSetField.foo")) + } } @Test - override fun keyPath_invalidNestedProperty() { - TODO("Not yet implemented") + override fun keyPath_invalidNestedProperty() = runBlocking { + val set = realm.write { copyToRealm(RealmSetContainer()) }.objectSetField + assertFailsWith { + set.asFlow(listOf("id.foo")) + } + assertFailsWith { + set.asFlow(listOf("objectSetField.intSetField.foo")) + } } } From 8dd8c3c2bb4d9505ca7de9888481281ae9a8026c Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 6 Nov 2023 16:17:35 +0100 Subject: [PATCH 07/21] Add map tests --- .../realm/kotlin/internal/RealmMapInternal.kt | 10 +- .../RealmDictionaryNotificationsTests.kt | 166 ++++++++++++++++-- 2 files changed, 162 insertions(+), 14 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 5124246fa0..61db5f3f70 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 @@ -103,8 +103,14 @@ internal abstract class ManagedRealmMap constructor( override fun asFlow(keyPaths: List?): Flow> { operator.realmReference.checkClosed() - // TODO - return operator.realmReference.owner.registerObserver(this, Pair(ClassKey(0), keyPaths)) + val classKey: ClassKey = keyPaths?.let { + if (operator is RealmObjectMapOperator) { + operator.classKey + } else { + throw IllegalArgumentException("Keypaths are only supported for maps containing objects.") + } + } ?: ClassKey(0) + return operator.realmReference.owner.registerObserver(this, Pair(classKey, keyPaths)) } override fun registerForNotification( 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 032f885baf..4f63d5f9da 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 @@ -20,7 +20,9 @@ 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.ext.realmDictionaryOf import io.realm.kotlin.notifications.DeletedMap +import io.realm.kotlin.notifications.InitialList import io.realm.kotlin.notifications.InitialMap import io.realm.kotlin.notifications.MapChange import io.realm.kotlin.notifications.UpdatedMap @@ -29,6 +31,7 @@ import io.realm.kotlin.test.common.NULLABLE_DICTIONARY_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.RealmDictionary import kotlinx.coroutines.async import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.first @@ -41,6 +44,7 @@ import kotlin.test.BeforeTest import kotlin.test.Ignore import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertFailsWith import kotlin.test.assertIs import kotlin.test.assertNotNull import kotlin.test.assertTrue @@ -430,32 +434,170 @@ class RealmDictionaryNotificationsTests : RealmEntityNotificationTests { } @Test - override fun keyPath_topLevelProperty() { - TODO("Not yet implemented") + override fun keyPath_topLevelProperty() = runBlocking { + val c = Channel>(1) + val dict: RealmDictionary = realm.write { copyToRealm(RealmDictionaryContainer().apply { + this.nullableObjectDictionaryField = realmDictionaryOf( + "1" to RealmDictionaryContainer().apply { this.stringField = "dict-item-1" }, + ) + })}.nullableObjectDictionaryField + val observer = async { + dict.asFlow(listOf("stringField")).collect { + c.trySend(it) + } + } + assertIs>(c.receiveOrFail()) + realm.write { + // Update field that should not trigger a notification + findLatest(dict.values.first()!!)!!.id = 42 + } + realm.write { + // Update field that should trigger a notification + findLatest(dict.values.first()!!)!!.stringField = "Foo" + } + c.receiveOrFail().let { mapChange -> + assertIs>(mapChange) + when(mapChange) { + is UpdatedMap -> { + assertEquals(1, mapChange.changes.size) + assertEquals("1", mapChange.changes.first()) + } + else -> fail("Unexpected change: $mapChange") + } + } + observer.cancel() + c.close() } @Test - override fun keyPath_nestedProperty() { - TODO("Not yet implemented") + override fun keyPath_nestedProperty() = runBlocking { + val c = Channel>(1) + val dict = realm.write { + copyToRealm(RealmDictionaryContainer().apply { + this.stringField = "parent" + this.nullableObjectDictionaryField = realmDictionaryOf( + "1" to RealmDictionaryContainer().apply { + this.stringField = "child" + this.nullableObjectDictionaryField = realmDictionaryOf( + "1-inner" to RealmDictionaryContainer().apply { this.stringField = "list-item-1" } + ) + } + ) + }) + }.nullableObjectDictionaryField + assertEquals(1, dict.size) + val observer = async { + dict.asFlow(listOf("nullableObjectDictionaryField.stringField")).collect { + c.trySend(it) + } + } + assertIs>(c.receiveOrFail()) + realm.write { + // Update field that should not trigger a notification + findLatest(dict.values.first()!!)!!.id = 1 + } + realm.write { + // Update field that should trigger a notification + findLatest(dict.values.first()!!)!!.nullableObjectDictionaryField.values.first()!!.stringField = "Bar" + } + c.receiveOrFail().let { mapChange -> + assertIs>(mapChange) + when(mapChange) { + is UpdatedMap -> { + assertEquals(1, mapChange.changes.size) + } + else -> fail("Unexpected change: $mapChange") + } + } + observer.cancel() + c.close() } @Test - override fun keyPath_propertyBelowDefaultLimit() { - TODO("Not yet implemented") + override fun keyPath_propertyBelowDefaultLimit() = runBlocking { + val c = Channel>(1) + val dict = realm.write { + copyToRealm(RealmDictionaryContainer().apply { + this.id = 1 + this.stringField = "parent" + this.nullableObjectDictionaryField = realmDictionaryOf("parent" to RealmDictionaryContainer().apply { + this.stringField = "child" + this.nullableObjectDictionaryField = realmDictionaryOf("child" to RealmDictionaryContainer().apply { + this.stringField = "child-child" + this.nullableObjectDictionaryField = realmDictionaryOf("child-child" to RealmDictionaryContainer().apply { + this.stringField = "child-child-child" + this.nullableObjectDictionaryField = realmDictionaryOf("child-child-child" to RealmDictionaryContainer().apply { + this.stringField = "child-child-child-child" + this.nullableObjectDictionaryField = realmDictionaryOf("child-child-child-child" to RealmDictionaryContainer().apply { + this.stringField = "child-child-child-child-child" + this.nullableObjectDictionaryField = realmDictionaryOf("child-child-child-child-child" to RealmDictionaryContainer().apply { + this.stringField = "BottomChild" + }) + }) + }) + }) + }) + }) + }) + }.nullableObjectDictionaryField + val observer = async { + dict.asFlow(listOf("nullableObjectDictionaryField.nullableObjectDictionaryField.nullableObjectDictionaryField.nullableObjectDictionaryField.nullableObjectDictionaryField.stringField")).collect { + c.trySend(it) + } + } + assertIs>(c.receiveOrFail()) + realm.write { + // Update field that should not trigger a notification + findLatest(dict.values.first()!!)!!.stringField = "Parent change" + } + realm.write { + // Update field that should trigger a notification + val obj = findLatest(dict.values.first()!!)!! + .nullableObjectDictionaryField.values.first()!! + .nullableObjectDictionaryField.values.first()!! + .nullableObjectDictionaryField.values.first()!! + .nullableObjectDictionaryField.values.first()!! + .nullableObjectDictionaryField.values.first()!! + obj.stringField = "Bar" + } + c.receiveOrFail().let { mapChange -> + assertIs>(mapChange) + when(mapChange) { + is MapChange -> { + // Core will only report something changed to the top-level property. + assertEquals(1, mapChange.changes.size) + } + else -> fail("Unexpected change: $mapChange") + } + } + observer.cancel() + c.close() } @Test - override fun keyPath_unknownTopLevelProperty() { - TODO("Not yet implemented") + override fun keyPath_unknownTopLevelProperty() = runBlocking { + val dict = realm.write { copyToRealm(RealmDictionaryContainer()) }.nullableObjectDictionaryField + assertFailsWith() { + dict.asFlow(listOf("foo")) + } } @Test - override fun keyPath_unknownNestedProperty() { - TODO("Not yet implemented") + override fun keyPath_unknownNestedProperty() = runBlocking { + val dict = realm.write { copyToRealm(RealmDictionaryContainer()) }.nullableObjectDictionaryField + assertFailsWith() { + dict.asFlow(listOf("objectDictionaryField.foo")) + } } @Test - override fun keyPath_invalidNestedProperty() { - TODO("Not yet implemented") + override fun keyPath_invalidNestedProperty() = runBlocking { + val dict = realm.write { copyToRealm(RealmDictionaryContainer()) }.nullableObjectDictionaryField + assertFailsWith { + dict.asFlow(listOf("intField.foo")) + } + assertFailsWith { + dict.asFlow(listOf("objectDictionaryField.intListField.foo")) + } } } From 94577bdd28ca015e4dd36c54b486f89d09d6a8c8 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 6 Nov 2023 16:17:49 +0100 Subject: [PATCH 08/21] Formatting --- .../io/realm/kotlin/ext/BaseRealmObjectExt.kt | 1 - .../io/realm/kotlin/ext/RealmDictionaryExt.kt | 8 +- .../io/realm/kotlin/ext/RealmListExt.kt | 4 +- .../kotlin/io/realm/kotlin/ext/RealmSetExt.kt | 6 +- .../io/realm/kotlin/internal/Notifiable.kt | 1 - .../io/realm/kotlin/internal/RealmImpl.kt | 1 - .../kotlin/internal/RealmListInternal.kt | 1 - .../realm/kotlin/internal/RealmMapInternal.kt | 1 - .../kotlin/internal/RealmObjectReference.kt | 7 +- .../realm/kotlin/internal/RealmResultsImpl.kt | 7 +- .../kotlin/internal/SuspendableNotifier.kt | 2 - .../RealmDictionaryNotificationsTests.kt | 101 ++++++++++------- .../RealmListNotificationsTests.kt | 107 ++++++++++-------- .../RealmObjectNotificationsTests.kt | 36 +++--- .../RealmResultsNotificationsTests.kt | 41 +++---- .../RealmSetNotificationsTests.kt | 101 ++++++++++------- 16 files changed, 237 insertions(+), 188 deletions(-) diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/BaseRealmObjectExt.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/BaseRealmObjectExt.kt index e67c3a4e88..6517a8c4e8 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/BaseRealmObjectExt.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/BaseRealmObjectExt.kt @@ -21,7 +21,6 @@ import io.realm.kotlin.VersionId import io.realm.kotlin.dynamic.DynamicRealmObject import io.realm.kotlin.internal.UnmanagedState import io.realm.kotlin.internal.checkNotificationsAvailable -import io.realm.kotlin.internal.interop.ClassKey import io.realm.kotlin.internal.interop.RealmInterop import io.realm.kotlin.internal.realmObjectReference import io.realm.kotlin.internal.runIfManaged diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmDictionaryExt.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmDictionaryExt.kt index 4586e9c850..0961467f86 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmDictionaryExt.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmDictionaryExt.kt @@ -18,7 +18,6 @@ package io.realm.kotlin.ext import io.realm.kotlin.TypedRealm import io.realm.kotlin.internal.ManagedRealmDictionary -import io.realm.kotlin.internal.ManagedRealmList import io.realm.kotlin.internal.ManagedRealmMap import io.realm.kotlin.internal.RealmMapMutableEntry import io.realm.kotlin.internal.UnmanagedRealmDictionary @@ -27,7 +26,6 @@ import io.realm.kotlin.internal.getRealm import io.realm.kotlin.internal.interop.ClassKey import io.realm.kotlin.internal.query import io.realm.kotlin.internal.realmMapEntryOf -import io.realm.kotlin.notifications.ListChange import io.realm.kotlin.notifications.MapChange import io.realm.kotlin.query.RealmQuery import io.realm.kotlin.query.TRUE_PREDICATE @@ -123,7 +121,7 @@ public fun RealmDictionary.query( /** * TODO */ -public fun RealmMap.asFlow(keyPaths: List? = null): Flow> { +public fun RealmMap.asFlow(keyPaths: List? = null): Flow> { if (this is ManagedRealmMap) { operator.realmReference.checkClosed() // TODO Find Class Key @@ -136,7 +134,7 @@ public fun RealmMap.asFlow(keyPaths: List< /** * TODO */ -public fun RealmDictionary.asFlow(keyPaths: List? = null): Flow> { +public fun RealmDictionary.asFlow(keyPaths: List? = null): Flow> { if (this is ManagedRealmDictionary) { operator.realmReference.checkClosed() // TODO Find Class Key @@ -144,4 +142,4 @@ public fun RealmDictionary.asFlow(keyPaths: List } else { throw UnsupportedOperationException("Unmanaged dictionaries cannot be observed.") } -} \ No newline at end of file +} diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmListExt.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmListExt.kt index ce75d4f808..d4b58af10e 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmListExt.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmListExt.kt @@ -79,7 +79,7 @@ public fun RealmList.query( /** * TODO */ -public fun RealmList.asFlow(keyPaths: List? = null): Flow> { +public fun RealmList.asFlow(keyPaths: List? = null): Flow> { if (this is ManagedRealmList) { operator.realmReference.checkClosed() // TODO @@ -87,4 +87,4 @@ public fun RealmList.asFlow(keyPaths: List? = nu } else { throw UnsupportedOperationException("Unmanaged lists cannot be observed.") } -} \ No newline at end of file +} diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmSetExt.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmSetExt.kt index 1ac1e6ef01..37cc3c4ba9 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmSetExt.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmSetExt.kt @@ -17,14 +17,12 @@ package io.realm.kotlin.ext import io.realm.kotlin.TypedRealm -import io.realm.kotlin.internal.ManagedRealmList import io.realm.kotlin.internal.ManagedRealmSet import io.realm.kotlin.internal.UnmanagedRealmSet import io.realm.kotlin.internal.asRealmSet import io.realm.kotlin.internal.getRealm import io.realm.kotlin.internal.interop.ClassKey import io.realm.kotlin.internal.query -import io.realm.kotlin.notifications.ListChange import io.realm.kotlin.notifications.SetChange import io.realm.kotlin.query.RealmQuery import io.realm.kotlin.query.TRUE_PREDICATE @@ -79,7 +77,7 @@ public fun RealmSet.query( /** * TODO */ -public fun RealmSet.asFlow(keyPaths: List? = null): Flow> { +public fun RealmSet.asFlow(keyPaths: List? = null): Flow> { if (this is ManagedRealmSet) { operator.realmReference.checkClosed() // TODO @@ -87,4 +85,4 @@ public fun RealmSet.asFlow(keyPaths: List? = nul } else { throw UnsupportedOperationException("Unmanaged sets cannot be observed.") } -} \ No newline at end of file +} 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 ab0cf3469f..f8eb5e1c51 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 @@ -18,7 +18,6 @@ package io.realm.kotlin.internal import io.realm.kotlin.Versioned import io.realm.kotlin.internal.interop.Callback import io.realm.kotlin.internal.interop.RealmChangesPointer -import io.realm.kotlin.internal.interop.RealmKeyPathArray import io.realm.kotlin.internal.interop.RealmKeyPathArrayPointer import io.realm.kotlin.internal.interop.RealmNotificationTokenPointer import io.realm.kotlin.internal.util.Validation.sdkError diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmImpl.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmImpl.kt index bec7e7d37e..53aab1ce54 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmImpl.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmImpl.kt @@ -23,7 +23,6 @@ import io.realm.kotlin.dynamic.DynamicRealm import io.realm.kotlin.internal.dynamic.DynamicRealmImpl import io.realm.kotlin.internal.interop.ClassKey import io.realm.kotlin.internal.interop.RealmInterop -import io.realm.kotlin.internal.interop.RealmKeyPathArray import io.realm.kotlin.internal.interop.SynchronizableObject import io.realm.kotlin.internal.platform.copyAssetFile import io.realm.kotlin.internal.platform.fileExists 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 0ebc0af893..2532e3e715 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,7 +25,6 @@ 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_set_embedded -import io.realm.kotlin.internal.interop.RealmKeyPathArray import io.realm.kotlin.internal.interop.RealmKeyPathArrayPointer import io.realm.kotlin.internal.interop.RealmListPointer import io.realm.kotlin.internal.interop.RealmNotificationTokenPointer 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 61db5f3f70..d6b86de5f3 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 @@ -31,7 +31,6 @@ 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_results_get -import io.realm.kotlin.internal.interop.RealmKeyPathArray import io.realm.kotlin.internal.interop.RealmKeyPathArrayPointer import io.realm.kotlin.internal.interop.RealmMapPointer import io.realm.kotlin.internal.interop.RealmNotificationTokenPointer 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 5fc259fcee..7b83b62a31 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 @@ -21,7 +21,6 @@ import io.realm.kotlin.internal.interop.ClassKey import io.realm.kotlin.internal.interop.PropertyKey import io.realm.kotlin.internal.interop.RealmChangesPointer import io.realm.kotlin.internal.interop.RealmInterop -import io.realm.kotlin.internal.interop.RealmKeyPathArray import io.realm.kotlin.internal.interop.RealmKeyPathArrayPointer import io.realm.kotlin.internal.interop.RealmNotificationTokenPointer import io.realm.kotlin.internal.interop.RealmObjectInterop @@ -106,8 +105,10 @@ public class RealmObjectReference( } as RealmObjectReference? } - override fun registerForNotification(keyPaths: RealmKeyPathArrayPointer?, - callback: Callback): RealmNotificationTokenPointer { + override fun registerForNotification( + keyPaths: RealmKeyPathArrayPointer?, + callback: Callback + ): RealmNotificationTokenPointer { // We should never get here unless it is a managed object as unmanaged doesn't support observing return RealmInterop.realm_object_add_notification_callback( this.objectPointer, 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 644cfb871e..e2aa4d4d15 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 @@ -22,7 +22,6 @@ import io.realm.kotlin.internal.interop.ClassKey import io.realm.kotlin.internal.interop.RealmChangesPointer import io.realm.kotlin.internal.interop.RealmInterop import io.realm.kotlin.internal.interop.RealmInterop.realm_results_get -import io.realm.kotlin.internal.interop.RealmKeyPathArray import io.realm.kotlin.internal.interop.RealmKeyPathArrayPointer import io.realm.kotlin.internal.interop.RealmNotificationTokenPointer import io.realm.kotlin.internal.interop.RealmResultsPointer @@ -136,8 +135,10 @@ internal class RealmResultsImpl constructor( return RealmResultsImpl(liveRealm, liveResultPtr, classKey, clazz, mediator) } - override fun registerForNotification(keyPaths: RealmKeyPathArrayPointer?, - callback: Callback): RealmNotificationTokenPointer { + override fun registerForNotification( + keyPaths: RealmKeyPathArrayPointer?, + callback: Callback + ): RealmNotificationTokenPointer { return RealmInterop.realm_results_add_notification_callback(nativePointer, keyPaths, callback) } 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 be2c762cb7..3004671f94 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 @@ -3,12 +3,10 @@ package io.realm.kotlin.internal import io.realm.kotlin.VersionId import io.realm.kotlin.internal.interop.Callback import io.realm.kotlin.internal.interop.ClassKey -import io.realm.kotlin.internal.interop.NativePointer import io.realm.kotlin.internal.interop.RealmChangesPointer import io.realm.kotlin.internal.interop.RealmInterop import io.realm.kotlin.internal.interop.RealmKeyPathArray import io.realm.kotlin.internal.interop.RealmKeyPathArrayPointer -import io.realm.kotlin.internal.interop.RealmKeyPathArrayT import io.realm.kotlin.internal.platform.runBlocking import io.realm.kotlin.internal.schema.RealmSchemaImpl import io.realm.kotlin.internal.util.LiveRealmContext 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 4f63d5f9da..20ce32d3cd 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 @@ -22,7 +22,6 @@ import io.realm.kotlin.entities.dictionary.DictionaryEmbeddedLevel1 import io.realm.kotlin.entities.dictionary.RealmDictionaryContainer import io.realm.kotlin.ext.realmDictionaryOf import io.realm.kotlin.notifications.DeletedMap -import io.realm.kotlin.notifications.InitialList import io.realm.kotlin.notifications.InitialMap import io.realm.kotlin.notifications.MapChange import io.realm.kotlin.notifications.UpdatedMap @@ -436,11 +435,15 @@ class RealmDictionaryNotificationsTests : RealmEntityNotificationTests { @Test override fun keyPath_topLevelProperty() = runBlocking { val c = Channel>(1) - val dict: RealmDictionary = realm.write { copyToRealm(RealmDictionaryContainer().apply { - this.nullableObjectDictionaryField = realmDictionaryOf( - "1" to RealmDictionaryContainer().apply { this.stringField = "dict-item-1" }, + val dict: RealmDictionary = realm.write { + copyToRealm( + RealmDictionaryContainer().apply { + this.nullableObjectDictionaryField = realmDictionaryOf( + "1" to RealmDictionaryContainer().apply { this.stringField = "dict-item-1" }, + ) + } ) - })}.nullableObjectDictionaryField + }.nullableObjectDictionaryField val observer = async { dict.asFlow(listOf("stringField")).collect { c.trySend(it) @@ -457,7 +460,7 @@ class RealmDictionaryNotificationsTests : RealmEntityNotificationTests { } c.receiveOrFail().let { mapChange -> assertIs>(mapChange) - when(mapChange) { + when (mapChange) { is UpdatedMap -> { assertEquals(1, mapChange.changes.size) assertEquals("1", mapChange.changes.first()) @@ -473,17 +476,19 @@ class RealmDictionaryNotificationsTests : RealmEntityNotificationTests { override fun keyPath_nestedProperty() = runBlocking { val c = Channel>(1) val dict = realm.write { - copyToRealm(RealmDictionaryContainer().apply { - this.stringField = "parent" - this.nullableObjectDictionaryField = realmDictionaryOf( - "1" to RealmDictionaryContainer().apply { - this.stringField = "child" - this.nullableObjectDictionaryField = realmDictionaryOf( - "1-inner" to RealmDictionaryContainer().apply { this.stringField = "list-item-1" } - ) - } - ) - }) + copyToRealm( + RealmDictionaryContainer().apply { + this.stringField = "parent" + this.nullableObjectDictionaryField = realmDictionaryOf( + "1" to RealmDictionaryContainer().apply { + this.stringField = "child" + this.nullableObjectDictionaryField = realmDictionaryOf( + "1-inner" to RealmDictionaryContainer().apply { this.stringField = "list-item-1" } + ) + } + ) + } + ) }.nullableObjectDictionaryField assertEquals(1, dict.size) val observer = async { @@ -502,7 +507,7 @@ class RealmDictionaryNotificationsTests : RealmEntityNotificationTests { } c.receiveOrFail().let { mapChange -> assertIs>(mapChange) - when(mapChange) { + when (mapChange) { is UpdatedMap -> { assertEquals(1, mapChange.changes.size) } @@ -517,28 +522,42 @@ class RealmDictionaryNotificationsTests : RealmEntityNotificationTests { override fun keyPath_propertyBelowDefaultLimit() = runBlocking { val c = Channel>(1) val dict = realm.write { - copyToRealm(RealmDictionaryContainer().apply { - this.id = 1 - this.stringField = "parent" - this.nullableObjectDictionaryField = realmDictionaryOf("parent" to RealmDictionaryContainer().apply { - this.stringField = "child" - this.nullableObjectDictionaryField = realmDictionaryOf("child" to RealmDictionaryContainer().apply { - this.stringField = "child-child" - this.nullableObjectDictionaryField = realmDictionaryOf("child-child" to RealmDictionaryContainer().apply { - this.stringField = "child-child-child" - this.nullableObjectDictionaryField = realmDictionaryOf("child-child-child" to RealmDictionaryContainer().apply { - this.stringField = "child-child-child-child" - this.nullableObjectDictionaryField = realmDictionaryOf("child-child-child-child" to RealmDictionaryContainer().apply { - this.stringField = "child-child-child-child-child" - this.nullableObjectDictionaryField = realmDictionaryOf("child-child-child-child-child" to RealmDictionaryContainer().apply { - this.stringField = "BottomChild" - }) - }) - }) - }) - }) - }) - }) + copyToRealm( + RealmDictionaryContainer().apply { + this.id = 1 + this.stringField = "parent" + this.nullableObjectDictionaryField = realmDictionaryOf( + "parent" to RealmDictionaryContainer().apply { + this.stringField = "child" + this.nullableObjectDictionaryField = realmDictionaryOf( + "child" to RealmDictionaryContainer().apply { + this.stringField = "child-child" + this.nullableObjectDictionaryField = realmDictionaryOf( + "child-child" to RealmDictionaryContainer().apply { + this.stringField = "child-child-child" + this.nullableObjectDictionaryField = realmDictionaryOf( + "child-child-child" to RealmDictionaryContainer().apply { + this.stringField = "child-child-child-child" + this.nullableObjectDictionaryField = realmDictionaryOf( + "child-child-child-child" to RealmDictionaryContainer().apply { + this.stringField = "child-child-child-child-child" + this.nullableObjectDictionaryField = realmDictionaryOf( + "child-child-child-child-child" to RealmDictionaryContainer().apply { + this.stringField = "BottomChild" + } + ) + } + ) + } + ) + } + ) + } + ) + } + ) + } + ) }.nullableObjectDictionaryField val observer = async { dict.asFlow(listOf("nullableObjectDictionaryField.nullableObjectDictionaryField.nullableObjectDictionaryField.nullableObjectDictionaryField.nullableObjectDictionaryField.stringField")).collect { @@ -562,7 +581,7 @@ class RealmDictionaryNotificationsTests : RealmEntityNotificationTests { } c.receiveOrFail().let { mapChange -> assertIs>(mapChange) - when(mapChange) { + when (mapChange) { is MapChange -> { // Core will only report something changed to the top-level property. assertEquals(1, mapChange.changes.size) 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 dfcf1f651f..06550eb10e 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 @@ -21,16 +21,12 @@ import io.realm.kotlin.RealmConfiguration import io.realm.kotlin.entities.Sample import io.realm.kotlin.entities.list.RealmListContainer import io.realm.kotlin.entities.list.listTestSchema -import io.realm.kotlin.ext.query import io.realm.kotlin.ext.realmListOf import io.realm.kotlin.notifications.DeletedList import io.realm.kotlin.notifications.InitialList -import io.realm.kotlin.notifications.InitialResults import io.realm.kotlin.notifications.ListChange import io.realm.kotlin.notifications.ListChangeSet.Range -import io.realm.kotlin.notifications.ResultsChange import io.realm.kotlin.notifications.UpdatedList -import io.realm.kotlin.notifications.UpdatedResults import io.realm.kotlin.test.common.OBJECT_VALUES import io.realm.kotlin.test.common.OBJECT_VALUES2 import io.realm.kotlin.test.common.OBJECT_VALUES3 @@ -479,12 +475,15 @@ class RealmListNotificationsTests : RealmEntityNotificationTests { @Test override fun keyPath_topLevelProperty() = runBlocking { val c = Channel>(1) - val obj = realm.write { copyToRealm(RealmListContainer().apply { - this.objectListField = realmListOf( - RealmListContainer().apply { this.stringField = "list-item-1" }, - RealmListContainer().apply { this.stringField = "list-item-2" } - ) - }) + val obj = realm.write { + copyToRealm( + RealmListContainer().apply { + this.objectListField = realmListOf( + RealmListContainer().apply { this.stringField = "list-item-1" }, + RealmListContainer().apply { this.stringField = "list-item-2" } + ) + } + ) } val list: RealmList = obj.objectListField val observer = async { @@ -503,7 +502,7 @@ class RealmListNotificationsTests : RealmEntityNotificationTests { } c.receiveOrFail().let { listChange -> assertIs>(listChange) - when(listChange) { + when (listChange) { is UpdatedList -> { assertEquals(1, listChange.changes.size) } @@ -518,17 +517,19 @@ class RealmListNotificationsTests : RealmEntityNotificationTests { override fun keyPath_nestedProperty() = runBlocking { val c = Channel>(1) val list = realm.write { - copyToRealm(RealmListContainer().apply { - this.stringField = "parent" - this.objectListField = realmListOf( - RealmListContainer().apply { - this.stringField = "child" - this.objectListField = realmListOf( - RealmListContainer().apply { this.stringField = "list-item-1" } - ) - } - ) - }) + copyToRealm( + RealmListContainer().apply { + this.stringField = "parent" + this.objectListField = realmListOf( + RealmListContainer().apply { + this.stringField = "child" + this.objectListField = realmListOf( + RealmListContainer().apply { this.stringField = "list-item-1" } + ) + } + ) + } + ) }.objectListField assertEquals(1, list.size) val observer = async { @@ -547,7 +548,7 @@ class RealmListNotificationsTests : RealmEntityNotificationTests { } c.receiveOrFail().let { listChange -> assertIs>(listChange) - when(listChange) { + when (listChange) { is UpdatedList -> { assertEquals(1, listChange.changes.size) } @@ -562,28 +563,42 @@ class RealmListNotificationsTests : RealmEntityNotificationTests { override fun keyPath_propertyBelowDefaultLimit() = runBlocking { val c = Channel>(1) val list = realm.write { - copyToRealm(RealmListContainer().apply { - this.id = 1 - this.stringField = "parent" - this.objectListField = realmListOf(RealmListContainer().apply { - this.stringField = "child" - this.objectListField = realmListOf(RealmListContainer().apply { - this.stringField = "child-child" - this.objectListField = realmListOf(RealmListContainer().apply { - this.stringField = "child-child-child" - this.objectListField = realmListOf(RealmListContainer().apply { - this.stringField = "child-child-child-child" - this.objectListField = realmListOf(RealmListContainer().apply { - this.stringField = "child-child-child-child-child" - this.objectListField = realmListOf(RealmListContainer().apply { - this.stringField = "BottomChild" - }) - }) - }) - }) - }) - }) - }) + copyToRealm( + RealmListContainer().apply { + this.id = 1 + this.stringField = "parent" + this.objectListField = realmListOf( + RealmListContainer().apply { + this.stringField = "child" + this.objectListField = realmListOf( + RealmListContainer().apply { + this.stringField = "child-child" + this.objectListField = realmListOf( + RealmListContainer().apply { + this.stringField = "child-child-child" + this.objectListField = realmListOf( + RealmListContainer().apply { + this.stringField = "child-child-child-child" + this.objectListField = realmListOf( + RealmListContainer().apply { + this.stringField = "child-child-child-child-child" + this.objectListField = realmListOf( + RealmListContainer().apply { + this.stringField = "BottomChild" + } + ) + } + ) + } + ) + } + ) + } + ) + } + ) + } + ) }.objectListField val observer = async { list.asFlow(listOf("objectListField.objectListField.objectListField.objectListField.objectListField.stringField")).collect { @@ -602,7 +617,7 @@ class RealmListNotificationsTests : RealmEntityNotificationTests { } c.receiveOrFail().let { listChange -> assertIs>(listChange) - when(listChange) { + when (listChange) { is ListChange -> { // Core will only report something changed to the top-level property. assertEquals(1, listChange.changes.size) 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 e0c3e42193..0d5cdfed22 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 @@ -323,7 +323,7 @@ class RealmObjectNotificationsTests : RealmEntityNotificationTests { } c.receiveOrFail().let { objectChange -> assertIs>(objectChange) - when(objectChange) { + when (objectChange) { is UpdatedObject -> { assertEquals(1, objectChange.changedFields.size) assertEquals("stringField", objectChange.changedFields.first()) @@ -340,12 +340,14 @@ class RealmObjectNotificationsTests : RealmEntityNotificationTests { override fun keyPath_nestedProperty() = runBlocking { val c = Channel>(1) val obj: Sample = realm.write { - copyToRealm(Sample().apply { - this.stringField = "parent" - this.nullableObject = Sample().apply { - this.stringField = "child" + copyToRealm( + Sample().apply { + this.stringField = "parent" + this.nullableObject = Sample().apply { + this.stringField = "child" + } } - }) + ) } val observer = async { obj.asFlow(listOf("nullableObject.stringField")).collect { @@ -363,7 +365,7 @@ class RealmObjectNotificationsTests : RealmEntityNotificationTests { } c.receiveOrFail().let { objectChange -> assertIs>(objectChange) - when(objectChange) { + when (objectChange) { is UpdatedObject -> { // Core will only report something changed to the top-level property. assertEquals(1, objectChange.changedFields.size) @@ -381,24 +383,26 @@ class RealmObjectNotificationsTests : RealmEntityNotificationTests { override fun keyPath_propertyBelowDefaultLimit() = runBlocking { val c = Channel>(1) val obj: Sample = realm.write { - copyToRealm(Sample().apply { - this.stringField = "parent" - this.nullableObject = Sample().apply { - this.stringField = "child" + copyToRealm( + Sample().apply { + this.stringField = "parent" this.nullableObject = Sample().apply { - this.stringField = "child-child" + this.stringField = "child" this.nullableObject = Sample().apply { - this.stringField = "child-child-child" + this.stringField = "child-child" this.nullableObject = Sample().apply { this.stringField = "child-child-child" this.nullableObject = Sample().apply { - this.stringField = "BottomChild" + this.stringField = "child-child-child" + this.nullableObject = Sample().apply { + this.stringField = "BottomChild" + } } } } } } - }) + ) } val observer = async { obj.asFlow(listOf("nullableObject.nullableObject.nullableObject.nullableObject.nullableObject.stringField")).collect { @@ -416,7 +420,7 @@ class RealmObjectNotificationsTests : RealmEntityNotificationTests { } c.receiveOrFail().let { objectChange -> assertIs>(objectChange) - when(objectChange) { + when (objectChange) { is UpdatedObject -> { // Core will only report something changed to the top-level property. assertEquals(1, objectChange.changedFields.size) diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmResultsNotificationsTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmResultsNotificationsTests.kt index 70af412bcc..5a1c648dcc 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmResultsNotificationsTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmResultsNotificationsTests.kt @@ -24,12 +24,9 @@ import io.realm.kotlin.entities.list.RealmListContainer import io.realm.kotlin.entities.list.listTestSchema import io.realm.kotlin.ext.asFlow import io.realm.kotlin.ext.query -import io.realm.kotlin.notifications.InitialObject import io.realm.kotlin.notifications.InitialResults import io.realm.kotlin.notifications.ListChangeSet.Range -import io.realm.kotlin.notifications.ObjectChange import io.realm.kotlin.notifications.ResultsChange -import io.realm.kotlin.notifications.UpdatedObject import io.realm.kotlin.notifications.UpdatedResults import io.realm.kotlin.query.find import io.realm.kotlin.test.common.OBJECT_VALUES @@ -414,7 +411,7 @@ class RealmResultsNotificationsTests : FlowableTests { } c.receiveOrFail().let { resultsChange -> assertIs>(resultsChange) - when(resultsChange) { + when (resultsChange) { is UpdatedResults -> { assertEquals(1, resultsChange.changes.size) } @@ -429,12 +426,14 @@ class RealmResultsNotificationsTests : FlowableTests { override fun keyPath_nestedProperty() = runBlocking { val c = Channel>(1) realm.write { - copyToRealm(Sample().apply { - this.stringField = "parent" - this.nullableObject = Sample().apply { - this.stringField = "child" + copyToRealm( + Sample().apply { + this.stringField = "parent" + this.nullableObject = Sample().apply { + this.stringField = "child" + } } - }) + ) } val results = realm.query("stringField != 'child'").find() assertEquals(1, results.size) @@ -454,7 +453,7 @@ class RealmResultsNotificationsTests : FlowableTests { } c.receiveOrFail().let { resultsChange -> assertIs>(resultsChange) - when(resultsChange) { + when (resultsChange) { is UpdatedResults -> { assertEquals(1, resultsChange.changes.size) } @@ -469,25 +468,27 @@ class RealmResultsNotificationsTests : FlowableTests { override fun keyPath_propertyBelowDefaultLimit() = runBlocking { val c = Channel>(1) realm.write { - copyToRealm(Sample().apply { - this.intField = 1 - this.stringField = "parent" - this.nullableObject = Sample().apply { - this.stringField = "child" + copyToRealm( + Sample().apply { + this.intField = 1 + this.stringField = "parent" this.nullableObject = Sample().apply { - this.stringField = "child-child" + this.stringField = "child" this.nullableObject = Sample().apply { - this.stringField = "child-child-child" + this.stringField = "child-child" this.nullableObject = Sample().apply { this.stringField = "child-child-child" this.nullableObject = Sample().apply { - this.stringField = "BottomChild" + this.stringField = "child-child-child" + this.nullableObject = Sample().apply { + this.stringField = "BottomChild" + } } } } } } - }) + ) } val results = realm.query("intField = 1").find() assertEquals(1, results.size) @@ -507,7 +508,7 @@ class RealmResultsNotificationsTests : FlowableTests { } c.receiveOrFail().let { resultsChange -> assertIs>(resultsChange) - when(resultsChange) { + when (resultsChange) { is ResultsChange<*> -> { // Core will only report something changed to the top-level property. assertEquals(1, resultsChange.changes.size) 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 00bcd64019..834403fc7c 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 @@ -349,12 +349,15 @@ class RealmSetNotificationsTests : RealmEntityNotificationTests { @Test override fun keyPath_topLevelProperty() = runBlocking { val c = Channel>(1) - val obj = realm.write { copyToRealm(RealmSetContainer().apply { - this.objectSetField = realmSetOf( - RealmSetContainer().apply { this.stringField = "list-item-1" }, - RealmSetContainer().apply { this.stringField = "list-item-2" } + val obj = realm.write { + copyToRealm( + RealmSetContainer().apply { + this.objectSetField = realmSetOf( + RealmSetContainer().apply { this.stringField = "list-item-1" }, + RealmSetContainer().apply { this.stringField = "list-item-2" } + ) + } ) - }) } val set: RealmSet = obj.objectSetField val observer = async { @@ -373,7 +376,7 @@ class RealmSetNotificationsTests : RealmEntityNotificationTests { } c.receiveOrFail().let { listChange -> assertIs>(listChange) - when(listChange) { + when (listChange) { is UpdatedSet -> { assertEquals(0, listChange.deletions) assertEquals(0, listChange.insertions) @@ -389,17 +392,19 @@ class RealmSetNotificationsTests : RealmEntityNotificationTests { override fun keyPath_nestedProperty() = runBlocking { val c = Channel>(1) val set = realm.write { - copyToRealm(RealmSetContainer().apply { - this.stringField = "parent" - this.objectSetField = realmSetOf( - RealmSetContainer().apply { - this.stringField = "child" - this.objectSetField = realmSetOf( - RealmSetContainer().apply { this.stringField = "list-item-1" } - ) - } - ) - }) + copyToRealm( + RealmSetContainer().apply { + this.stringField = "parent" + this.objectSetField = realmSetOf( + RealmSetContainer().apply { + this.stringField = "child" + this.objectSetField = realmSetOf( + RealmSetContainer().apply { this.stringField = "list-item-1" } + ) + } + ) + } + ) }.objectSetField assertEquals(1, set.size) val observer = async { @@ -418,7 +423,7 @@ class RealmSetNotificationsTests : RealmEntityNotificationTests { } c.receiveOrFail().let { setChange -> assertIs>(setChange) - when(setChange) { + when (setChange) { is UpdatedSet -> { assertEquals(0, setChange.insertions) assertEquals(0, setChange.deletions) @@ -434,28 +439,42 @@ class RealmSetNotificationsTests : RealmEntityNotificationTests { override fun keyPath_propertyBelowDefaultLimit() = runBlocking { val c = Channel>(1) val list = realm.write { - copyToRealm(RealmSetContainer().apply { - this.id = 1 - this.stringField = "parent" - this.objectSetField = realmSetOf(RealmSetContainer().apply { - this.stringField = "child" - this.objectSetField = realmSetOf(RealmSetContainer().apply { - this.stringField = "child-child" - this.objectSetField = realmSetOf(RealmSetContainer().apply { - this.stringField = "child-child-child" - this.objectSetField = realmSetOf(RealmSetContainer().apply { - this.stringField = "child-child-child-child" - this.objectSetField = realmSetOf(RealmSetContainer().apply { - this.stringField = "child-child-child-child-child" - this.objectSetField = realmSetOf(RealmSetContainer().apply { - this.stringField = "BottomChild" - }) - }) - }) - }) - }) - }) - }) + copyToRealm( + RealmSetContainer().apply { + this.id = 1 + this.stringField = "parent" + this.objectSetField = realmSetOf( + RealmSetContainer().apply { + this.stringField = "child" + this.objectSetField = realmSetOf( + RealmSetContainer().apply { + this.stringField = "child-child" + this.objectSetField = realmSetOf( + RealmSetContainer().apply { + this.stringField = "child-child-child" + this.objectSetField = realmSetOf( + RealmSetContainer().apply { + this.stringField = "child-child-child-child" + this.objectSetField = realmSetOf( + RealmSetContainer().apply { + this.stringField = "child-child-child-child-child" + this.objectSetField = realmSetOf( + RealmSetContainer().apply { + this.stringField = "BottomChild" + } + ) + } + ) + } + ) + } + ) + } + ) + } + ) + } + ) }.objectSetField val observer = async { list.asFlow(listOf("objectSetField.objectSetField.objectSetField.objectSetField.objectSetField.stringField")).collect { @@ -474,7 +493,7 @@ class RealmSetNotificationsTests : RealmEntityNotificationTests { } c.receiveOrFail().let { listChange -> assertIs>(listChange) - when(listChange) { + when (listChange) { is SetChange -> { // Core will only report something changed to the top-level property. assertEquals(0, listChange.insertions) From caa4f38b6f2eb55f27d3c5d20a5db6fbb5b2aa90 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 6 Nov 2023 16:23:18 +0100 Subject: [PATCH 09/21] Update core submodule --- packages/external/core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/external/core b/packages/external/core index df70e2a69d..e4b7aca95a 160000 --- a/packages/external/core +++ b/packages/external/core @@ -1 +1 @@ -Subproject commit df70e2a69dd2a8d1af2528e57eafa4eb201b9d9e +Subproject commit e4b7aca95a20bf9e52d7c9b2f6389dcdb2a2ca8a From 1ec7bd1a688da7a5593f1dc7c7204c618bd24ba9 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 8 Nov 2023 12:01:06 +0100 Subject: [PATCH 10/21] More cleanup --- .../kotlin/internal/interop/RealmInterop.kt | 2 +- packages/jni-swig-stub/realm.i | 7 +---- .../src/main/jni/realm_api_helpers.cpp | 1 - .../src/main/jni/realm_api_helpers.h | 7 ++--- .../io/realm/kotlin/ext/BaseRealmObjectExt.kt | 5 ++- .../io/realm/kotlin/ext/RealmDictionaryExt.kt | 31 ------------------- .../io/realm/kotlin/ext/RealmListExt.kt | 16 ---------- .../kotlin/io/realm/kotlin/ext/RealmSetExt.kt | 16 ---------- .../io/realm/kotlin/internal/BaseRealmImpl.kt | 2 +- .../io/realm/kotlin/internal/RealmImpl.kt | 2 +- .../kotlin/internal/RealmListInternal.kt | 9 +++--- .../realm/kotlin/internal/RealmMapInternal.kt | 11 ++++--- .../kotlin/internal/RealmObjectReference.kt | 9 +++--- .../realm/kotlin/internal/RealmSetInternal.kt | 9 +++--- .../kotlin/internal/SuspendableNotifier.kt | 2 +- .../internal/query/ObjectBoundQueries.kt | 3 -- .../kotlin/internal/query/ObjectQuery.kt | 4 +-- .../kotlin/internal/query/ScalarQuery.kt | 7 ++--- .../kotlin/internal/query/SingleQuery.kt | 6 ++-- .../kotlin/io/realm/kotlin/types/RealmList.kt | 6 +++- .../kotlin/io/realm/kotlin/types/RealmMap.kt | 6 +++- .../kotlin/io/realm/kotlin/types/RealmSet.kt | 6 +++- 22 files changed, 57 insertions(+), 110 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 23f470fc62..e71bfcc7bd 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 @@ -440,7 +440,7 @@ expect object RealmInterop { ): RealmObjectPointer? fun realm_object_delete(obj: RealmObjectPointer) - fun create_key_paths_array(realm: RealmPointer, clazz: ClassKey, keyPaths: RealmKeyPathArray?): RealmKeyPathArrayPointer? + fun create_key_paths_array(realm: RealmPointer, clazz: ClassKey, keyPaths: RealmKeyPathArray): RealmKeyPathArrayPointer fun realm_object_add_notification_callback( obj: RealmObjectPointer, keyPaths: RealmKeyPathArrayPointer?, diff --git a/packages/jni-swig-stub/realm.i b/packages/jni-swig-stub/realm.i index 228e0aa292..42ef42b5b9 100644 --- a/packages/jni-swig-stub/realm.i +++ b/packages/jni-swig-stub/realm.i @@ -444,8 +444,6 @@ import static io.realm.kotlin.internal.interop.realm_errno_e.*; jenv->DeleteLocalRef(temp_string); } } -// -- End - /* These 3 typemaps tell SWIG what JNI and Java types to use */ %typemap(jni) char ** "jobjectArray" @@ -458,10 +456,7 @@ import static io.realm.kotlin.internal.interop.realm_errno_e.*; %typemap(javaout) char ** { return $jnicall; } - - - - +// -- End // FIXME OPTIMIZE Support getting/setting multiple attributes. Ignored for now due to incorrect // type cast in Swig-generated wrapper for "const realm_property_key_t*" which is not cast diff --git a/packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp b/packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp index cc6d426950..fec9e07157 100644 --- a/packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp +++ b/packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp @@ -1072,7 +1072,6 @@ jni_realm_create_key_path_array(const realm_t* realm, if (result) { return out; } else { - delete(out); auto env = get_env(); throw_last_error_as_java_exception(env); return nullptr; diff --git a/packages/jni-swig-stub/src/main/jni/realm_api_helpers.h b/packages/jni-swig-stub/src/main/jni/realm_api_helpers.h index 1f5f4f4c36..da9bbc33a9 100644 --- a/packages/jni-swig-stub/src/main/jni/realm_api_helpers.h +++ b/packages/jni-swig-stub/src/main/jni/realm_api_helpers.h @@ -145,10 +145,9 @@ realm_create_generic_scheduler(); realm_key_path_array_t* jni_realm_create_key_path_array(const realm_t* realm, - const realm_class_key_t object_class_key, - int user_key_paths_count, - const char** user_key_paths -); + const realm_class_key_t object_class_key, + int user_key_paths_count, + const char** user_key_paths); void realm_property_info_t_cleanup(realm_property_info_t* value); diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/BaseRealmObjectExt.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/BaseRealmObjectExt.kt index 6517a8c4e8..2543d30556 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/BaseRealmObjectExt.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/BaseRealmObjectExt.kt @@ -83,7 +83,10 @@ public fun BaseRealmObject.isValid(): Boolean = runIfManaged { * the elements in a timely manner the coroutine scope will be cancelled with a * [CancellationException]. * - * @param keyPaths TODO + * @param keyPaths An optional list of properties that defines when a change to the object will + * result in a change being emitted. Nested properties can be defined using a dotted + * syntex, e.g. `parent.child.name`. If no keypaths are provided, changes to all top-level + * properties and nested properties 4 levels down will trigger a change. * @return a flow representing changes to the object. * @throws UnsupportedOperationException if called on a live [RealmObject] or [EmbeddedRealmObject] * from a write transaction ([Realm.write]) or on a [DynamicRealmObject] inside a migration diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmDictionaryExt.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmDictionaryExt.kt index 0961467f86..b3d48fa629 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmDictionaryExt.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmDictionaryExt.kt @@ -18,25 +18,20 @@ package io.realm.kotlin.ext import io.realm.kotlin.TypedRealm import io.realm.kotlin.internal.ManagedRealmDictionary -import io.realm.kotlin.internal.ManagedRealmMap import io.realm.kotlin.internal.RealmMapMutableEntry import io.realm.kotlin.internal.UnmanagedRealmDictionary import io.realm.kotlin.internal.asRealmDictionary import io.realm.kotlin.internal.getRealm -import io.realm.kotlin.internal.interop.ClassKey import io.realm.kotlin.internal.query import io.realm.kotlin.internal.realmMapEntryOf -import io.realm.kotlin.notifications.MapChange import io.realm.kotlin.query.RealmQuery import io.realm.kotlin.query.TRUE_PREDICATE import io.realm.kotlin.types.BaseRealmObject import io.realm.kotlin.types.RealmDictionary import io.realm.kotlin.types.RealmDictionaryMutableEntry 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 kotlinx.coroutines.flow.Flow /** * Instantiates an **unmanaged** [RealmDictionary] from a variable number of [Pair]s of [String] @@ -117,29 +112,3 @@ public fun RealmDictionary.query( } else { throw IllegalArgumentException("Unmanaged dictionary values cannot be queried.") } - -/** - * TODO - */ -public fun RealmMap.asFlow(keyPaths: List? = null): Flow> { - if (this is ManagedRealmMap) { - operator.realmReference.checkClosed() - // TODO Find Class Key - return operator.realmReference.owner.registerObserver(this, Pair(ClassKey(0), keyPaths)) - } else { - throw UnsupportedOperationException("Unmanaged maps cannot be observed.") - } -} - -/** - * TODO - */ -public fun RealmDictionary.asFlow(keyPaths: List? = null): Flow> { - if (this is ManagedRealmDictionary) { - operator.realmReference.checkClosed() - // TODO Find Class Key - return operator.realmReference.owner.registerObserver(this, Pair(ClassKey(0), keyPaths)) - } else { - throw UnsupportedOperationException("Unmanaged dictionaries cannot be observed.") - } -} diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmListExt.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmListExt.kt index d4b58af10e..81e4004a9e 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmListExt.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmListExt.kt @@ -21,9 +21,7 @@ import io.realm.kotlin.internal.ManagedRealmList import io.realm.kotlin.internal.UnmanagedRealmList import io.realm.kotlin.internal.asRealmList import io.realm.kotlin.internal.getRealm -import io.realm.kotlin.internal.interop.ClassKey import io.realm.kotlin.internal.query -import io.realm.kotlin.notifications.ListChange import io.realm.kotlin.query.RealmQuery import io.realm.kotlin.query.TRUE_PREDICATE import io.realm.kotlin.types.BaseRealmObject @@ -31,7 +29,6 @@ import io.realm.kotlin.types.RealmDictionary import io.realm.kotlin.types.RealmList import io.realm.kotlin.types.RealmSet import io.realm.kotlin.types.TypedRealmObject -import kotlinx.coroutines.flow.Flow /** * Instantiates an **unmanaged** [RealmList]. @@ -75,16 +72,3 @@ public fun RealmList.query( } else { throw IllegalArgumentException("Unmanaged list cannot be queried") } - -/** - * TODO - */ -public fun RealmList.asFlow(keyPaths: List? = null): Flow> { - if (this is ManagedRealmList) { - operator.realmReference.checkClosed() - // TODO - return operator.realmReference.owner.registerObserver(this, Pair(ClassKey(0), keyPaths)) - } else { - throw UnsupportedOperationException("Unmanaged lists cannot be observed.") - } -} diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmSetExt.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmSetExt.kt index 37cc3c4ba9..6f34237352 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmSetExt.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/RealmSetExt.kt @@ -21,9 +21,7 @@ import io.realm.kotlin.internal.ManagedRealmSet import io.realm.kotlin.internal.UnmanagedRealmSet import io.realm.kotlin.internal.asRealmSet import io.realm.kotlin.internal.getRealm -import io.realm.kotlin.internal.interop.ClassKey import io.realm.kotlin.internal.query -import io.realm.kotlin.notifications.SetChange import io.realm.kotlin.query.RealmQuery import io.realm.kotlin.query.TRUE_PREDICATE import io.realm.kotlin.types.BaseRealmObject @@ -31,7 +29,6 @@ import io.realm.kotlin.types.RealmDictionary import io.realm.kotlin.types.RealmList import io.realm.kotlin.types.RealmObject import io.realm.kotlin.types.RealmSet -import kotlinx.coroutines.flow.Flow /** * Instantiates an **unmanaged** [RealmSet]. @@ -73,16 +70,3 @@ public fun RealmSet.query( } else { throw IllegalArgumentException("Unmanaged set cannot be queried") } - -/** - * TODO - */ -public fun RealmSet.asFlow(keyPaths: List? = null): Flow> { - if (this is ManagedRealmSet) { - operator.realmReference.checkClosed() - // TODO - return operator.realmReference.owner.registerObserver(this, Pair(ClassKey(0), keyPaths)) - } else { - throw UnsupportedOperationException("Unmanaged sets cannot be observed.") - } -} diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/BaseRealmImpl.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/BaseRealmImpl.kt index 35f3508872..cb8b3372cc 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/BaseRealmImpl.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/BaseRealmImpl.kt @@ -65,7 +65,7 @@ public abstract class BaseRealmImpl internal constructor( return RealmInterop.realm_get_schema_version(realmReference.dbPointer) } - internal open fun , C> registerObserver(t: Observable, keyPaths: Pair?>): Flow { + internal open fun , C> registerObserver(t: Observable, keyPaths: Pair>?): Flow { throw UnsupportedOperationException(OBSERVABLE_NOT_SUPPORTED_MESSAGE) } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmImpl.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmImpl.kt index 09fd3afb7d..850297ea14 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmImpl.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmImpl.kt @@ -224,7 +224,7 @@ public class RealmImpl private constructor( ) } - override fun , C> registerObserver(t: Observable, keyPaths: Pair?>): Flow { + override fun , C> registerObserver(t: Observable, keyPaths: Pair>?): Flow { return notifier.registerObserver(t, keyPaths) } 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 2532e3e715..d558ffc3b6 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 @@ -116,14 +116,15 @@ internal class ManagedRealmList( override fun asFlow(keyPaths: List?): Flow> { operator.realmReference.checkClosed() - val classKey: ClassKey = keyPaths?.let { - if (operator is RealmObjectListOperator) { + val keyPathInfo = keyPaths?.let { + val classKey = if (operator is RealmObjectListOperator) { operator.classKey } else { throw IllegalArgumentException("Keypaths are only supported for lists of objects.") } - } ?: ClassKey(0) - return operator.realmReference.owner.registerObserver(this, Pair(classKey, keyPaths)) + Pair(classKey, keyPaths) + } + return operator.realmReference.owner.registerObserver(this, keyPathInfo) } override fun freeze(frozenRealm: RealmReference): ManagedRealmList? { 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 d6b86de5f3..440a20d4a3 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 @@ -102,14 +102,15 @@ internal abstract class ManagedRealmMap constructor( override fun asFlow(keyPaths: List?): Flow> { operator.realmReference.checkClosed() - val classKey: ClassKey = keyPaths?.let { - if (operator is RealmObjectMapOperator) { + val keyPathInfo = keyPaths?.let { + val classKey = if (operator is RealmObjectMapOperator) { operator.classKey } else { - throw IllegalArgumentException("Keypaths are only supported for maps containing objects.") + throw IllegalArgumentException("Keypaths are only supported for maps of objects.") } - } ?: ClassKey(0) - return operator.realmReference.owner.registerObserver(this, Pair(classKey, keyPaths)) + Pair(classKey, keyPaths) + } + return operator.realmReference.owner.registerObserver(this, keyPathInfo) } override fun registerForNotification( 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 7b83b62a31..ec59b2aea2 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 @@ -17,7 +17,6 @@ package io.realm.kotlin.internal import io.realm.kotlin.internal.interop.Callback -import io.realm.kotlin.internal.interop.ClassKey import io.realm.kotlin.internal.interop.PropertyKey import io.realm.kotlin.internal.interop.RealmChangesPointer import io.realm.kotlin.internal.interop.RealmInterop @@ -130,9 +129,11 @@ public class RealmObjectReference( }.toTypedArray() } - override fun asFlow(keyPath: List?): Flow> { - // TODO - return this.owner.owner.registerObserver(this, Pair(ClassKey(0), keyPath)) + override fun asFlow(keyPaths: List?): Flow> { + val keyPathInfo = keyPaths?.let { + Pair(metadata.classKey, it) + } + return this.owner.owner.registerObserver(this, keyPathInfo) } override fun delete() { 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 7509b3d383..ec3c2115a8 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 @@ -161,14 +161,15 @@ internal class ManagedRealmSet constructor( } override fun asFlow(keyPaths: List?): Flow> { - val classKey: ClassKey = keyPaths?.let { - if (operator is RealmObjectSetOperator) { + val keyPathInfo = keyPaths?.let { + val classKey = if (operator is RealmObjectSetOperator) { operator.classKey } else { throw IllegalArgumentException("Keypaths are only supported for sets of objects.") } - } ?: ClassKey(0) - return operator.realmReference.owner.registerObserver(this, Pair(classKey, keyPaths)) + Pair(classKey, keyPaths) + } + return operator.realmReference.owner.registerObserver(this, keyPathInfo) } override fun freeze(frozenRealm: RealmReference): ManagedRealmSet? { 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 04bce538e8..1f10db5127 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 @@ -96,7 +96,7 @@ internal class SuspendableNotifier( return _realmChanged.asSharedFlow() } - internal fun , C> registerObserver(flowable: Observable, keyPaths: Pair): Flow { + internal fun , C> registerObserver(flowable: Observable, keyPaths: Pair?): Flow { val keypathsPtr: RealmKeyPathArrayPointer? = keyPaths?.let { RealmInterop.create_key_paths_array(realm.owner.realmReference.dbPointer, keyPaths.first, keyPaths.second) } return callbackFlow { val token: AtomicRef = diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/ObjectBoundQueries.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/ObjectBoundQueries.kt index 97b07ed053..e679ab524f 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/ObjectBoundQueries.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/ObjectBoundQueries.kt @@ -48,7 +48,6 @@ internal class ObjectBoundQuery( realmQuery.query(filter, *arguments) ) - // TODO override fun asFlow(keyPaths: List?): Flow> = realmQuery.asFlow().bind( targetObject, keyPaths @@ -118,7 +117,6 @@ internal class ObjectBoundRealmScalarNullableQuery( val targetObject: RealmObjectReference<*>, val realmQuery: RealmScalarNullableQuery ) : RealmScalarNullableQuery by realmQuery { - // TODO is it correct to set keyPaths to null? override fun asFlow(): Flow = realmQuery.asFlow().bind(targetObject, null) } @@ -126,6 +124,5 @@ internal class ObjectBoundRealmScalarQuery( val targetObject: RealmObjectReference<*>, val realmQuery: RealmScalarQuery ) : RealmScalarQuery by realmQuery { - // TODO is it correct to set keyPaths to null? override fun asFlow(): Flow = realmQuery.asFlow().bind(targetObject, null) } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/ObjectQuery.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/ObjectQuery.kt index c91f95e35a..ffe3dd6b62 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/ObjectQuery.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/ObjectQuery.kt @@ -171,9 +171,9 @@ internal class ObjectQuery constructor( QueryResultNotifiable(resultsPointer, classKey, clazz, mediator) override fun asFlow(keyPath: List?): Flow> { + val keyPathInfo = keyPath?.let { Pair(classKey, it) } return realmReference.owner - // TODO - .registerObserver(this, Pair(ClassKey(0), keyPath)) + .registerObserver(this, keyPathInfo) } override fun delete() { 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 8c7ce07a31..7c65abbf71 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 @@ -97,7 +97,7 @@ internal class CountQuery constructor( realmReference.checkClosed() return realmReference.owner // TODO - .registerObserver(this, Pair(ClassKey(0), null)) + .registerObserver(this, null) .map { it.list.size.toLong() }.distinctUntilChanged() @@ -149,8 +149,7 @@ internal class MinMaxQuery constructor( override fun asFlow(): Flow { realmReference.checkClosed() return realmReference.owner - // TODO - .registerObserver(this, Pair(ClassKey(0), null)) + .registerObserver(this, null) .map { val realmResults = it.list as RealmResultsImpl<*> findFromResults(realmResults.nativePointer, realmResults.realm) @@ -219,7 +218,7 @@ internal class SumQuery constructor( realmReference.checkClosed() return realmReference.owner // TODO - .registerObserver(this, Pair(ClassKey(0), null)) + .registerObserver(this, null) .map { findFromResults((it.list as RealmResultsImpl<*>).nativePointer) } .distinctUntilChanged() } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/SingleQuery.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/SingleQuery.kt index 6a1fc41e64..7c74a69ab7 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/SingleQuery.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/SingleQuery.kt @@ -58,8 +58,10 @@ internal class SingleQuery constructor( */ override fun asFlow(keyPaths: List?): Flow> { var oldHead: E? = null - // TODO - return realmReference.owner.registerObserver(this, Pair(ClassKey(0), keyPaths)) + val keyPathInfo = keyPaths?.let { + Pair(classKey, it) + } + return realmReference.owner.registerObserver(this, keyPathInfo) // Convert into flow of result head .map { resultChange: ResultsChange -> resultChange.list.firstOrNull() } // Only react when head is changed diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmList.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmList.kt index c82a1acb8c..d741273578 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmList.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmList.kt @@ -72,8 +72,12 @@ public interface RealmList : MutableList, Deleteable { * the elements in a timely manner the coroutine scope will be cancelled with a * [CancellationException]. * - * @param keyPaths TODO + * @param keyPaths An optional list of properties that defines when a change to the object will + * result in a change being emitted. Nested properties can be defined using a dotted + * syntex, e.g. `parent.child.name`. If no keypaths are provided, changes to all top-level + * properties and nested properties 4 levels down will trigger a change. * @return a flow representing changes to the list. + * @throws IllegalArgumentException if keypaths are provided for lists not containing objects. * @throws CancellationException if the stream produces changes faster than the consumer can * consume them and results in a buffer overflow. */ diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmMap.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmMap.kt index 672b67104f..c45789afce 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmMap.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmMap.kt @@ -55,8 +55,12 @@ public interface RealmMap : MutableMap { * the elements in a timely manner the coroutine scope will be cancelled with a * [CancellationException]. * - * @param keyPaths TODO + * @param keyPaths An optional list of properties that defines when a change to the object will + * result in a change being emitted. Nested properties can be defined using a dotted + * syntex, e.g. `parent.child.name`. If no keypaths are provided, changes to all top-level + * properties and nested properties 4 levels down will trigger a change. * @return a flow representing changes to the dictionary. + * @throws IllegalArgumentException if keypaths are provided for maps not containing objects. * @throws CancellationException if the stream produces changes faster than the consumer can * consume them and results in a buffer overflow. */ diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmSet.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmSet.kt index bfc140b450..2b1852e61f 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmSet.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmSet.kt @@ -52,8 +52,12 @@ public interface RealmSet : MutableSet, Deleteable { * the elements in a timely manner the coroutine scope will be cancelled with a * [CancellationException]. * - * @param keyPaths TODO Keypaths only make sense for sets with objects? Should we capture this in the type system? + * @param keyPaths An optional list of properties that defines when a change to the object will + * result in a change being emitted. Nested properties can be defined using a dotted + * syntex, e.g. `parent.child.name`. If no keypaths are provided, changes to all top-level + * properties and nested properties 4 levels down will trigger a change. * @return a flow representing changes to the set. + * @throws IllegalArgumentException if keypaths are provided for sets not containing objects. * @throws CancellationException if the stream produces changes faster than the consumer can * consume them and results in a buffer overflow. */ From 4c6f15161cfadb20efb1470e37d49b553c4e09a9 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 9 Nov 2023 15:38:23 +0100 Subject: [PATCH 11/21] Fix macOS implementation --- .../kotlin/internal/interop/RealmInterop.kt | 4 +--- .../kotlin/internal/interop/RealmInterop.kt | 8 +++---- .../kotlin/internal/interop/RealmInterop.kt | 22 +++++++++++++------ .../io/realm/kotlin/ext/BaseRealmObjectExt.kt | 6 ++++- .../realm/kotlin/internal/RealmResultsImpl.kt | 5 ++++- .../kotlin/internal/SuspendableNotifier.kt | 3 +-- 6 files changed, 29 insertions(+), 19 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 e71bfcc7bd..8d0b953157 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 @@ -123,8 +123,6 @@ typealias RealmBaseSubscriptionSetPointer = NativePointer typealias RealmMutableSubscriptionSetPointer = NativePointer -typealias RealmKeyPathArray = List - /** * Class for grouping and normalizing values we want to send as part of * logging in Sync Users. @@ -440,7 +438,7 @@ expect object RealmInterop { ): RealmObjectPointer? fun realm_object_delete(obj: RealmObjectPointer) - fun create_key_paths_array(realm: RealmPointer, clazz: ClassKey, keyPaths: RealmKeyPathArray): RealmKeyPathArrayPointer + fun create_key_paths_array(realm: RealmPointer, clazz: ClassKey, keyPaths: List): RealmKeyPathArrayPointer fun realm_object_add_notification_callback( obj: RealmObjectPointer, keyPaths: RealmKeyPathArrayPointer?, 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 2e8c72d511..de44e2bc52 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 @@ -813,11 +813,9 @@ actual object RealmInterop { return realmc.realm_dictionary_is_valid(dictionary.cptr()) } - actual fun create_key_paths_array(realm: RealmPointer, clazz: ClassKey, keyPaths: RealmKeyPathArray?): RealmKeyPathArrayPointer? { - return keyPaths?.let { - val ptr = realmc.jni_realm_create_key_path_array(realm.cptr(), clazz.key, keyPaths.size, keyPaths.toTypedArray()) - return LongPointerWrapper(ptr) - } + actual fun create_key_paths_array(realm: RealmPointer, clazz: ClassKey, keyPaths: List): RealmKeyPathArrayPointer { + val ptr = realmc.jni_realm_create_key_path_array(realm.cptr(), clazz.key, keyPaths.size, keyPaths.toTypedArray()) + return LongPointerWrapper(ptr) } actual fun realm_object_add_notification_callback( 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 f4cda069d6..04f28093f4 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 @@ -45,6 +45,7 @@ import kotlinx.cinterop.COpaquePointer import kotlinx.cinterop.CPointed import kotlinx.cinterop.CPointer import kotlinx.cinterop.CPointerVar +import kotlinx.cinterop.CPointerVarOf import kotlinx.cinterop.CValue import kotlinx.cinterop.CVariable import kotlinx.cinterop.LongVar @@ -70,6 +71,7 @@ import kotlinx.cinterop.readValue import kotlinx.cinterop.refTo import kotlinx.cinterop.set import kotlinx.cinterop.staticCFunction +import kotlinx.cinterop.toCStringArray import kotlinx.cinterop.toKString import kotlinx.cinterop.useContents import kotlinx.cinterop.usePinned @@ -103,6 +105,7 @@ import realm_wrapper.realm_http_header_t import realm_wrapper.realm_http_request_method import realm_wrapper.realm_http_request_t import realm_wrapper.realm_http_response_t +import realm_wrapper.realm_key_path_array_t import realm_wrapper.realm_link_t import realm_wrapper.realm_list_t import realm_wrapper.realm_object_id_t @@ -1653,8 +1656,13 @@ actual object RealmInterop { checkedBooleanResult(realm_wrapper.realm_object_delete(obj.cptr())) } - actual fun create_key_paths_array(realm: RealmPointer, clazz: ClassKey, keyPaths: RealmKeyPathArray?): RealmKeyPathArrayPointer? { - TODO() + actual fun create_key_paths_array(realm: RealmPointer, clazz: ClassKey, keyPaths: List): RealmKeyPathArrayPointer { + memScoped { + val kps: CPointer>>> = keyPaths.toCStringArray(this) + val kp = allocArray>(1) + checkedBooleanResult(realm_wrapper.realm_create_key_path_array(realm.cptr(), clazz.key.toUInt(), keyPaths.size, kps, kp)) + return CPointerWrapper(kp[0]) + } } actual fun realm_object_add_notification_callback( @@ -1672,7 +1680,7 @@ actual object RealmInterop { ?.dispose() ?: error("Notification callback data should never be null") }, - null, // TODO + keyPaths?.cptr(), staticCFunction { userdata, change -> // Change callback try { userdata?.asStableRef>() @@ -1706,7 +1714,7 @@ actual object RealmInterop { ?.dispose() ?: error("Notification callback data should never be null") }, - null, // See https://github.com/realm/realm-kotlin/issues/661 + keyPaths?.cptr(), staticCFunction { userdata, change -> // Change callback try { userdata?.asStableRef>() @@ -1739,7 +1747,7 @@ actual object RealmInterop { userdata?.asStableRef>()?.dispose() ?: error("Notification callback data should never be null") }, - null, // See https://github.com/realm/realm-kotlin/issues/661 + keyPaths?.cptr(), staticCFunction { userdata, change -> // Change callback try { userdata?.asStableRef>() @@ -1773,7 +1781,7 @@ actual object RealmInterop { ?.dispose() ?: error("Notification callback data should never be null") }, - null, // See https://github.com/realm/realm-kotlin/issues/661 + keyPaths?.cptr(), staticCFunction { userdata, change -> // Change callback try { userdata?.asStableRef>() @@ -1807,7 +1815,7 @@ actual object RealmInterop { ?.dispose() ?: error("Notification callback data should never be null") }, - null, // See https://github.com/realm/realm-kotlin/issues/661 + keyPaths?.cptr(), staticCFunction { userdata, change -> // Change callback try { userdata?.asStableRef>() diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/BaseRealmObjectExt.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/BaseRealmObjectExt.kt index 2543d30556..935e93373e 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/BaseRealmObjectExt.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/BaseRealmObjectExt.kt @@ -94,5 +94,9 @@ public fun BaseRealmObject.isValid(): Boolean = runIfManaged { */ public fun T.asFlow(keyPaths: List? = null): Flow> = runIfManaged { checkNotificationsAvailable() - return owner.owner.registerObserver(this, Pair(this.metadata.classKey, keyPaths)) as Flow> + val keyPathInfo = keyPaths?.let { + Pair(this.metadata.classKey, keyPaths) + } + + return owner.owner.registerObserver(this, keyPathInfo) as Flow> } ?: throw IllegalStateException("Changes cannot be observed on unmanaged objects.") 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 e2aa4d4d15..b5586d9ef2 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 @@ -107,7 +107,10 @@ internal class RealmResultsImpl constructor( override fun asFlow(keyPaths: List?): Flow> { realm.checkClosed() - return realm.owner.registerObserver(this, Pair(classKey, keyPaths)) + val keyPathInfo = keyPaths?.let { + Pair(classKey, keyPaths) + } + return realm.owner.registerObserver(this, keyPathInfo) } override fun delete() { 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 1f10db5127..888d791f18 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 @@ -5,7 +5,6 @@ import io.realm.kotlin.internal.interop.Callback import io.realm.kotlin.internal.interop.ClassKey import io.realm.kotlin.internal.interop.RealmChangesPointer import io.realm.kotlin.internal.interop.RealmInterop -import io.realm.kotlin.internal.interop.RealmKeyPathArray import io.realm.kotlin.internal.interop.RealmKeyPathArrayPointer import io.realm.kotlin.internal.platform.runBlocking import io.realm.kotlin.internal.schema.RealmSchemaImpl @@ -96,7 +95,7 @@ internal class SuspendableNotifier( return _realmChanged.asSharedFlow() } - internal fun , C> registerObserver(flowable: Observable, keyPaths: Pair?): Flow { + internal fun , C> registerObserver(flowable: Observable, keyPaths: Pair>?): Flow { val keypathsPtr: RealmKeyPathArrayPointer? = keyPaths?.let { RealmInterop.create_key_paths_array(realm.owner.realmReference.dbPointer, keyPaths.first, keyPaths.second) } return callbackFlow { val token: AtomicRef = From 078535e59c49493f053f7a7772bc031d3388ace5 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 9 Nov 2023 16:43:51 +0100 Subject: [PATCH 12/21] Add changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d56a375bb1..3eff43fb8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ * None. ### Enhancements -* None. +* Added support for keypaths in `asFlow()` methods on objects. This makes it possible to control which properties will trigger change events, including properties on objects below the default nested limit of 4. (Issue [#661](https://github.com/realm/realm-kotlin/issues/661)) ### Fixed * None. From cdfd2de654d41b75655d4cf9120c82c6c1aed622 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 16 Nov 2023 08:56:27 +0100 Subject: [PATCH 13/21] Add support for keypaths on queries --- CHANGELOG.md | 2 +- packages/external/core | 2 +- .../src/main/jni/realm_api_helpers.cpp | 5 +- .../kotlin/internal/query/SingleQuery.kt | 4 +- .../io/realm/kotlin/test/common/QueryTests.kt | 80 +++++++++ .../BacklinksNotificationsTests.kt | 167 ++++++++++++++++-- 6 files changed, 241 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3eff43fb8d..637824572b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ * None. ### Enhancements -* Added support for keypaths in `asFlow()` methods on objects. This makes it possible to control which properties will trigger change events, including properties on objects below the default nested limit of 4. (Issue [#661](https://github.com/realm/realm-kotlin/issues/661)) +* Added support for keypaths in `asFlow()` methods on objects and queries. This makes it possible to control which properties will trigger change events, including properties on objects below the default nested limit of 4. (Issue [#661](https://github.com/realm/realm-kotlin/issues/661)) ### Fixed * None. diff --git a/packages/external/core b/packages/external/core index e4b7aca95a..9cfa50342b 160000 --- a/packages/external/core +++ b/packages/external/core @@ -1 +1 @@ -Subproject commit e4b7aca95a20bf9e52d7c9b2f6389dcdb2a2ca8a +Subproject commit 9cfa50342b8ac8b28a59443486535f9485b52b9f diff --git a/packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp b/packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp index 2e89defdbd..761ca0aec7 100644 --- a/packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp +++ b/packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp @@ -1073,10 +1073,9 @@ jni_realm_create_key_path_array(const realm_t* realm, int user_key_paths_count, const char** user_key_paths) { - realm_key_path_array_t* out = nullptr; - bool result = realm_create_key_path_array(realm, object_class_key, user_key_paths_count, user_key_paths, &out); + realm_key_path_array_t* result = realm_create_key_path_array(realm, object_class_key, user_key_paths_count, user_key_paths); if (result) { - return out; + return result ; } else { auto env = get_env(); throw_last_error_as_java_exception(env); diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/SingleQuery.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/SingleQuery.kt index 7c74a69ab7..3db3de2c10 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/SingleQuery.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/SingleQuery.kt @@ -87,9 +87,9 @@ internal class SingleQuery constructor( } else { oldHead = newHead if (!oldHeadDeleted) { - newHead.asFlow() + newHead.asFlow(keyPaths) } else { - newHead.asFlow().onStart { emit(DeletedObjectImpl()) } + newHead.asFlow(keyPaths).onStart { emit(DeletedObjectImpl()) } } } } diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/QueryTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/QueryTests.kt index 3f7267c8a0..b1c69ffcfd 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/QueryTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/QueryTests.kt @@ -2946,6 +2946,86 @@ class QueryTests { } } + @Test + fun asFlow_results_withKeyPath() { + val channel = Channel>(1) + runBlocking { + val observer = async { + realm.query() + .asFlow(listOf("stringField")) + .collect { results -> + assertNotNull(results) + channel.send(results) + } + } + channel.receiveOrFail().let { resultsChange -> + assertIs>(resultsChange) + assertTrue(resultsChange.list.isEmpty()) + } + val obj = realm.writeBlocking { + copyToRealm(QuerySample()) + } + channel.receiveOrFail().let { resultsChange -> + assertIs>(resultsChange) + assertEquals(1, resultsChange.list.size) + } + realm.writeBlocking { + // Should not trigger notification + findLatest(obj)!!.intField = 42 + } + realm.writeBlocking { + // Should not trigger notification + findLatest(obj)!!.stringField = "update" + } + channel.receiveOrFail().let { resultsChange -> + assertIs>(resultsChange) + assertEquals(1, resultsChange.list.size) + } + observer.cancel() + channel.close() + } + } + + @Test + fun asFlow_objectBound_withKeyPath() { + val channel = Channel>(1) + runBlocking { + val observer = async { + realm.query() + .first() + .asFlow(listOf("stringField")) + .collect { change -> + assertNotNull(change) + channel.send(change) + } + } + channel.receiveOrFail().let { objChange -> + assertIs>(objChange) + } + val obj = realm.writeBlocking { + copyToRealm(QuerySample()) + } + channel.receiveOrFail().let { objChange -> + assertIs>(objChange) + } + realm.writeBlocking { + // Should not trigger notification + findLatest(obj)!!.intField = 42 + } + realm.writeBlocking { + // Should trigger notification + findLatest(obj)!!.stringField = "update" + } + channel.receiveOrFail().let { objChange -> + assertIs>(objChange) + assertEquals(1, objChange.changedFields.size) + assertEquals("stringField", objChange.changedFields.first()) + } + observer.cancel() + channel.close() + } + } + // ---------------- // Coercion helpers // ---------------- 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 e33130ea57..c206190f33 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 @@ -19,6 +19,7 @@ package io.realm.kotlin.test.common.notifications import io.realm.kotlin.Realm import io.realm.kotlin.RealmConfiguration import io.realm.kotlin.entities.Sample +import io.realm.kotlin.ext.query import io.realm.kotlin.notifications.InitialResults import io.realm.kotlin.notifications.ResultsChange import io.realm.kotlin.notifications.UpdatedResults @@ -410,32 +411,174 @@ class BacklinksNotificationsTests : RealmEntityNotificationTests { } @Test - override fun keyPath_topLevelProperty() { - TODO("Not yet implemented") + override fun keyPath_topLevelProperty() = runBlocking { + val c = Channel>(1) + realm.write { + copyToRealm( + Sample().apply { + this.stringField = "parent" + this.nullableObject = Sample().apply { + this.stringField = "child" + } + } + ) + } + val result: RealmResults = realm.query("stringField = 'child'").find() + assertEquals(1, result.first().objectBacklinks.size) + val observer = async { + result.asFlow(listOf("objectBacklinks")).collect { + c.trySend(it) + } + } + assertIs>(c.receiveOrFail()) + realm.write { + val child = findLatest(result.first())!! + assertEquals("child", child.stringField) + copyToRealm( + Sample().apply { + this.stringField = "newParent" + this.nullableObject = child + } + ) + } + c.receiveOrFail().let { resultsChange -> + assertIs>(resultsChange) + when (resultsChange) { + is UpdatedResults -> { + assertEquals(0, resultsChange.insertions.size) + assertEquals(1, resultsChange.changes.size) + assertEquals(0, resultsChange.deletions.size) + assertEquals(2, resultsChange.list.first().objectBacklinks.size) + } + else -> fail("Unexpected change: $resultsChange") + } + } + observer.cancel() + c.close() } @Test - override fun keyPath_nestedProperty() { - TODO("Not yet implemented") + override fun keyPath_nestedProperty() = runBlocking { + val c = Channel>(1) + realm.write { + copyToRealm( + Sample().apply { + this.stringField = "parent" + this.nullableObject = Sample().apply { + this.stringField = "child" + } + } + ) + } + val result: RealmResults = realm.query("stringField = 'child'").find() + assertEquals(1, result.first().objectBacklinks.size) + val observer = async { + result.asFlow(listOf("objectBacklinks.intField")).collect { + c.trySend(it) + } + } + assertIs>(c.receiveOrFail()) + realm.write { + // Update field that should not trigger a notification + findLatest(result.first())!!.booleanField = false + } + realm.write { + findLatest(result.first())!!.objectBacklinks.first().intField = 1 + } + c.receiveOrFail().let { resultsChange -> + assertIs>(resultsChange) + when (resultsChange) { + is UpdatedResults -> { + assertEquals(0, resultsChange.insertions.size) + assertEquals(1, resultsChange.changes.size) + assertEquals(0, resultsChange.deletions.size) + assertEquals(1, resultsChange.list.first().objectBacklinks.size) + } + else -> fail("Unexpected change: $resultsChange") + } + } + observer.cancel() + c.close() } @Test - override fun keyPath_propertyBelowDefaultLimit() { - TODO("Not yet implemented") + override fun keyPath_propertyBelowDefaultLimit() = runBlocking { + val c = Channel>(1) + realm.write { + copyToRealm( + Sample().apply { + this.intField = 1 + this.stringField = "parent" + this.nullableObject = Sample().apply { + this.stringField = "child" + this.nullableObject = Sample().apply { + this.stringField = "child-child" + this.nullableObject = Sample().apply { + this.stringField = "child-child-child" + this.nullableObject = Sample().apply { + this.stringField = "child-child-child" + this.nullableObject = Sample().apply { + this.stringField = "BottomChild" + } + } + } + } + } + } + ) + } + val results = realm.query("stringField = 'BottomChild'").find() + val observer = async { + results.asFlow(listOf("objectBacklinks.objectBacklinks.objectBacklinks.objectBacklinks.objectBacklinks.stringField")).collect { + c.trySend(it) + } + } + assertIs>(c.receiveOrFail()) + realm.write { + // Update field that should not trigger a notification + findLatest(results.first())!!.booleanField = true + } + realm.write { + // Update field that should trigger a notification + findLatest(results.first())!! + .objectBacklinks.first() + .objectBacklinks.first() + .objectBacklinks.first() + .objectBacklinks.first() + .objectBacklinks.first() + .stringField = "Bar" + } + c.receiveOrFail().let { resultsChange -> + assertIs>(resultsChange) + when (resultsChange) { + is ResultsChange<*> -> { + assertEquals(0, resultsChange.insertions.size) + assertEquals(1, resultsChange.changes.size) + assertEquals(0, resultsChange.deletions.size) + assertEquals(1, resultsChange.list.first().objectBacklinks.size) + } + else -> fail("Unexpected change: $resultsChange") + } + } + observer.cancel() + c.close() } @Test - override fun keyPath_unknownTopLevelProperty() { - TODO("Not yet implemented") + @Ignore // Already covered by RealmResultsNotificationTests + override fun keyPath_unknownTopLevelProperty() = runBlocking { + TODO() } @Test - override fun keyPath_unknownNestedProperty() { - TODO("Not yet implemented") + @Ignore // Already covered by RealmResultsNotificationTests + override fun keyPath_unknownNestedProperty() = runBlocking { + TODO() } @Test - override fun keyPath_invalidNestedProperty() { - TODO("Not yet implemented") + @Ignore // Already covered by RealmResultsNotificationTests + override fun keyPath_invalidNestedProperty() = runBlocking { + TODO() } } From 4f43141d973804c41502c36e6a3a73aa8c192f36 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 20 Nov 2023 13:43:23 +0100 Subject: [PATCH 14/21] Cleanup --- .../io/realm/kotlin/internal/interop/RealmInterop.kt | 2 +- .../io/realm/kotlin/internal/interop/RealmInterop.kt | 2 +- .../io/realm/kotlin/internal/interop/RealmInterop.kt | 9 ++++----- packages/external/core | 2 +- packages/jni-swig-stub/realm.i | 7 ++++--- .../kotlin/io/realm/kotlin/ext/BaseRealmObjectExt.kt | 3 ++- .../io/realm/kotlin/internal/SuspendableNotifier.kt | 2 +- .../kotlin/io/realm/kotlin/query/RealmElementQuery.kt | 6 +++++- .../kotlin/io/realm/kotlin/query/RealmSingleQuery.kt | 6 +++++- .../commonMain/kotlin/io/realm/kotlin/types/RealmList.kt | 4 ++-- .../commonMain/kotlin/io/realm/kotlin/types/RealmMap.kt | 2 +- .../commonMain/kotlin/io/realm/kotlin/types/RealmSet.kt | 4 ++-- 12 files changed, 29 insertions(+), 20 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 8d0b953157..8fa36f9e40 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 @@ -438,7 +438,7 @@ expect object RealmInterop { ): RealmObjectPointer? fun realm_object_delete(obj: RealmObjectPointer) - fun create_key_paths_array(realm: RealmPointer, clazz: ClassKey, keyPaths: List): RealmKeyPathArrayPointer + fun realm_create_key_paths_array(realm: RealmPointer, clazz: ClassKey, keyPaths: List): RealmKeyPathArrayPointer fun realm_object_add_notification_callback( obj: RealmObjectPointer, keyPaths: RealmKeyPathArrayPointer?, 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 de44e2bc52..3d5b077f5f 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 @@ -813,7 +813,7 @@ actual object RealmInterop { return realmc.realm_dictionary_is_valid(dictionary.cptr()) } - actual fun create_key_paths_array(realm: RealmPointer, clazz: ClassKey, keyPaths: List): RealmKeyPathArrayPointer { + actual fun realm_create_key_paths_array(realm: RealmPointer, clazz: ClassKey, keyPaths: List): RealmKeyPathArrayPointer { val ptr = realmc.jni_realm_create_key_path_array(realm.cptr(), clazz.key, keyPaths.size, keyPaths.toTypedArray()) return LongPointerWrapper(ptr) } 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 0ea6901a76..561cef5baa 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 @@ -1656,12 +1656,11 @@ actual object RealmInterop { checkedBooleanResult(realm_wrapper.realm_object_delete(obj.cptr())) } - actual fun create_key_paths_array(realm: RealmPointer, clazz: ClassKey, keyPaths: List): RealmKeyPathArrayPointer { + actual fun realm_create_key_paths_array(realm: RealmPointer, clazz: ClassKey, keyPaths: List): RealmKeyPathArrayPointer { memScoped { - val kps: CPointer>>> = keyPaths.toCStringArray(this) - val kp = allocArray>(1) - checkedBooleanResult(realm_wrapper.realm_create_key_path_array(realm.cptr(), clazz.key.toUInt(), keyPaths.size, kps, kp)) - return CPointerWrapper(kp[0]) + val userKeyPaths: CPointer>>> = keyPaths.toCStringArray(this) + val keyPathPointer = realm_wrapper.realm_create_key_path_array(realm.cptr(), clazz.key.toUInt(), keyPaths.size, userKeyPaths) + return CPointerWrapper(keyPathPointer) } } diff --git a/packages/external/core b/packages/external/core index 9cfa50342b..fce8171140 160000 --- a/packages/external/core +++ b/packages/external/core @@ -1 +1 @@ -Subproject commit 9cfa50342b8ac8b28a59443486535f9485b52b9f +Subproject commit fce8171140053c3aee19f6dac05b7007a5c1af47 diff --git a/packages/jni-swig-stub/realm.i b/packages/jni-swig-stub/realm.i index 42ef42b5b9..fcaae087eb 100644 --- a/packages/jni-swig-stub/realm.i +++ b/packages/jni-swig-stub/realm.i @@ -423,8 +423,9 @@ import static io.realm.kotlin.internal.interop.realm_errno_e.*; /* This cleans up the memory we malloc'd before the function call */ %typemap(freearg) char ** { int i; - for (i=0; i T.asFlow(keyPaths: List? = null): Flow> = runIfManaged { checkNotificationsAvailable() 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 888d791f18..b3e68aaa7a 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 @@ -96,7 +96,7 @@ internal class SuspendableNotifier( } internal fun , C> registerObserver(flowable: Observable, keyPaths: Pair>?): Flow { - val keypathsPtr: RealmKeyPathArrayPointer? = keyPaths?.let { RealmInterop.create_key_paths_array(realm.owner.realmReference.dbPointer, keyPaths.first, keyPaths.second) } + val keypathsPtr: RealmKeyPathArrayPointer? = keyPaths?.let { RealmInterop.realm_create_key_paths_array(realm.owner.realmReference.dbPointer, keyPaths.first, keyPaths.second) } return callbackFlow { val token: AtomicRef = kotlinx.atomicfu.atomic(NO_OP_NOTIFICATION_TOKEN) diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/query/RealmElementQuery.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/query/RealmElementQuery.kt index b2f61ede7c..78326031ea 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/query/RealmElementQuery.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/query/RealmElementQuery.kt @@ -60,8 +60,12 @@ public interface RealmElementQuery : Deleteable { * * **It is not allowed to call [asFlow] on queries generated from a [MutableRealm].** * - * @param keyPath TODO + * @param keyPaths An optional list of properties that defines when a change to the object will + * result in a change being emitted. Nested properties can be defined using a dotted + * syntax, e.g. `parent.child.name`. If no keypaths are provided, changes to all top-level + * properties and nested properties 4 levels down will trigger a change. * @return a flow representing changes to the [RealmResults] resulting from running this query. + * @throws IllegalArgumentException if an invalid keypath is provided. */ public fun asFlow(keyPath: List? = null): Flow> } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/query/RealmSingleQuery.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/query/RealmSingleQuery.kt index 8d730f14d2..e73eab1804 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/query/RealmSingleQuery.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/query/RealmSingleQuery.kt @@ -72,9 +72,13 @@ public interface RealmSingleQuery : Deleteable { * the elements in a timely manner the coroutine scope will be cancelled with a * [CancellationException]. * - * @param keyPaths TODO + * @param keyPaths An optional list of properties that defines when a change to the object will + * result in a change being emitted. Nested properties can be defined using a dotted + * syntex, e.g. `parent.child.name`. If no keypaths are provided, changes to all top-level + * properties and nested properties 4 levels down will trigger a change. * @return a flow representing changes to the [RealmObject] or [EmbeddedRealmObject] resulting from * running this query. + * @throws IllegalArgumentException if an invalid keypath is provided. */ public fun asFlow(keyPaths: List? = null): Flow> } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmList.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmList.kt index d741273578..b7df356419 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmList.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmList.kt @@ -74,10 +74,10 @@ public interface RealmList : MutableList, Deleteable { * * @param keyPaths An optional list of properties that defines when a change to the object will * result in a change being emitted. Nested properties can be defined using a dotted - * syntex, e.g. `parent.child.name`. If no keypaths are provided, changes to all top-level + * syntax, e.g. `parent.child.name`. If no keypaths are provided, changes to all top-level * properties and nested properties 4 levels down will trigger a change. * @return a flow representing changes to the list. - * @throws IllegalArgumentException if keypaths are provided for lists not containing objects. + * @throws IllegalArgumentException if an invalid keypath is provided. * @throws CancellationException if the stream produces changes faster than the consumer can * consume them and results in a buffer overflow. */ diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmMap.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmMap.kt index c45789afce..cb0ea30773 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmMap.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmMap.kt @@ -57,7 +57,7 @@ public interface RealmMap : MutableMap { * * @param keyPaths An optional list of properties that defines when a change to the object will * result in a change being emitted. Nested properties can be defined using a dotted - * syntex, e.g. `parent.child.name`. If no keypaths are provided, changes to all top-level + * syntax, e.g. `parent.child.name`. If no keypaths are provided, changes to all top-level * properties and nested properties 4 levels down will trigger a change. * @return a flow representing changes to the dictionary. * @throws IllegalArgumentException if keypaths are provided for maps not containing objects. diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmSet.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmSet.kt index 2b1852e61f..0098e19d0f 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmSet.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmSet.kt @@ -54,10 +54,10 @@ public interface RealmSet : MutableSet, Deleteable { * * @param keyPaths An optional list of properties that defines when a change to the object will * result in a change being emitted. Nested properties can be defined using a dotted - * syntex, e.g. `parent.child.name`. If no keypaths are provided, changes to all top-level + * syntax, e.g. `parent.child.name`. If no keypaths are provided, changes to all top-level * properties and nested properties 4 levels down will trigger a change. * @return a flow representing changes to the set. - * @throws IllegalArgumentException if keypaths are provided for sets not containing objects. + * @throws IllegalArgumentException if an invalid keypath is provided. * @throws CancellationException if the stream produces changes faster than the consumer can * consume them and results in a buffer overflow. */ From 9ec94d0b3f473e140c15427f5d924fca20170f91 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 20 Nov 2023 15:12:30 +0100 Subject: [PATCH 15/21] Fix static analysis --- .../kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt | 1 - 1 file changed, 1 deletion(-) 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 561cef5baa..f5dfe83a64 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 @@ -105,7 +105,6 @@ import realm_wrapper.realm_http_header_t import realm_wrapper.realm_http_request_method import realm_wrapper.realm_http_request_t import realm_wrapper.realm_http_response_t -import realm_wrapper.realm_key_path_array_t import realm_wrapper.realm_link_t import realm_wrapper.realm_list_t import realm_wrapper.realm_object_id_t From 4a2562299c7e4314e8de76354dae37f67778002d Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 22 Nov 2023 17:05:38 +0100 Subject: [PATCH 16/21] PR feedback --- .../kotlin/internal/interop/RealmInterop.kt | 1 - .../kotlin/internal/interop/RealmInterop.kt | 2 +- packages/external/core | 2 +- .../src/main/jni/realm_api_helpers.cpp | 16 ---- .../src/main/jni/realm_api_helpers.h | 6 -- .../realm/kotlin/internal/KeyPathFlowable.kt | 4 - .../internal/ObjectBoundRealmResults.kt | 7 +- .../io/realm/kotlin/internal/RealmImpl.kt | 2 +- .../kotlin/internal/RealmListInternal.kt | 8 +- .../realm/kotlin/internal/RealmMapInternal.kt | 8 +- .../realm/kotlin/internal/RealmSetInternal.kt | 8 +- .../internal/query/ObjectBoundQueries.kt | 11 ++- .../kotlin/internal/query/ScalarQuery.kt | 1 - .../realm/kotlin/internal/util/Validation.kt | 17 ++++ .../io/realm/kotlin/query/RealmResults.kt | 8 +- .../kotlin/io/realm/kotlin/types/RealmList.kt | 12 +-- .../kotlin/io/realm/kotlin/types/RealmMap.kt | 13 +-- .../kotlin/io/realm/kotlin/types/RealmSet.kt | 12 +-- .../io/realm/kotlin/test/common/QueryTests.kt | 2 +- .../BacklinksNotificationsTests.kt | 66 +++++++++++++++ .../RealmDictionaryNotificationsTests.kt | 82 +++++++++++++++++++ .../RealmListNotificationsTests.kt | 79 +++++++++++++++++- .../notifications/RealmNotificationsTests.kt | 40 +-------- .../RealmObjectNotificationsTests.kt | 55 +++++++++++++ .../RealmResultsNotificationsTests.kt | 62 +++++++++++++- .../RealmSetNotificationsTests.kt | 76 +++++++++++++++++ .../kotlin/test/common/utils/FlowableTests.kt | 18 ---- .../test/common/utils/KeyPathFlowableTests.kt | 31 +++++++ .../utils/RealmEntityNotificationTests.kt | 2 +- 29 files changed, 519 insertions(+), 132 deletions(-) create mode 100644 packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/utils/KeyPathFlowableTests.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 8fa36f9e40..b0e3447796 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 @@ -56,7 +56,6 @@ const val UUID_BYTES_SIZE = 16 interface CapiT interface RealmConfigT : CapiT interface RealmSchemaT : CapiT -interface RealmObjectSchemaT : CapiT interface RealmT : CapiT interface LiveRealmT : RealmT interface FrozenRealmT : RealmT 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 3d5b077f5f..738d3811d5 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 @@ -814,7 +814,7 @@ actual object RealmInterop { } actual fun realm_create_key_paths_array(realm: RealmPointer, clazz: ClassKey, keyPaths: List): RealmKeyPathArrayPointer { - val ptr = realmc.jni_realm_create_key_path_array(realm.cptr(), clazz.key, keyPaths.size, keyPaths.toTypedArray()) + val ptr = realmc.realm_create_key_path_array(realm.cptr(), clazz.key, keyPaths.size, keyPaths.toTypedArray()) return LongPointerWrapper(ptr) } diff --git a/packages/external/core b/packages/external/core index fce8171140..6cca07f5e9 160000 --- a/packages/external/core +++ b/packages/external/core @@ -1 +1 @@ -Subproject commit fce8171140053c3aee19f6dac05b7007a5c1af47 +Subproject commit 6cca07f5e959df94381f557ccd29dc83c0519484 diff --git a/packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp b/packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp index 761ca0aec7..b047236a6f 100644 --- a/packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp +++ b/packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp @@ -1067,22 +1067,6 @@ realm_create_generic_scheduler() { return new realm_scheduler_t { realm::util::Scheduler::make_dummy() }; } -realm_key_path_array_t* -jni_realm_create_key_path_array(const realm_t* realm, - const realm_class_key_t object_class_key, - int user_key_paths_count, - const char** user_key_paths) -{ - realm_key_path_array_t* result = realm_create_key_path_array(realm, object_class_key, user_key_paths_count, user_key_paths); - if (result) { - return result ; - } else { - auto env = get_env(); - throw_last_error_as_java_exception(env); - return nullptr; - } -} - void realm_property_info_t_cleanup(realm_property_info_t* value) { delete[] value->link_origin_property_name; diff --git a/packages/jni-swig-stub/src/main/jni/realm_api_helpers.h b/packages/jni-swig-stub/src/main/jni/realm_api_helpers.h index da9bbc33a9..4f466beef2 100644 --- a/packages/jni-swig-stub/src/main/jni/realm_api_helpers.h +++ b/packages/jni-swig-stub/src/main/jni/realm_api_helpers.h @@ -143,12 +143,6 @@ realm_sync_thread_error(realm_userdata_t userdata, const char* error); realm_scheduler_t* realm_create_generic_scheduler(); -realm_key_path_array_t* -jni_realm_create_key_path_array(const realm_t* realm, - const realm_class_key_t object_class_key, - int user_key_paths_count, - const char** user_key_paths); - void realm_property_info_t_cleanup(realm_property_info_t* value); diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/KeyPathFlowable.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/KeyPathFlowable.kt index 41528a7afb..ecf54903fb 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/KeyPathFlowable.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/KeyPathFlowable.kt @@ -31,7 +31,3 @@ import kotlinx.coroutines.flow.Flow internal interface KeyPathFlowable { fun asFlow(keyPaths: List? = null): Flow } - -internal interface Flowable { - fun asFlow(): Flow -} diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/ObjectBoundRealmResults.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/ObjectBoundRealmResults.kt index a7228b2c8a..93d501ae9a 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/ObjectBoundRealmResults.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/ObjectBoundRealmResults.kt @@ -61,7 +61,7 @@ internal class ObjectBoundRealmResults( */ override fun asFlow(keyPaths: List?): Flow> { - return realmResults.asFlow(keyPaths).bind(targetObject, keyPaths) + return realmResults.asFlow(keyPaths).bind(targetObject) } override fun delete() { @@ -79,7 +79,6 @@ internal class ObjectBoundRealmResults( * deleted. It is used on sub-queries and backlinks. */ internal fun Flow.bind( - reference: RealmObjectReference, - keyPaths: List? + reference: RealmObjectReference ): Flow = - this.terminateWhen(reference.asFlow(keyPaths)) { it is DeletedObject } + this.terminateWhen(reference.asFlow()) { it is DeletedObject } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmImpl.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmImpl.kt index 850297ea14..adfef17780 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmImpl.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmImpl.kt @@ -53,7 +53,7 @@ import kotlin.reflect.KClass // TODO Public due to being accessed from `SyncedRealmContext` public class RealmImpl private constructor( configuration: InternalConfiguration, -) : BaseRealmImpl(configuration), Realm, InternalTypedRealm, Flowable> { +) : BaseRealmImpl(configuration), Realm, InternalTypedRealm { public val notificationScheduler: LiveRealmContext = configuration.notificationDispatcherFactory.createLiveRealmContext() 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 d558ffc3b6..9fe3ff398c 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 @@ -33,6 +33,7 @@ import io.realm.kotlin.internal.interop.getterScope import io.realm.kotlin.internal.interop.inputScope import io.realm.kotlin.internal.query.ObjectBoundQuery import io.realm.kotlin.internal.query.ObjectQuery +import io.realm.kotlin.internal.util.Validation import io.realm.kotlin.notifications.ListChange import io.realm.kotlin.notifications.internal.DeletedListImpl import io.realm.kotlin.notifications.internal.InitialListImpl @@ -117,11 +118,8 @@ internal class ManagedRealmList( override fun asFlow(keyPaths: List?): Flow> { operator.realmReference.checkClosed() val keyPathInfo = keyPaths?.let { - val classKey = if (operator is RealmObjectListOperator) { - operator.classKey - } else { - throw IllegalArgumentException("Keypaths are only supported for lists of objects.") - } + Validation.isType>(operator, "Keypaths are only supported for lists of objects.") + val classKey = operator.classKey Pair(classKey, keyPaths) } return operator.realmReference.owner.registerObserver(this, keyPathInfo) 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 440a20d4a3..9f4e621785 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 @@ -40,6 +40,7 @@ import io.realm.kotlin.internal.interop.getterScope import io.realm.kotlin.internal.interop.inputScope import io.realm.kotlin.internal.query.ObjectBoundQuery import io.realm.kotlin.internal.query.ObjectQuery +import io.realm.kotlin.internal.util.Validation import io.realm.kotlin.notifications.MapChange import io.realm.kotlin.notifications.MapChangeSet import io.realm.kotlin.notifications.internal.DeletedDictionaryImpl @@ -103,11 +104,8 @@ internal abstract class ManagedRealmMap constructor( override fun asFlow(keyPaths: List?): Flow> { operator.realmReference.checkClosed() val keyPathInfo = keyPaths?.let { - val classKey = if (operator is RealmObjectMapOperator) { - operator.classKey - } else { - throw IllegalArgumentException("Keypaths are only supported for maps of objects.") - } + Validation.isType>(operator, "Keypaths are only supported for maps of objects.") + val classKey = operator.classKey // if (operator is RealmObjectMapOperator) { Pair(classKey, keyPaths) } return operator.realmReference.owner.registerObserver(this, keyPathInfo) 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 ec3c2115a8..72073c8a94 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 @@ -33,6 +33,7 @@ import io.realm.kotlin.internal.interop.getterScope import io.realm.kotlin.internal.interop.inputScope import io.realm.kotlin.internal.query.ObjectBoundQuery import io.realm.kotlin.internal.query.ObjectQuery +import io.realm.kotlin.internal.util.Validation import io.realm.kotlin.notifications.SetChange import io.realm.kotlin.notifications.internal.DeletedSetImpl import io.realm.kotlin.notifications.internal.InitialSetImpl @@ -162,11 +163,8 @@ internal class ManagedRealmSet constructor( override fun asFlow(keyPaths: List?): Flow> { val keyPathInfo = keyPaths?.let { - val classKey = if (operator is RealmObjectSetOperator) { - operator.classKey - } else { - throw IllegalArgumentException("Keypaths are only supported for sets of objects.") - } + Validation.isType>(operator, "Keypaths are only supported for sets of objects.") + val classKey = operator.classKey Pair(classKey, keyPaths) } return operator.realmReference.owner.registerObserver(this, keyPathInfo) diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/ObjectBoundQueries.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/ObjectBoundQueries.kt index e679ab524f..f1db1ec0b1 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/ObjectBoundQueries.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/query/ObjectBoundQueries.kt @@ -48,9 +48,8 @@ internal class ObjectBoundQuery( realmQuery.query(filter, *arguments) ) - override fun asFlow(keyPaths: List?): Flow> = realmQuery.asFlow().bind( - targetObject, - keyPaths + override fun asFlow(keyPaths: List?): Flow> = realmQuery.asFlow(keyPaths).bind( + targetObject ) override fun sort(property: String, sortOrder: Sort): RealmQuery = ObjectBoundQuery( @@ -110,19 +109,19 @@ internal class ObjectBoundRealmSingleQuery( val targetObject: RealmObjectReference<*>, val realmQuery: RealmSingleQuery ) : RealmSingleQuery by realmQuery { - override fun asFlow(keyPaths: List?): Flow> = realmQuery.asFlow(keyPaths).bind(targetObject, keyPaths) + override fun asFlow(keyPaths: List?): Flow> = realmQuery.asFlow(keyPaths).bind(targetObject) } internal class ObjectBoundRealmScalarNullableQuery( val targetObject: RealmObjectReference<*>, val realmQuery: RealmScalarNullableQuery ) : RealmScalarNullableQuery by realmQuery { - override fun asFlow(): Flow = realmQuery.asFlow().bind(targetObject, null) + override fun asFlow(): Flow = realmQuery.asFlow().bind(targetObject) } internal class ObjectBoundRealmScalarQuery( val targetObject: RealmObjectReference<*>, val realmQuery: RealmScalarQuery ) : RealmScalarQuery by realmQuery { - override fun asFlow(): Flow = realmQuery.asFlow().bind(targetObject, null) + override fun asFlow(): Flow = realmQuery.asFlow().bind(targetObject) } 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 7c65abbf71..e3556485f2 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 @@ -96,7 +96,6 @@ internal class CountQuery constructor( override fun asFlow(): Flow { realmReference.checkClosed() return realmReference.owner - // TODO .registerObserver(this, null) .map { it.list.size.toLong() diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/util/Validation.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/util/Validation.kt index 1a76d2ee98..7172efdc20 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/util/Validation.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/util/Validation.kt @@ -16,6 +16,9 @@ package io.realm.kotlin.internal.util +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract + /** * Collection of validation methods to ensure uniform input validation. */ @@ -30,6 +33,20 @@ public object Validation { return value } + /** + * Verifies that a given argument has a given type. If yes, it will be implicitly cast + * to that type, otherwise an IllegalArgumentException is thrown with the provided error message. + */ + @OptIn(ExperimentalContracts::class) + public inline fun isType(arg: Any?, errorMessage: String) { + contract { + returns() implies (arg is T) + } + if (arg !is T) { + throw IllegalArgumentException(errorMessage) + } + } + public fun isEmptyString(str: String?): Boolean { return str == null || str.length == 0 } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/query/RealmResults.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/query/RealmResults.kt index 2da94c8938..12636815ac 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/query/RealmResults.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/query/RealmResults.kt @@ -69,7 +69,13 @@ public interface RealmResults : List, Deleteable, Versio * the elements in a timely manner the coroutine scope will be cancelled with a * [CancellationException]. * - * @param keyPath TODO + * @param keyPaths An optional list of model class properties that defines when a change to + * objects inside the RealmResults will result in a change being emitted. Nested properties can + * be defined using a dotted syntax, e.g. `parent.child.name`. If no keypaths are provided, + * changes to all top-level properties and nested properties up to 4 levels down will trigger a + * change. + * @return a flow representing changes to the list. + * @throws IllegalArgumentException if an invalid keypath is provided. * @return a flow representing changes to the RealmResults. */ public fun asFlow(keyPaths: List? = null): Flow> diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmList.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmList.kt index b7df356419..28c3ec35b5 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmList.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmList.kt @@ -72,12 +72,14 @@ public interface RealmList : MutableList, Deleteable { * the elements in a timely manner the coroutine scope will be cancelled with a * [CancellationException]. * - * @param keyPaths An optional list of properties that defines when a change to the object will - * result in a change being emitted. Nested properties can be defined using a dotted - * syntax, e.g. `parent.child.name`. If no keypaths are provided, changes to all top-level - * properties and nested properties 4 levels down will trigger a change. + * @param keyPaths An optional list of model class properties that defines when a change to + * objects inside the list will result in a change being emitted. Nested properties can be + * defined using a dotted syntax, e.g. `parent.child.name`. If no keypaths are provided, changes + * to all top-level properties and nested properties up to 4 levels down will trigger a change. + * Keypaths are only supported for lists of objects. * @return a flow representing changes to the list. - * @throws IllegalArgumentException if an invalid keypath is provided. + * @throws IllegalArgumentException if an invalid keypath is provided or the RealmList does not + * contain realm objects. * @throws CancellationException if the stream produces changes faster than the consumer can * consume them and results in a buffer overflow. */ diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmMap.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmMap.kt index cb0ea30773..79870d8e42 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmMap.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmMap.kt @@ -55,12 +55,15 @@ public interface RealmMap : MutableMap { * the elements in a timely manner the coroutine scope will be cancelled with a * [CancellationException]. * - * @param keyPaths An optional list of properties that defines when a change to the object will - * result in a change being emitted. Nested properties can be defined using a dotted - * syntax, e.g. `parent.child.name`. If no keypaths are provided, changes to all top-level - * properties and nested properties 4 levels down will trigger a change. + * @param keyPaths An optional list of model class properties that defines when a change to + * objects inside the map will result in a change being emitted. For maps, keypaths are + * evaluted based on the values of the map. This means that keypaths are only supported + * for maps containing realm objects. Nested properties can be defined using a dotted syntax, + * e.g. `parent.child.name`. If no keypaths are provided, changes to all top-level properties + * and nested properties up to 4 levels down will trigger a change * @return a flow representing changes to the dictionary. - * @throws IllegalArgumentException if keypaths are provided for maps not containing objects. + * @throws IllegalArgumentException if keypaths are invalid or the map does not contain realm + * objects. * @throws CancellationException if the stream produces changes faster than the consumer can * consume them and results in a buffer overflow. */ diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmSet.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmSet.kt index 0098e19d0f..19badece7e 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmSet.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmSet.kt @@ -52,12 +52,14 @@ public interface RealmSet : MutableSet, Deleteable { * the elements in a timely manner the coroutine scope will be cancelled with a * [CancellationException]. * - * @param keyPaths An optional list of properties that defines when a change to the object will - * result in a change being emitted. Nested properties can be defined using a dotted - * syntax, e.g. `parent.child.name`. If no keypaths are provided, changes to all top-level - * properties and nested properties 4 levels down will trigger a change. + * @param keyPaths An optional list of model class properties that defines when a change to + * objects inside the set will result in a change being emitted. Nested properties can be + * defined using a dotted syntax, e.g. `parent.child.name`. If no keypaths are provided, changes + * to all top-level properties and nested properties up to 4 levels down will trigger a change. + * Keypaths are only supported for sets of objects. * @return a flow representing changes to the set. - * @throws IllegalArgumentException if an invalid keypath is provided. + * @throws IllegalArgumentException if an invalid keypath is provided or the set does not + * contain realm objects. * @throws CancellationException if the stream produces changes faster than the consumer can * consume them and results in a buffer overflow. */ diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/QueryTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/QueryTests.kt index b1c69ffcfd..f43d6e74b3 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/QueryTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/QueryTests.kt @@ -2974,7 +2974,7 @@ class QueryTests { findLatest(obj)!!.intField = 42 } realm.writeBlocking { - // Should not trigger notification + // Should trigger notification findLatest(obj)!!.stringField = "update" } channel.receiveOrFail().let { resultsChange -> 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 c206190f33..1a172c15cf 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 @@ -493,6 +493,72 @@ class BacklinksNotificationsTests : RealmEntityNotificationTests { assertEquals(1, resultsChange.changes.size) assertEquals(0, resultsChange.deletions.size) assertEquals(1, resultsChange.list.first().objectBacklinks.size) + // This starts at 42, s if the first write triggers a change event, it will + // catch it here. + assertEquals(resultsChange.list.first().objectBacklinks.first().intField, 1) + } + else -> fail("Unexpected change: $resultsChange") + } + } + observer.cancel() + c.close() + } + + @Test + override fun keyPath_defaultDepth() = runBlocking { + val c = Channel>(1) + realm.write { + copyToRealm( + Sample().apply { + this.intField = 1 + this.stringField = "parent" + this.nullableObject = Sample().apply { + this.stringField = "child" + this.nullableObject = Sample().apply { + this.stringField = "child-child" + this.nullableObject = Sample().apply { + this.stringField = "child-child-child" + this.nullableObject = Sample().apply { + this.stringField = "child-child-child" + this.nullableObject = Sample().apply { + this.stringField = "BottomChild" + } + } + } + } + } + } + ) + } + val results = realm.query("stringField = 'BottomChild'").find() + val observer = async { + // Default keypath + results.asFlow().collect { + c.trySend(it) + } + } + assertIs>(c.receiveOrFail()) + realm.write { + // Update field that should not trigger a notification + findLatest(results.first())!! + .objectBacklinks.first() + .objectBacklinks.first() + .objectBacklinks.first() + .objectBacklinks.first() + .objectBacklinks.first() + .stringField = "Bar" + } + realm.write { + // Update field that should trigger a notification + findLatest(results.first())!!.intField = 1 + } + c.receiveOrFail().let { resultsChange -> + assertIs>(resultsChange) + when (resultsChange) { + is ResultsChange<*> -> { + // Default value is 42, so if this event is triggered by the first write + // this assert will fail + assertEquals(1, resultsChange.list.first().intField) } else -> fail("Unexpected change: $resultsChange") } 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 20ce32d3cd..15f65247ec 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 @@ -464,6 +464,9 @@ class RealmDictionaryNotificationsTests : RealmEntityNotificationTests { is UpdatedMap -> { assertEquals(1, mapChange.changes.size) assertEquals("1", mapChange.changes.first()) + // This starts as Realm, so if the first write triggers a change event, it will + // catch it here. + assertEquals("Foo", mapChange.map["1"]!!.stringField) } else -> fail("Unexpected change: $mapChange") } @@ -518,6 +521,85 @@ class RealmDictionaryNotificationsTests : RealmEntityNotificationTests { c.close() } + @Test + override fun keyPath_defaultDepth() = runBlocking { + val c = Channel>(1) + val dict = realm.write { + copyToRealm( + RealmDictionaryContainer().apply { + this.id = 1 + this.stringField = "parent" + this.nullableObjectDictionaryField = realmDictionaryOf( + "parent" to RealmDictionaryContainer().apply { + this.stringField = "child" + this.nullableObjectDictionaryField = realmDictionaryOf( + "child" to RealmDictionaryContainer().apply { + this.stringField = "child-child" + this.nullableObjectDictionaryField = realmDictionaryOf( + "child-child" to RealmDictionaryContainer().apply { + this.stringField = "child-child-child" + this.nullableObjectDictionaryField = realmDictionaryOf( + "child-child-child" to RealmDictionaryContainer().apply { + this.stringField = "child-child-child-child" + this.nullableObjectDictionaryField = realmDictionaryOf( + "child-child-child-child" to RealmDictionaryContainer().apply { + this.stringField = "child-child-child-child-child" + this.nullableObjectDictionaryField = realmDictionaryOf( + "child-child-child-child-child" to RealmDictionaryContainer().apply { + this.stringField = "BottomChild" + } + ) + } + ) + } + ) + } + ) + } + ) + } + ) + } + ) + }.nullableObjectDictionaryField + val observer = async { + // Default keypath + dict.asFlow().collect { + c.trySend(it) + } + } + assertIs>(c.receiveOrFail()) + realm.write { + // Update field that should not trigger a notification + val obj = findLatest(dict.values.first()!!)!! + .nullableObjectDictionaryField.values.first()!! + .nullableObjectDictionaryField.values.first()!! + .nullableObjectDictionaryField.values.first()!! + .nullableObjectDictionaryField.values.first()!! + .nullableObjectDictionaryField.values.first()!! + obj.stringField = "Bar" + } + realm.write { + // Update field that should trigger a notification + findLatest(dict.values.first()!!)!!.stringField = "Parent change" + } + c.receiveOrFail().let { mapChange -> + assertIs>(mapChange) + when (mapChange) { + is MapChange -> { + // Core will only report something changed to the top-level property. + assertEquals(1, mapChange.changes.size) + // Default value is Realm, so if this event is triggered by the first write + // this assert will fail + assertEquals("Parent change", mapChange.map.values.first()!!.stringField) + } + else -> fail("Unexpected change: $mapChange") + } + } + observer.cancel() + c.close() + } + @Test override fun keyPath_propertyBelowDefaultLimit() = runBlocking { val c = Channel>(1) 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 06550eb10e..8fcd21909d 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 @@ -501,10 +501,13 @@ class RealmListNotificationsTests : RealmEntityNotificationTests { findLatest(list.first())!!.stringField = "Foo" } c.receiveOrFail().let { listChange -> - assertIs>(listChange) + assertIs>(listChange) when (listChange) { is UpdatedList -> { assertEquals(1, listChange.changes.size) + // This starts as Realm, so if the first write triggers a change event, it will + // catch it here. + assertEquals("Foo", listChange.list.first().stringField) } else -> fail("Unexpected change: $listChange") } @@ -559,6 +562,80 @@ class RealmListNotificationsTests : RealmEntityNotificationTests { c.close() } + @Test + override fun keyPath_defaultDepth() = runBlocking { + val c = Channel>(1) + val list = realm.write { + copyToRealm( + RealmListContainer().apply { + this.id = 1 + this.stringField = "parent" + this.objectListField = realmListOf( + RealmListContainer().apply { + this.stringField = "child" + this.objectListField = realmListOf( + RealmListContainer().apply { + this.stringField = "child-child" + this.objectListField = realmListOf( + RealmListContainer().apply { + this.stringField = "child-child-child" + this.objectListField = realmListOf( + RealmListContainer().apply { + this.stringField = "child-child-child-child" + this.objectListField = realmListOf( + RealmListContainer().apply { + this.stringField = "child-child-child-child-child" + this.objectListField = realmListOf( + RealmListContainer().apply { + this.stringField = "BottomChild" + } + ) + } + ) + } + ) + } + ) + } + ) + } + ) + } + ) + }.objectListField + val observer = async { + // Default keypath + list.asFlow().collect { + c.trySend(it) + } + } + assertIs>(c.receiveOrFail()) + realm.write { + // Update below the default limit should not trigger a notification + val obj = findLatest(list.first())!!.objectListField.first().objectListField.first().objectListField.first().objectListField.first().objectListField.first() + obj.stringField = "Bar" + } + realm.write { + // Update field that should trigger a notification + findLatest(list.first())!!.id = 1 + } + c.receiveOrFail().let { listChange -> + assertIs>(listChange) + when (listChange) { + is ListChange -> { + // Core will only report something changed to the top-level property. + assertEquals(1, listChange.changes.size) + // Default value is -1, so if this event is triggered by the first write + // this assert will fail + assertEquals(1, listChange.list.first().id) + } + else -> fail("Unexpected change: $listChange") + } + } + observer.cancel() + c.close() + } + @Test override fun keyPath_propertyBelowDefaultLimit() = runBlocking { val c = Channel>(1) diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmNotificationsTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmNotificationsTests.kt index b42a5391b2..26f85807f2 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmNotificationsTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmNotificationsTests.kt @@ -189,49 +189,13 @@ class RealmNotificationsTests : FlowableTests { @Test @Ignore override fun closeRealmInsideFlowThrows() { - // Keypaths not supported by Realm notifications + TODO("Wait for a Global change listener to become available") } @Test @Ignore override fun closingRealmDoesNotCancelFlows() { - // Keypaths not supported by Realm notifications - } - - @Test - @Ignore - override fun keyPath_topLevelProperty() { - // Keypaths not supported by Realm notifications - } - - @Test - @Ignore - override fun keyPath_nestedProperty() { - // Keypaths not supported by Realm notifications - } - - @Test - @Ignore - override fun keyPath_propertyBelowDefaultLimit() { - // Keypaths not supported by Realm notifications - } - - @Test - @Ignore - override fun keyPath_unknownTopLevelProperty() { - // Keypaths not supported by Realm notifications - } - - @Test - @Ignore - override fun keyPath_unknownNestedProperty() { - // Keypaths not supported by Realm notifications - } - - @Test - @Ignore - override fun keyPath_invalidNestedProperty() { - // Keypaths not supported by Realm notifications + TODO("Wait for a Global change listener to become available") } @Test 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 0d5cdfed22..0ccec3a602 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 @@ -379,6 +379,61 @@ class RealmObjectNotificationsTests : RealmEntityNotificationTests { c.close() } + @Test + override fun keyPath_defaultDepth() = runBlocking { + val c = Channel>(1) + val obj: Sample = realm.write { + copyToRealm( + Sample().apply { + this.stringField = "parent" + this.nullableObject = Sample().apply { + this.stringField = "child" + this.nullableObject = Sample().apply { + this.stringField = "child-child" + this.nullableObject = Sample().apply { + this.stringField = "child-child-child" + this.nullableObject = Sample().apply { + this.stringField = "child-child-child" + this.nullableObject = Sample().apply { + this.stringField = "BottomChild" + } + } + } + } + } + } + ) + } + val observer = async { + // Default keypath + obj.asFlow().collect { + c.trySend(it) + } + } + assertIs>(c.receiveOrFail()) + realm.write { + // Update field that should not trigger a notification + findLatest(obj)!!.nullableObject!!.nullableObject!!.nullableObject!!.nullableObject!!.nullableObject!!.intField = 1 + } + realm.write { + // Update field that should trigger a notification + findLatest(obj)!!.stringField = "Parent change" + } + c.receiveOrFail().let { objectChange -> + assertIs>(objectChange) + when (objectChange) { + is UpdatedObject -> { + // Default value is Realm, so if this event is triggered by the first write + // this assert will fail + assertEquals("Parent change", objectChange.obj.stringField) + } + else -> fail("Unexpected change: $objectChange") + } + } + observer.cancel() + c.close() + } + @Test override fun keyPath_propertyBelowDefaultLimit() = runBlocking { val c = Channel>(1) diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmResultsNotificationsTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmResultsNotificationsTests.kt index 5a1c648dcc..2a6cadd5d3 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmResultsNotificationsTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmResultsNotificationsTests.kt @@ -33,6 +33,7 @@ import io.realm.kotlin.test.common.OBJECT_VALUES import io.realm.kotlin.test.common.OBJECT_VALUES2 import io.realm.kotlin.test.common.OBJECT_VALUES3 import io.realm.kotlin.test.common.utils.FlowableTests +import io.realm.kotlin.test.common.utils.KeyPathFlowableTests import io.realm.kotlin.test.common.utils.assertIsChangeSet import io.realm.kotlin.test.platform.PlatformUtils import io.realm.kotlin.test.util.receiveOrFail @@ -51,7 +52,7 @@ import kotlin.test.assertNotNull import kotlin.test.assertTrue import kotlin.test.fail -class RealmResultsNotificationsTests : FlowableTests { +class RealmResultsNotificationsTests : FlowableTests, KeyPathFlowableTests { lateinit var tmpDir: String lateinit var configuration: RealmConfiguration @@ -464,6 +465,65 @@ class RealmResultsNotificationsTests : FlowableTests { c.close() } + override fun keyPath_defaultDepth() = runBlocking { + val c = Channel>(1) + realm.write { + copyToRealm( + Sample().apply { + this.intField = 1 + this.stringField = "parent" + this.nullableObject = Sample().apply { + this.stringField = "child" + this.nullableObject = Sample().apply { + this.stringField = "child-child" + this.nullableObject = Sample().apply { + this.stringField = "child-child-child" + this.nullableObject = Sample().apply { + this.stringField = "child-child-child" + this.nullableObject = Sample().apply { + this.stringField = "BottomChild" + } + } + } + } + } + } + ) + } + val results = realm.query("intField = 1").find() + assertEquals(1, results.size) + val observer = async { + // Default keypath + results.asFlow().collect { + c.trySend(it) + } + } + assertIs>(c.receiveOrFail()) + realm.write { + // Update below the default limit should not trigger a notification + findLatest(results.first())!!.nullableObject!!.nullableObject!!.nullableObject!!.nullableObject!!.nullableObject!!.stringField = "Bar" + } + realm.write { + // Update field that should trigger a notification + findLatest(results.first())!!.intField = 1 + } + c.receiveOrFail().let { resultsChange -> + assertIs>(resultsChange) + when (resultsChange) { + is ResultsChange<*> -> { + // Core will only report something changed to the top-level property. + assertEquals(1, resultsChange.changes.size) + // Default value is 42, so if this event is triggered by the first write + // this assert will fail + assertEquals(1, resultsChange.list.first().intField) + } + else -> fail("Unexpected change: $resultsChange") + } + } + observer.cancel() + c.close() + } + @Test override fun keyPath_propertyBelowDefaultLimit() = runBlocking { val c = Channel>(1) 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 834403fc7c..38c5135b9f 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 @@ -435,6 +435,82 @@ class RealmSetNotificationsTests : RealmEntityNotificationTests { c.close() } + @Test + override fun keyPath_defaultDepth() = runBlocking { + val c = Channel>(1) + val objectSet: RealmSet = realm.write { + copyToRealm( + RealmSetContainer().apply { + this.id = 1 + this.stringField = "parent" + this.objectSetField = realmSetOf( + RealmSetContainer().apply { + this.stringField = "child" + this.objectSetField = realmSetOf( + RealmSetContainer().apply { + this.stringField = "child-child" + this.objectSetField = realmSetOf( + RealmSetContainer().apply { + this.stringField = "child-child-child" + this.objectSetField = realmSetOf( + RealmSetContainer().apply { + this.stringField = "child-child-child-child" + this.objectSetField = realmSetOf( + RealmSetContainer().apply { + this.stringField = "child-child-child-child-child" + this.objectSetField = realmSetOf( + RealmSetContainer().apply { + this.stringField = "BottomChild" + } + ) + } + ) + } + ) + } + ) + } + ) + } + ) + } + ) + }.objectSetField + val observer = async { + // Default keypath + objectSet.asFlow().collect { + c.trySend(it) + } + } + assertIs>(c.receiveOrFail()) + realm.write { + // Update below the default limit should not trigger a notification + val obj = findLatest(objectSet.first())!!.objectSetField.first().objectSetField.first().objectSetField.first().objectSetField.first().objectSetField.first() + obj.stringField = "Bar" + } + realm.write { + findLatest(objectSet.first())!!.stringField = "Bar" + } + realm.write { + } + c.receiveOrFail().let { setChange -> + assertIs>(setChange) + when (setChange) { + is SetChange -> { + // Core will only report something changed to the top-level property. + assertEquals(0, setChange.insertions) + assertEquals(0, setChange.deletions) + // Default value is Realm, so if this event is triggered by the first write + // this assert will fail + assertEquals("Bar", setChange.set.first().stringField) + } + else -> fail("Unexpected change: $setChange") + } + } + observer.cancel() + c.close() + } + @Test override fun keyPath_propertyBelowDefaultLimit() = runBlocking { val c = Channel>(1) diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/utils/FlowableTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/utils/FlowableTests.kt index 7556b641ef..1b43a545b5 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/utils/FlowableTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/utils/FlowableTests.kt @@ -49,24 +49,6 @@ interface FlowableTests { @Test fun closingRealmDoesNotCancelFlows() - @Test - fun keyPath_topLevelProperty() - - @Test - fun keyPath_nestedProperty() - - @Test - fun keyPath_propertyBelowDefaultLimit() - - @Test - fun keyPath_unknownTopLevelProperty() - - @Test - fun keyPath_unknownNestedProperty() - - @Test - fun keyPath_invalidNestedProperty() - // @Test // fun addChangeListener_emitOnProvidedDispatcher() { // // FIXME Implement in another PR diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/utils/KeyPathFlowableTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/utils/KeyPathFlowableTests.kt new file mode 100644 index 0000000000..696f8601b9 --- /dev/null +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/utils/KeyPathFlowableTests.kt @@ -0,0 +1,31 @@ +package io.realm.kotlin.test.common.utils + +import kotlin.test.Test + +/** + * All tests classes that tests classes exposing keypath notifications (RealmObject, RealmResults, + * RealmList, RealmSet, RealmMap) should implement this interface to be sure that we test common + * behaviour across those classes. + */ +interface KeyPathFlowableTests { + @Test + fun keyPath_topLevelProperty() + + @Test + fun keyPath_nestedProperty() + + @Test + fun keyPath_defaultDepth() + + @Test + fun keyPath_propertyBelowDefaultLimit() + + @Test + fun keyPath_unknownTopLevelProperty() + + @Test + fun keyPath_unknownNestedProperty() + + @Test + fun keyPath_invalidNestedProperty() +} 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..9f69e83046 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 @@ -22,7 +22,7 @@ package io.realm.kotlin.test.common.utils * RealmResults) should implement this interface to be sure that we test common behaviour across * those classes. */ -interface RealmEntityNotificationTests : FlowableTests { +interface RealmEntityNotificationTests : FlowableTests, KeyPathFlowableTests { // Verify that we get deletion events and close the Flow when the object being observed (or // containing object) is deleted. fun deleteEntity() From 9314d702b96086da00837c745741056cf2976aa9 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 23 Nov 2023 14:45:06 +0100 Subject: [PATCH 17/21] Use correct commit from master. --- CHANGELOG.md | 2 +- packages/external/core | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b92296c41..87d236cf88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,7 @@ * Minimum R8: 8.0.34. ### Internal -* None. +* Updated to Realm Core 13.24.0, commit 3bc7ce91fa67b38f3ff433e6372f741a7c906f2b. ## 1.12.1-SNAPSHOT (YYYY-MM-DD) diff --git a/packages/external/core b/packages/external/core index 6cca07f5e9..3bc7ce91fa 160000 --- a/packages/external/core +++ b/packages/external/core @@ -1 +1 @@ -Subproject commit 6cca07f5e959df94381f557ccd29dc83c0519484 +Subproject commit 3bc7ce91fa67b38f3ff433e6372f741a7c906f2b From 1422d2bfe87b0e809518d031651b21b36a4bf506 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 1 Dec 2023 10:46:11 +0100 Subject: [PATCH 18/21] PR feedback + add mention of wildcards to docs --- CHANGELOG.md | 2 +- .../kotlin/internal/interop/RealmInterop.kt | 16 +++--- .../kotlin/internal/interop/RealmInterop.kt | 2 +- packages/external/core | 2 +- .../io/realm/kotlin/ext/BaseRealmObjectExt.kt | 3 +- .../kotlin/internal/query/ScalarQuery.kt | 1 - .../io/realm/kotlin/query/RealmResults.kt | 7 +-- .../kotlin/io/realm/kotlin/types/RealmList.kt | 7 +-- .../kotlin/io/realm/kotlin/types/RealmMap.kt | 5 +- .../kotlin/io/realm/kotlin/types/RealmSet.kt | 7 +-- .../io/realm/kotlin/test/common/QueryTests.kt | 54 +++++++++++++++++++ .../BacklinksNotificationsTests.kt | 28 ++++++---- .../RealmObjectNotificationsTests.kt | 4 +- .../RealmResultsNotificationsTests.kt | 4 +- .../RealmSetNotificationsTests.kt | 45 ++++++++++------ 15 files changed, 135 insertions(+), 52 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87d236cf88..90415ec291 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,7 @@ * Minimum R8: 8.0.34. ### Internal -* Updated to Realm Core 13.24.0, commit 3bc7ce91fa67b38f3ff433e6372f741a7c906f2b. +* Updated to Realm Core 13.24.0, commit e593a5f19d0dc205db931ec5618a8c10c95cac90. ## 1.12.1-SNAPSHOT (YYYY-MM-DD) 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 738d3811d5..50f7fff451 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 @@ -41,6 +41,10 @@ import org.mongodb.kbson.ObjectId actual val INVALID_CLASS_KEY: ClassKey by lazy { ClassKey(realmc.getRLM_INVALID_CLASS_KEY()) } actual val INVALID_PROPERTY_KEY: PropertyKey by lazy { PropertyKey(realmc.getRLM_INVALID_PROPERTY_KEY()) } +// The value to pass to JNI functions that accept longs as replacements for pointers and need +// to represent null. +val NULL_POINTER_VALUE = 0L + /** * JVM/Android interop implementation. * @@ -814,7 +818,7 @@ actual object RealmInterop { } actual fun realm_create_key_paths_array(realm: RealmPointer, clazz: ClassKey, keyPaths: List): RealmKeyPathArrayPointer { - val ptr = realmc.realm_create_key_path_array(realm.cptr(), clazz.key, keyPaths.size, keyPaths.toTypedArray()) + val ptr = realmc.realm_create_key_path_array(realm.cptr(), clazz.key, keyPaths.size.toLong(), keyPaths.toTypedArray()) return LongPointerWrapper(ptr) } @@ -828,7 +832,7 @@ actual object RealmInterop { realmc.register_notification_cb( obj.cptr(), CollectionType.RLM_COLLECTION_TYPE_NONE.nativeValue, - keyPaths?.cptr() ?: 0, + keyPaths?.cptr() ?: NULL_POINTER_VALUE, object : NotificationCallback { override fun onChange(pointer: Long) { callback.onChange(LongPointerWrapper(realmc.realm_clone(pointer), true)) @@ -847,7 +851,7 @@ actual object RealmInterop { return LongPointerWrapper( realmc.register_results_notification_cb( results.cptr(), - keyPaths?.cptr() ?: 0, + keyPaths?.cptr() ?: NULL_POINTER_VALUE, object : NotificationCallback { override fun onChange(pointer: Long) { callback.onChange(LongPointerWrapper(realmc.realm_clone(pointer), true)) @@ -867,7 +871,7 @@ actual object RealmInterop { realmc.register_notification_cb( list.cptr(), CollectionType.RLM_COLLECTION_TYPE_LIST.nativeValue, - keyPaths?.cptr() ?: 0, + keyPaths?.cptr() ?: NULL_POINTER_VALUE, object : NotificationCallback { override fun onChange(pointer: Long) { callback.onChange(LongPointerWrapper(realmc.realm_clone(pointer), true)) @@ -887,7 +891,7 @@ actual object RealmInterop { realmc.register_notification_cb( set.cptr(), CollectionType.RLM_COLLECTION_TYPE_SET.nativeValue, - keyPaths?.cptr() ?: 0, + keyPaths?.cptr() ?: NULL_POINTER_VALUE, object : NotificationCallback { override fun onChange(pointer: Long) { callback.onChange(LongPointerWrapper(realmc.realm_clone(pointer), true)) @@ -907,7 +911,7 @@ actual object RealmInterop { realmc.register_notification_cb( map.cptr(), CollectionType.RLM_COLLECTION_TYPE_DICTIONARY.nativeValue, - keyPaths?.cptr() ?: 0, + keyPaths?.cptr() ?: NULL_POINTER_VALUE, object : NotificationCallback { override fun onChange(pointer: Long) { callback.onChange(LongPointerWrapper(realmc.realm_clone(pointer), true)) 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 f5dfe83a64..914d9cb6fb 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 @@ -1658,7 +1658,7 @@ actual object RealmInterop { actual fun realm_create_key_paths_array(realm: RealmPointer, clazz: ClassKey, keyPaths: List): RealmKeyPathArrayPointer { memScoped { val userKeyPaths: CPointer>>> = keyPaths.toCStringArray(this) - val keyPathPointer = realm_wrapper.realm_create_key_path_array(realm.cptr(), clazz.key.toUInt(), keyPaths.size, userKeyPaths) + val keyPathPointer = realm_wrapper.realm_create_key_path_array(realm.cptr(), clazz.key.toUInt(), keyPaths.size.toULong(), userKeyPaths) return CPointerWrapper(keyPathPointer) } } diff --git a/packages/external/core b/packages/external/core index 3bc7ce91fa..e593a5f19d 160000 --- a/packages/external/core +++ b/packages/external/core @@ -1 +1 @@ -Subproject commit 3bc7ce91fa67b38f3ff433e6372f741a7c906f2b +Subproject commit e593a5f19d0dc205db931ec5618a8c10c95cac90 diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/BaseRealmObjectExt.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/BaseRealmObjectExt.kt index faf0437e90..508d232790 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/BaseRealmObjectExt.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/ext/BaseRealmObjectExt.kt @@ -85,7 +85,8 @@ public fun BaseRealmObject.isValid(): Boolean = runIfManaged { * * @param keyPaths An optional list of properties that defines when a change to the object will * result in a change being emitted. Nested properties can be defined using a dotted - * syntax, e.g. `parent.child.name`. If no keypaths are provided, changes to all top-level + * syntax, e.g. `parent.child.name`. Wildcards `*` can be be used to capture all properties at a + * given level, e.g. `child.*` or `*.*`. If no keypaths are provided, changes to all top-level * properties and nested properties 4 levels down will trigger a change. * @return a flow representing changes to the object. * @throws UnsupportedOperationException if called on a live [RealmObject] or [EmbeddedRealmObject] 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 e3556485f2..bba1c4f58f 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 @@ -216,7 +216,6 @@ internal class SumQuery constructor( override fun asFlow(): Flow { realmReference.checkClosed() return realmReference.owner - // TODO .registerObserver(this, null) .map { findFromResults((it.list as RealmResultsImpl<*>).nativePointer) } .distinctUntilChanged() diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/query/RealmResults.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/query/RealmResults.kt index 12636815ac..f3de8dd200 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/query/RealmResults.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/query/RealmResults.kt @@ -71,9 +71,10 @@ public interface RealmResults : List, Deleteable, Versio * * @param keyPaths An optional list of model class properties that defines when a change to * objects inside the RealmResults will result in a change being emitted. Nested properties can - * be defined using a dotted syntax, e.g. `parent.child.name`. If no keypaths are provided, - * changes to all top-level properties and nested properties up to 4 levels down will trigger a - * change. + * be defined using a dotted syntax, e.g. `parent.child.name`. Wildcards `*` can be be used + * to capture all properties at a given level, e.g. `child.*` or `*.*`. If no keypaths are + * provided, changes to all top-level properties and nested properties up to 4 levels down + * will trigger a change. * @return a flow representing changes to the list. * @throws IllegalArgumentException if an invalid keypath is provided. * @return a flow representing changes to the RealmResults. diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmList.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmList.kt index 28c3ec35b5..0cc3fa4380 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmList.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmList.kt @@ -74,9 +74,10 @@ public interface RealmList : MutableList, Deleteable { * * @param keyPaths An optional list of model class properties that defines when a change to * objects inside the list will result in a change being emitted. Nested properties can be - * defined using a dotted syntax, e.g. `parent.child.name`. If no keypaths are provided, changes - * to all top-level properties and nested properties up to 4 levels down will trigger a change. - * Keypaths are only supported for lists of objects. + * defined using a dotted syntax, e.g. `parent.child.name`. Wildcards `*` can be be used + * to capture all properties at a given level, e.g. `child.*` or `*.*`.If no keypaths are + * provided, changes to all top-level properties and nested properties up to 4 levels down + * will trigger a change. Keypaths are only supported for lists of objects. * @return a flow representing changes to the list. * @throws IllegalArgumentException if an invalid keypath is provided or the RealmList does not * contain realm objects. diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmMap.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmMap.kt index 79870d8e42..7a03c805f9 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmMap.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmMap.kt @@ -59,8 +59,9 @@ public interface RealmMap : MutableMap { * objects inside the map will result in a change being emitted. For maps, keypaths are * evaluted based on the values of the map. This means that keypaths are only supported * for maps containing realm objects. Nested properties can be defined using a dotted syntax, - * e.g. `parent.child.name`. If no keypaths are provided, changes to all top-level properties - * and nested properties up to 4 levels down will trigger a change + * e.g. `parent.child.name`. Wildcards `*` can be be used to capture all properties at a given + * level, e.g. `child.*` or `*.*`. If no keypaths are provided, changes to all top-level + * properties and nested properties up to 4 levels down will trigger a change * @return a flow representing changes to the dictionary. * @throws IllegalArgumentException if keypaths are invalid or the map does not contain realm * objects. diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmSet.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmSet.kt index 19badece7e..6847974642 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmSet.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmSet.kt @@ -54,9 +54,10 @@ public interface RealmSet : MutableSet, Deleteable { * * @param keyPaths An optional list of model class properties that defines when a change to * objects inside the set will result in a change being emitted. Nested properties can be - * defined using a dotted syntax, e.g. `parent.child.name`. If no keypaths are provided, changes - * to all top-level properties and nested properties up to 4 levels down will trigger a change. - * Keypaths are only supported for sets of objects. + * defined using a dotted syntax, e.g. `parent.child.name`. Wildcards `*` can be be used + * to capture all properties at a given level, e.g. `child.*` or `*.*`. If no keypaths + * are provided, changes to all top-level properties and nested properties up to 4 levels down + * will trigger a change. Keypaths are only supported for sets containing realm objects. * @return a flow representing changes to the set. * @throws IllegalArgumentException if an invalid keypath is provided or the set does not * contain realm objects. diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/QueryTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/QueryTests.kt index f43d6e74b3..a75f24e9ed 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/QueryTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/QueryTests.kt @@ -2980,6 +2980,7 @@ class QueryTests { channel.receiveOrFail().let { resultsChange -> assertIs>(resultsChange) assertEquals(1, resultsChange.list.size) + assertEquals("update", resultsChange.list.first().stringField) } observer.cancel() channel.close() @@ -3026,6 +3027,59 @@ class QueryTests { } } + // Smoke-test for wildcards. + @Test + fun keyPath_usingWildCards() { + val channel = Channel>(1) + runBlocking { + val observer = async { + realm.query("stringField = 'parent'") + // Should match what the notifier is doing by default + .asFlow(listOf("*.*.*.*")) + .collect { results -> + assertNotNull(results) + channel.send(results) + } + } + channel.receiveOrFail().let { resultsChange -> + assertIs>(resultsChange) + assertTrue(resultsChange.list.isEmpty()) + } + val obj = realm.writeBlocking { + copyToRealm( + QuerySample().apply { + stringField = "parent" + nullableRealmObject = QuerySample().apply { + stringField = "child" + } + } + ) + } + channel.receiveOrFail().let { resultsChange -> + assertIs>(resultsChange) + assertEquals(1, resultsChange.list.size) + } + realm.writeBlocking { + findLatest(obj)!!.intField = 42 + } + channel.receiveOrFail().let { resultsChange -> + assertIs>(resultsChange) + assertEquals(1, resultsChange.list.size) + assertEquals(42, resultsChange.list.first().intField) + } + realm.writeBlocking { + findLatest(obj)!!.nullableRealmObject!!.stringField = "update" + } + channel.receiveOrFail().let { resultsChange -> + assertIs>(resultsChange) + assertEquals(1, resultsChange.list.size) + assertEquals("update", resultsChange.list.first().nullableRealmObject!!.stringField) + } + observer.cancel() + channel.close() + } + } + // ---------------- // Coercion helpers // ---------------- 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 1a172c15cf..c32ca0f6eb 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 @@ -40,6 +40,7 @@ import kotlin.test.BeforeTest import kotlin.test.Ignore import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertFailsWith import kotlin.test.assertIs import kotlin.test.assertNotNull import kotlin.test.assertNull @@ -493,7 +494,7 @@ class BacklinksNotificationsTests : RealmEntityNotificationTests { assertEquals(1, resultsChange.changes.size) assertEquals(0, resultsChange.deletions.size) assertEquals(1, resultsChange.list.first().objectBacklinks.size) - // This starts at 42, s if the first write triggers a change event, it will + // This starts at 42, if the first write triggers a change event, it will // catch it here. assertEquals(resultsChange.list.first().objectBacklinks.first().intField, 1) } @@ -519,7 +520,7 @@ class BacklinksNotificationsTests : RealmEntityNotificationTests { this.nullableObject = Sample().apply { this.stringField = "child-child-child" this.nullableObject = Sample().apply { - this.stringField = "child-child-child" + this.stringField = "child-child-child-child" this.nullableObject = Sample().apply { this.stringField = "BottomChild" } @@ -582,7 +583,7 @@ class BacklinksNotificationsTests : RealmEntityNotificationTests { this.nullableObject = Sample().apply { this.stringField = "child-child-child" this.nullableObject = Sample().apply { - this.stringField = "child-child-child" + this.stringField = "child-child-child-child" this.nullableObject = Sample().apply { this.stringField = "BottomChild" } @@ -631,20 +632,29 @@ class BacklinksNotificationsTests : RealmEntityNotificationTests { } @Test - @Ignore // Already covered by RealmResultsNotificationTests override fun keyPath_unknownTopLevelProperty() = runBlocking { - TODO() + val results = realm.query() + assertFailsWith() { + results.asFlow(listOf("foo")) + } } @Test - @Ignore // Already covered by RealmResultsNotificationTests override fun keyPath_unknownNestedProperty() = runBlocking { - TODO() + val results = realm.query() + assertFailsWith() { + results.asFlow(listOf("objectBacklinks.foo")) + } } @Test - @Ignore // Already covered by RealmResultsNotificationTests override fun keyPath_invalidNestedProperty() = runBlocking { - TODO() + val results = realm.query() + assertFailsWith { + results.asFlow(listOf("objectBacklinks.intField.foo")) + } + assertFailsWith { + results.asFlow(listOf("objectBacklinks.intListField.foo")) + } } } 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 0ccec3a602..b02c273112 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 @@ -393,7 +393,7 @@ class RealmObjectNotificationsTests : RealmEntityNotificationTests { this.nullableObject = Sample().apply { this.stringField = "child-child-child" this.nullableObject = Sample().apply { - this.stringField = "child-child-child" + this.stringField = "child-child-child-child" this.nullableObject = Sample().apply { this.stringField = "BottomChild" } @@ -448,7 +448,7 @@ class RealmObjectNotificationsTests : RealmEntityNotificationTests { this.nullableObject = Sample().apply { this.stringField = "child-child-child" this.nullableObject = Sample().apply { - this.stringField = "child-child-child" + this.stringField = "child-child-child-child" this.nullableObject = Sample().apply { this.stringField = "BottomChild" } diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmResultsNotificationsTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmResultsNotificationsTests.kt index 2a6cadd5d3..992491cbe6 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmResultsNotificationsTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmResultsNotificationsTests.kt @@ -479,7 +479,7 @@ class RealmResultsNotificationsTests : FlowableTests, KeyPathFlowableTests { this.nullableObject = Sample().apply { this.stringField = "child-child-child" this.nullableObject = Sample().apply { - this.stringField = "child-child-child" + this.stringField = "child-child-child-child" this.nullableObject = Sample().apply { this.stringField = "BottomChild" } @@ -539,7 +539,7 @@ class RealmResultsNotificationsTests : FlowableTests, KeyPathFlowableTests { this.nullableObject = Sample().apply { this.stringField = "child-child-child" this.nullableObject = Sample().apply { - this.stringField = "child-child-child" + this.stringField = "child-child-child-child" this.nullableObject = Sample().apply { this.stringField = "BottomChild" } 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 38c5135b9f..d2256eac9d 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 @@ -349,17 +349,16 @@ class RealmSetNotificationsTests : RealmEntityNotificationTests { @Test override fun keyPath_topLevelProperty() = runBlocking { val c = Channel>(1) - val obj = realm.write { + val set: RealmSet = realm.write { copyToRealm( RealmSetContainer().apply { this.objectSetField = realmSetOf( - RealmSetContainer().apply { this.stringField = "list-item-1" }, - RealmSetContainer().apply { this.stringField = "list-item-2" } + RealmSetContainer().apply { this.stringField = "set-item-1" }, + RealmSetContainer().apply { this.stringField = "set-item-2" } ) } ) - } - val set: RealmSet = obj.objectSetField + }.objectSetField val observer = async { set.asFlow(listOf("stringField")).collect { c.trySend(it) @@ -374,14 +373,15 @@ class RealmSetNotificationsTests : RealmEntityNotificationTests { // Update field that should trigger a notification findLatest(set.first())!!.stringField = "Foo" } - c.receiveOrFail().let { listChange -> - assertIs>(listChange) - when (listChange) { + c.receiveOrFail().let { setChange -> + assertIs>(setChange) + when (setChange) { is UpdatedSet -> { - assertEquals(0, listChange.deletions) - assertEquals(0, listChange.insertions) + assertEquals(0, setChange.deletions) + assertEquals(0, setChange.insertions) + assertNotNull(setChange.set.firstOrNull { it.stringField == "Foo" }) } - else -> fail("Unexpected change: $listChange") + else -> fail("Unexpected change: $setChange") } } observer.cancel() @@ -427,6 +427,7 @@ class RealmSetNotificationsTests : RealmEntityNotificationTests { is UpdatedSet -> { assertEquals(0, setChange.insertions) assertEquals(0, setChange.deletions) + assertEquals("Bar", setChange.set.first().objectSetField.first().stringField) } else -> fail("Unexpected change: $setChange") } @@ -567,15 +568,25 @@ class RealmSetNotificationsTests : RealmEntityNotificationTests { val obj = findLatest(list.first())!!.objectSetField.first().objectSetField.first().objectSetField.first().objectSetField.first().objectSetField.first() obj.stringField = "Bar" } - c.receiveOrFail().let { listChange -> - assertIs>(listChange) - when (listChange) { + c.receiveOrFail().let { setChange -> + assertIs>(setChange) + when (setChange) { is SetChange -> { // Core will only report something changed to the top-level property. - assertEquals(0, listChange.insertions) - assertEquals(0, listChange.deletions) + assertEquals(0, setChange.insertions) + assertEquals(0, setChange.deletions) + assertEquals( + "Bar", + setChange.set.first() + .objectSetField.first() + .objectSetField.first() + .objectSetField.first() + .objectSetField.first() + .objectSetField.first() + .stringField + ) } - else -> fail("Unexpected change: $listChange") + else -> fail("Unexpected change: $setChange") } } observer.cancel() From 438f72f5d61d560bd2442ccc9fc7cf89d7505d32 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 1 Dec 2023 12:22:13 +0100 Subject: [PATCH 19/21] Fix detekt --- .../jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 50f7fff451..53f18a3bed 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 @@ -43,7 +43,7 @@ actual val INVALID_PROPERTY_KEY: PropertyKey by lazy { PropertyKey(realmc.getRLM // The value to pass to JNI functions that accept longs as replacements for pointers and need // to represent null. -val NULL_POINTER_VALUE = 0L +const val NULL_POINTER_VALUE = 0L /** * JVM/Android interop implementation. From 45b909cc74f5e616cfd0f4ef60c558cb3803728b Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 1 Dec 2023 14:21:56 +0100 Subject: [PATCH 20/21] PR feedback --- .../kotlin/internal/RealmListInternal.kt | 3 +- .../realm/kotlin/internal/RealmMapInternal.kt | 3 +- .../realm/kotlin/internal/RealmSetInternal.kt | 3 +- .../io/realm/kotlin/test/common/QueryTests.kt | 90 +++++++++---------- 4 files changed, 47 insertions(+), 52 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 9fe3ff398c..8f1e60a0a7 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 @@ -119,8 +119,7 @@ internal class ManagedRealmList( operator.realmReference.checkClosed() val keyPathInfo = keyPaths?.let { Validation.isType>(operator, "Keypaths are only supported for lists of objects.") - val classKey = operator.classKey - Pair(classKey, keyPaths) + Pair(operator.classKey, keyPaths) } return operator.realmReference.owner.registerObserver(this, keyPathInfo) } 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 9f4e621785..b6c8ff5b47 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 @@ -105,8 +105,7 @@ internal abstract class ManagedRealmMap constructor( operator.realmReference.checkClosed() val keyPathInfo = keyPaths?.let { Validation.isType>(operator, "Keypaths are only supported for maps of objects.") - val classKey = operator.classKey // if (operator is RealmObjectMapOperator) { - Pair(classKey, keyPaths) + Pair(operator.classKey, keyPaths) } return operator.realmReference.owner.registerObserver(this, keyPathInfo) } 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 72073c8a94..57337a8973 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 @@ -164,8 +164,7 @@ internal class ManagedRealmSet constructor( override fun asFlow(keyPaths: List?): Flow> { val keyPathInfo = keyPaths?.let { Validation.isType>(operator, "Keypaths are only supported for sets of objects.") - val classKey = operator.classKey - Pair(classKey, keyPaths) + Pair(operator.classKey, keyPaths) } return operator.realmReference.owner.registerObserver(this, keyPathInfo) } diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/QueryTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/QueryTests.kt index a75f24e9ed..2ea585ab40 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/QueryTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/QueryTests.kt @@ -3029,55 +3029,53 @@ class QueryTests { // Smoke-test for wildcards. @Test - fun keyPath_usingWildCards() { + fun keyPath_usingWildCards() = runBlocking { val channel = Channel>(1) - runBlocking { - val observer = async { - realm.query("stringField = 'parent'") - // Should match what the notifier is doing by default - .asFlow(listOf("*.*.*.*")) - .collect { results -> - assertNotNull(results) - channel.send(results) - } - } - channel.receiveOrFail().let { resultsChange -> - assertIs>(resultsChange) - assertTrue(resultsChange.list.isEmpty()) - } - val obj = realm.writeBlocking { - copyToRealm( - QuerySample().apply { - stringField = "parent" - nullableRealmObject = QuerySample().apply { - stringField = "child" - } + val observer = async { + realm.query("stringField = 'parent'") + // Should match what the notifier is doing by default + .asFlow(listOf("*.*.*.*")) + .collect { results -> + assertNotNull(results) + channel.send(results) + } + } + channel.receiveOrFail().let { resultsChange -> + assertIs>(resultsChange) + assertTrue(resultsChange.list.isEmpty()) + } + val obj = realm.write { + copyToRealm( + QuerySample().apply { + stringField = "parent" + nullableRealmObject = QuerySample().apply { + stringField = "child" } - ) - } - channel.receiveOrFail().let { resultsChange -> - assertIs>(resultsChange) - assertEquals(1, resultsChange.list.size) - } - realm.writeBlocking { - findLatest(obj)!!.intField = 42 - } - channel.receiveOrFail().let { resultsChange -> - assertIs>(resultsChange) - assertEquals(1, resultsChange.list.size) - assertEquals(42, resultsChange.list.first().intField) - } - realm.writeBlocking { - findLatest(obj)!!.nullableRealmObject!!.stringField = "update" - } - channel.receiveOrFail().let { resultsChange -> - assertIs>(resultsChange) - assertEquals(1, resultsChange.list.size) - assertEquals("update", resultsChange.list.first().nullableRealmObject!!.stringField) - } - observer.cancel() - channel.close() + } + ) + } + channel.receiveOrFail().let { resultsChange -> + assertIs>(resultsChange) + assertEquals(1, resultsChange.list.size) + } + realm.write { + findLatest(obj)!!.intField = 42 + } + channel.receiveOrFail().let { resultsChange -> + assertIs>(resultsChange) + assertEquals(1, resultsChange.list.size) + assertEquals(42, resultsChange.list.first().intField) + } + realm.write { + findLatest(obj)!!.nullableRealmObject!!.stringField = "update" + } + channel.receiveOrFail().let { resultsChange -> + assertIs>(resultsChange) + assertEquals(1, resultsChange.list.size) + assertEquals("update", resultsChange.list.first().nullableRealmObject!!.stringField) } + observer.cancel() + channel.close() } // ---------------- From fa1de3ca2c12a184fc4b7aee2563b1b19f870ffb Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 1 Dec 2023 14:53:20 +0100 Subject: [PATCH 21/21] Fix test --- .../commonTest/kotlin/io/realm/kotlin/test/common/QueryTests.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/QueryTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/QueryTests.kt index 2ea585ab40..a63f89d83b 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/QueryTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/QueryTests.kt @@ -3029,7 +3029,7 @@ class QueryTests { // Smoke-test for wildcards. @Test - fun keyPath_usingWildCards() = runBlocking { + fun keyPath_usingWildCards() = runBlocking { val channel = Channel>(1) val observer = async { realm.query("stringField = 'parent'")