diff --git a/CHANGELOG.md b/CHANGELOG.md index 130171b81f..07a9db0a72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * Realm will no longer set the JVM bytecode to 1.8 when applying the Realm plugin. ([#1513](https://github.com/realm/realm-kotlin/issues/1513)) ### Fixed +* Fix error in `RealmAny.equals` that would sometimes return `true` when comparing RealmAnys wrapping same type but different values. (Issue [#1523](https://github.com/realm/realm-kotlin/pull/1523)) * [Sync] If calling a function on App Services that resulted in a redirect, it would only redirect for GET requests. (Issue [#1517](https://github.com/realm/realm-kotlin/pull/1517)) ### Compatibility diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmAnyImpl.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmAnyImpl.kt index 0a2791fab8..356eb6397e 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmAnyImpl.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmAnyImpl.kt @@ -127,27 +127,12 @@ internal class RealmAnyImpl constructor( if (other.type != this.type) return false if (clazz == ByteArray::class) { if (other.internalValue !is ByteArray) return false - if (!other.internalValue.contentEquals(this.internalValue as ByteArray)) return false - } else if (internalValue is BsonObjectId) { - if (other.clazz != BsonObjectId::class) return false - if (other.internalValue != this.internalValue) return false + return other.internalValue.contentEquals(this.internalValue as ByteArray) } else if (internalValue is RealmObject) { if (other.clazz != this.clazz) return false - if (other.internalValue !== this.internalValue) return false - } else if (internalValue is Number) { // Numerics are the same as long as their value is the same - when (other.internalValue) { - is Char -> if (other.internalValue.code.toLong() != internalValue.toLong()) return false - is Number -> if (other.internalValue.toLong() != this.internalValue.toLong()) return false - else -> return false - } - } else if (internalValue is Char) { // We are comparing chars - when (other.internalValue) { - is Char -> if (other.internalValue.code.toLong() != internalValue.toLong()) return false - is Number -> if (other.internalValue.toLong() != this.internalValue.toLong()) return false - else -> return false - } + return other.internalValue == this.internalValue } - return true + return internalValue == other.internalValue } override fun hashCode(): Int { diff --git a/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/entities/SerializableSample.kt b/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/entities/SerializableSample.kt index 9f1a4ab718..9e6b939bf7 100644 --- a/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/entities/SerializableSample.kt +++ b/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/entities/SerializableSample.kt @@ -50,6 +50,7 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.UseSerializers import org.mongodb.kbson.BsonObjectId import org.mongodb.kbson.Decimal128 +import kotlin.reflect.KClass import kotlin.reflect.KMutableProperty1 @Suppress("MagicNumber") @@ -65,7 +66,9 @@ class SerializableSample : RealmObject { var floatField: Float = 3.14f var doubleField: Double = 1.19840122 var decimal128Field: Decimal128 = Decimal128("1.8446744073709551618E-6157") - var timestampField: RealmInstant = RealmInstant.from(100, 1000) + // We will loose nano second precision when we round trip these, so framework only works for + // timestamps with 0-nanosecond fraction. + var timestampField: RealmInstant = RealmInstant.from(100, 1000000) var bsonObjectIdField: BsonObjectId = BsonObjectId("507f1f77bcf86cd799439011") var uuidField: RealmUUID = RealmUUID.from("46423f1b-ce3e-4a7e-812f-004cf9c42d76") var binaryField: ByteArray = byteArrayOf(42) @@ -212,7 +215,7 @@ class SerializableSample : RealmObject { ) @Suppress("UNCHECKED_CAST") - val listNullableProperties = mapOf( + val listNullableProperties: Map, KMutableProperty1>> = mapOf( String::class to SerializableSample::nullableStringListField as KMutableProperty1>, Byte::class to SerializableSample::nullableByteListField as KMutableProperty1>, Char::class to SerializableSample::nullableCharListField as KMutableProperty1>, diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyTests.kt index 843fbd29c2..f99e36d2d0 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyTests.kt @@ -58,6 +58,7 @@ import kotlin.test.assertContentEquals import kotlin.test.assertEquals import kotlin.test.assertFailsWith import kotlin.test.assertIs +import kotlin.test.assertNotEquals import kotlin.test.assertNotNull import kotlin.test.assertNull import kotlin.test.fail @@ -421,6 +422,74 @@ class RealmAnyTests { } } + @Test + fun equals() { + RealmAny.Type.values().forEach { type -> + when (type) { + RealmAny.Type.INT -> { + assertEquals(RealmAny.create(1), RealmAny.create(Char(1))) + assertEquals(RealmAny.create(1), RealmAny.create(1.toByte())) + assertEquals(RealmAny.create(1), RealmAny.create(1.toShort())) + assertEquals(RealmAny.create(1), RealmAny.create(1.toInt())) + assertEquals(RealmAny.create(1), RealmAny.create(1.toLong())) + assertNotEquals(RealmAny.create(1), RealmAny.create(2)) + } + RealmAny.Type.BOOL -> { + assertEquals(RealmAny.create(true), RealmAny.create(true)) + assertNotEquals(RealmAny.create(true), RealmAny.create(false)) + } + RealmAny.Type.STRING -> { + assertEquals(RealmAny.create("Realm"), RealmAny.create("Realm")) + assertNotEquals(RealmAny.create("Realm"), RealmAny.create("Not Realm")) + } + RealmAny.Type.BINARY -> { + assertEquals( + RealmAny.create(byteArrayOf(1, 2)), RealmAny.create(byteArrayOf(1, 2)) + ) + assertNotEquals( + RealmAny.create(byteArrayOf(1, 2)), RealmAny.create(byteArrayOf(2, 1)) + ) + } + RealmAny.Type.TIMESTAMP -> { + val now = RealmInstant.now() + assertEquals(RealmAny.create(now), RealmAny.create(now)) + assertNotEquals(RealmAny.create(RealmInstant.from(1, 1)), RealmAny.create(now)) + } + RealmAny.Type.FLOAT -> { + assertEquals(RealmAny.create(1.5f), RealmAny.create(1.5f)) + assertNotEquals(RealmAny.create(1.2f), RealmAny.create(1.3f)) + } + RealmAny.Type.DOUBLE -> { + assertEquals(RealmAny.create(1.5), RealmAny.create(1.5)) + assertNotEquals(RealmAny.create(1.2), RealmAny.create(1.3)) + } + RealmAny.Type.DECIMAL128 -> { + assertEquals(RealmAny.create(Decimal128("1E64")), RealmAny.create(Decimal128("1E64"))) + assertNotEquals(RealmAny.create(Decimal128("1E64")), RealmAny.create(Decimal128("-1E64"))) + } + RealmAny.Type.OBJECT_ID -> { + val value = ObjectId() + assertEquals(RealmAny.create(value), RealmAny.create(value)) + assertNotEquals(RealmAny.create(ObjectId()), RealmAny.create(value)) + } + RealmAny.Type.UUID -> { + val value = RealmUUID.random() + assertEquals(RealmAny.create(value), RealmAny.create(value)) + assertNotEquals(RealmAny.create(RealmUUID.random()), RealmAny.create(value)) + } + RealmAny.Type.OBJECT -> { + val realmObject = Sample() + // Same object is equal + assertEquals(RealmAny.create(realmObject), RealmAny.create(realmObject)) + // Different kind of objects are not equal + assertNotEquals(RealmAny.create(RealmAnyContainer()), RealmAny.create(realmObject)) + // Different objects of same type are not equal + assertNotEquals(RealmAny.create(Sample()), RealmAny.create(realmObject)) + } + } + } + } + @Test fun embeddedObject_worksInsideParent() { val embeddedChild = EmbeddedChild("CHILD") diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/SerializationTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/SerializationTests.kt index 088cf1919e..223f0a9af0 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/SerializationTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/SerializationTests.kt @@ -139,6 +139,11 @@ class SerializationTests { RealmInstant::class -> dataSet.map { (it as RealmInstant?)?.restrictToMillisPrecision() as T } + RealmAny::class -> dataSet.map { + if ((it as? RealmAny)?.type == RealmAny.Type.TIMESTAMP) { + RealmAny.create((it.asRealmInstant()!!.restrictToMillisPrecision()))as T + } else { it } + } else -> dataSet } @@ -177,6 +182,12 @@ class SerializationTests { RealmInstant::class -> dataSet.map { entry -> entry.first to (entry.second as RealmInstant?)?.restrictToMillisPrecision() as T } + RealmAny::class -> dataSet.map { entry -> + val (key, value) = entry + if ((value as? RealmAny)?.type == RealmAny.Type.TIMESTAMP) { + key to RealmAny.create((value.asRealmInstant()!!.restrictToMillisPrecision()))as T + } else { entry } + } else -> dataSet }