From be6f7095df06d60187b8a2de19bd17ea8fe75e63 Mon Sep 17 00:00:00 2001 From: clementetb Date: Tue, 14 Nov 2023 09:58:34 +0100 Subject: [PATCH 1/2] Allow aggregators on properties annotated with @PersistedName (#1569) --- CHANGELOG.md | 1 + .../kotlin/internal/query/ObjectQuery.kt | 2 +- .../internal/schema/CachedClassKeyMap.kt | 5 +- .../kotlin/test/common/PersistedNameTests.kt | 54 +++++++++++++++++++ 4 files changed, 60 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f8a2bb01ab..99e9ed341f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ### Fixed * Fix craches caused by posting to a released scheduler. (Issue [#1543](https://github.com/realm/realm-kotlin/issues/1543)) +* Fix NPE when applying query aggregators on classes annotated with `@PersistedName`. (Issue [1569](https://github.com/realm/realm-kotlin/pull/1569)) ### Compatibility * File format: Generates Realms with file format v23. 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..fe2891a88f 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 @@ -54,7 +54,7 @@ internal class ObjectQuery constructor( RealmInterop.realm_query_find_all(queryPointer) } - private val classMetadata: ClassMetadata? = realmReference.schemaMetadata[clazz.simpleName!!] + private val classMetadata: ClassMetadata? = realmReference.schemaMetadata[classKey] internal constructor( realmReference: RealmReference, diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/schema/CachedClassKeyMap.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/schema/CachedClassKeyMap.kt index 7f6c0b1649..7da918259f 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/schema/CachedClassKeyMap.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/schema/CachedClassKeyMap.kt @@ -24,6 +24,7 @@ import io.realm.kotlin.internal.interop.PropertyKey import io.realm.kotlin.internal.interop.PropertyType import io.realm.kotlin.internal.interop.RealmInterop import io.realm.kotlin.internal.interop.RealmPointer +import io.realm.kotlin.internal.interop.SCHEMA_NO_VALUE import io.realm.kotlin.types.BaseRealmObject import io.realm.kotlin.types.TypedRealmObject import kotlin.reflect.KClass @@ -64,6 +65,7 @@ public interface ClassMetadata { public interface PropertyMetadata { public val name: String + public val publicName: String public val key: PropertyKey public val collectionType: CollectionType public val type: PropertyType @@ -154,7 +156,7 @@ public class CachedClassMetadata( primaryKeyProperty = properties.firstOrNull { it.isPrimaryKey } isEmbeddedRealmObject = classInfo.isEmbedded - nameMap = properties.associateBy { it.name } + nameMap = properties.associateBy { it.name } + properties.filterNot { it.publicName == SCHEMA_NO_VALUE }.associateBy { it.publicName } keyMap = properties.associateBy { it.key } propertyMap = properties.associateBy { it.accessor } } @@ -169,6 +171,7 @@ public class CachedPropertyMetadata( override val accessor: KProperty1? = null ) : PropertyMetadata { override val name: String = propertyInfo.name + override val publicName: String = propertyInfo.publicName override val key: PropertyKey = propertyInfo.key override val collectionType: CollectionType = propertyInfo.collectionType override val type: PropertyType = propertyInfo.type diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/PersistedNameTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/PersistedNameTests.kt index 07dd7069e1..603047d1ee 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/PersistedNameTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/PersistedNameTests.kt @@ -27,6 +27,9 @@ import io.realm.kotlin.ext.realmListOf import io.realm.kotlin.ext.realmSetOf import io.realm.kotlin.internal.asDynamicRealm import io.realm.kotlin.migration.AutomaticSchemaMigration +import io.realm.kotlin.query.max +import io.realm.kotlin.query.min +import io.realm.kotlin.query.sum import io.realm.kotlin.schema.RealmStorageType import io.realm.kotlin.test.common.utils.assertFailsWithMessage import io.realm.kotlin.test.platform.PlatformUtils @@ -76,6 +79,54 @@ class PersistedNameTests { PlatformUtils.deleteTempDir(tmpDir) } + // -------------------------------------------------- + // Aggregators + // -------------------------------------------------- + + @Test + fun aggregators_byPublicName() { + realm.writeBlocking { + copyToRealm(PersistedNameSample()) + } + + assertEquals( + expected = 10, + actual = realm.query().sum("publicNameIntField").find() + ) + + assertEquals( + expected = 10, + actual = realm.query().max("publicNameIntField").find() + ) + + assertEquals( + expected = 10, + actual = realm.query().min("publicNameIntField").find() + ) + } + + @Test + fun aggregators_byPersistedName() { + realm.writeBlocking { + copyToRealm(PersistedNameSample()) + } + + assertEquals( + expected = 10, + actual = realm.query().sum("persistedNameIntField").find() + ) + + assertEquals( + expected = 10, + actual = realm.query().max("persistedNameIntField").find() + ) + + assertEquals( + expected = 10, + actual = realm.query().min("persistedNameIntField").find() + ) + } + // -------------------------------------------------- // Query // -------------------------------------------------- @@ -479,6 +530,9 @@ class PersistedNameSample : RealmObject { // the underlying schema due to being equal to the persisted name. @PersistedName("sameName2") var sameName2 = "Realm" + + @PersistedName("persistedNameIntField") + var publicNameIntField: Int = 10 } class PersistedNameParentSample(var id: Int) : RealmObject { From 30c3bfda67a2bc74d7ce56dd1c3315142fd2305a Mon Sep 17 00:00:00 2001 From: clementetb Date: Wed, 15 Nov 2023 10:48:49 +0100 Subject: [PATCH 2/2] DynamicObjects can now access fields by public name. (#1571) --- .../kotlin/io/realm/kotlin/entities/Sample.kt | 4 ++++ .../realm/kotlin/entities/migration/Sample.kt | 4 ++++ .../kotlin/test/common/PersistedNameTests.kt | 6 +++--- .../common/migration/RealmMigrationTests.kt | 21 +++++++++++++++++++ 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/entities/Sample.kt b/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/entities/Sample.kt index 476b969931..d4617e5250 100644 --- a/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/entities/Sample.kt +++ b/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/entities/Sample.kt @@ -30,6 +30,7 @@ import io.realm.kotlin.types.RealmList import io.realm.kotlin.types.RealmObject import io.realm.kotlin.types.RealmSet import io.realm.kotlin.types.RealmUUID +import io.realm.kotlin.types.annotations.PersistedName import org.mongodb.kbson.BsonObjectId import org.mongodb.kbson.Decimal128 @@ -178,6 +179,9 @@ class Sample : RealmObject { val listBacklinks by backlinks(Sample::objectListField) val setBacklinks by backlinks(Sample::objectSetField) + @PersistedName("persistedStringField") + var publicStringField = "Realm" + // For verification that references inside class is also using our modified accessors and are // not optimized to use the backing field directly. fun stringFieldGetter(): String { diff --git a/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/entities/migration/Sample.kt b/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/entities/migration/Sample.kt index 63228127cc..9237a889ca 100644 --- a/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/entities/migration/Sample.kt +++ b/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/entities/migration/Sample.kt @@ -17,10 +17,14 @@ package io.realm.kotlin.entities.migration import io.realm.kotlin.types.RealmObject +import io.realm.kotlin.types.annotations.PersistedName @Suppress("MagicNumber") class Sample : RealmObject { var name: String = "Migration" var stringField: String = "Realm" var intField: Int = 42 + + @PersistedName("persistedStringField") + var publicStringField = "" } diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/PersistedNameTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/PersistedNameTests.kt index 603047d1ee..e5af30b72d 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/PersistedNameTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/PersistedNameTests.kt @@ -232,9 +232,9 @@ class PersistedNameTests { assertNotNull(dynamicSample) assertEquals("Realm", dynamicSample.getValue("persistedNameStringField")) - assertFailsWithMessage("Schema for type 'AlternativePersistedNameSample' doesn't contain a property named 'publicNameStringField'") { - dynamicSample.getValue("publicNameStringField") - } + // We can access property via the public name because the dynamic Realm is build upon a typed + // Realm via the extension function `asDynamicRealm`. + assertEquals("Realm", dynamicSample.getValue("publicNameStringField")) } // -------------------------------------------------- diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/migration/RealmMigrationTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/migration/RealmMigrationTests.kt index 090402e387..867f282de2 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/migration/RealmMigrationTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/migration/RealmMigrationTests.kt @@ -59,6 +59,27 @@ class RealmMigrationTests { PlatformUtils.deleteTempDir(tmpDir) } + @Test + fun migrationContext_publicNamesNotAvailable() { + migration( + initialSchema = setOf( + io.realm.kotlin.entities.schema.SchemaVariations::class, + io.realm.kotlin.entities.Sample::class + ), + migratedSchema = setOf(io.realm.kotlin.entities.migration.Sample::class), + migration = { context -> + val oldRealm = context.oldRealm + val newRealm = context.newRealm + + assertNotNull(oldRealm.schema()["Sample"]?.get("persistedStringField")) + assertNull(oldRealm.schema()["Sample"]?.get("publicStringField")) + + assertNotNull(newRealm.schema()["Sample"]?.get("persistedStringField")) + assertNull(newRealm.schema()["Sample"]?.get("publicStringField")) + } + ) + } + @Test fun migrationContext_schemaVerification() { migration(