From 99489a4e024ea99539d535c3dd2c7421e4b49652 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Mon, 4 Dec 2023 17:18:44 +0100 Subject: [PATCH 01/36] Initial scaffolding of API --- .../kotlin/internal/interop/RealmInterop.kt | 1 + .../kotlin/internal/interop/RealmInterop.kt | 3 +- .../kotlin/internal/interop/RealmInterop.kt | 3 +- .../kotlin/io/realm/kotlin/mongodb/User.kt | 5 + .../realm/kotlin/mongodb/ext/FunctionsExt.kt | 63 +- .../io/realm/kotlin/mongodb/ext/UserExt.kt | 47 +- .../kotlin/mongodb/internal/FunctionsImpl.kt | 2 + .../mongodb/internal/MongoClientImpl.kt | 28 + .../mongodb/internal/MongoCollectionImpl.kt | 65 + .../mongodb/internal/MongoDatabaseImpl.kt | 30 + .../realm/kotlin/mongodb/internal/UserImpl.kt | 5 + .../realm/kotlin/mongodb/mongo/MongoClient.kt | 27 + .../kotlin/mongodb/mongo/MongoCollection.kt | 343 ++++ .../kotlin/mongodb/mongo/MongoDatabase.kt | 31 + .../kotlin/test/mongodb/util/AppAdmin.kt | 7 + .../test/mongodb/util/AppServicesClient.kt | 2 +- .../mongodb/common/mongo/MongoClientTest.kt | 1429 +++++++++++++++++ .../kotlin/test/mongodb/common/utils/Utils.kt | 13 + 18 files changed, 2065 insertions(+), 39 deletions(-) create mode 100644 packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoClientImpl.kt create mode 100644 packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt create mode 100644 packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDatabaseImpl.kt create mode 100644 packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt create mode 100644 packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt create mode 100644 packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoDatabase.kt create mode 100644 packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTest.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 7b49fc5f8b..a716611111 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 @@ -720,6 +720,7 @@ expect object RealmInterop { app: RealmAppPointer, user: RealmUserPointer, name: String, + serviceName: String? = null, serializedEjsonArgs: String, // as ejson callback: AppCallback ) 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 20f8b0f1fb..f4ff9a3312 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 @@ -1618,10 +1618,11 @@ actual object RealmInterop { app: RealmAppPointer, user: RealmUserPointer, name: String, + serviceName: String?, serializedEjsonArgs: String, callback: AppCallback ) { - realmc.realm_app_call_function(app.cptr(), user.cptr(), name, serializedEjsonArgs, null, callback) + realmc.realm_app_call_function(app.cptr(), user.cptr(), name, serializedEjsonArgs, serviceName, callback) } actual fun realm_app_call_reset_password_function( 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 6363425be5..dd12ff0080 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 @@ -2997,6 +2997,7 @@ actual object RealmInterop { app: RealmAppPointer, user: RealmUserPointer, name: String, + serviceName: String?, serializedEjsonArgs: String, callback: AppCallback ) { @@ -3005,7 +3006,7 @@ actual object RealmInterop { user.cptr(), name, serializedEjsonArgs, - null, + serviceName, staticCFunction { userData: CPointer?, data: CPointer>?, error: CPointer? -> handleAppCallback(userData, error) { data.safeKString() diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/User.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/User.kt index 2f68bb6528..c276cdfb00 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/User.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/User.kt @@ -20,6 +20,7 @@ import io.realm.kotlin.mongodb.auth.ApiKeyAuth import io.realm.kotlin.mongodb.exceptions.AppException import io.realm.kotlin.mongodb.ext.customDataAsBsonDocument import io.realm.kotlin.mongodb.ext.profileAsBsonDocument +import io.realm.kotlin.mongodb.mongo.MongoClient import io.realm.kotlin.mongodb.sync.SyncConfiguration /** @@ -201,6 +202,10 @@ public interface User { */ public suspend fun linkCredentials(credentials: Credentials): User + // TODO Doc + // TODO serializer argument + public fun mongoClient(serviceName: String): MongoClient + /** * Two Users are considered equal if they have the same user identity and are associated * with the same app. diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/FunctionsExt.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/FunctionsExt.kt index d387dbb624..348685c5cf 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/FunctionsExt.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/FunctionsExt.kt @@ -58,7 +58,7 @@ public suspend inline fun Functions.call( vararg args: Any? ): T = with(this as FunctionsImpl) { val serializedEjsonArgs = Bson.toJson(BsonEncoder.encodeToBsonValue(args.toList())) - val encodedResult = callInternal(name, serializedEjsonArgs) + val encodedResult = callInternal(name, serializedEjsonArgs = serializedEjsonArgs) BsonEncoder.decodeFromBsonValue( resultClass = T::class, @@ -90,26 +90,52 @@ public suspend inline fun Functions.call( * @param T the function return value type. * @return result of the function call. */ + @ExperimentalRealmSerializerApi @OptIn(ExperimentalKBsonSerializerApi::class) -public suspend inline fun Functions.call( +public suspend fun Functions.call2( name: String, callBuilderBlock: CallBuilder.() -> Unit -): T = with(this as FunctionsImpl) { - CallBuilder(app.configuration.ejson) - .apply(callBuilderBlock) - .run { - val serializedEjsonArgs = Bson.toJson(arguments) +): Pair, String> = + with(this as FunctionsImpl) { + val builder = CallBuilder(app.configuration.ejson) + builder + .apply(callBuilderBlock) + .run { + val serializedEjsonArgs: String = Bson.toJson(arguments) - val encodedResult = callInternal(name, serializedEjsonArgs) + val encodedResult: String = callInternal(name, serviceName = serviceName, serializedEjsonArgs = serializedEjsonArgs) - val returnValueSerializer = - returnValueSerializer - ?: ejson.serializersModule.serializerOrRealmBuiltInSerializer() + this to encodedResult + } + } - ejson.decodeFromString(returnValueSerializer, encodedResult) - } +@ExperimentalRealmSerializerApi +@OptIn(ExperimentalKBsonSerializerApi::class) +public suspend inline fun Functions.call( + name: String, + noinline callBuilderBlock: CallBuilder.() -> Unit +): T = call2(name, callBuilderBlock).run { + first.ejson.decodeFromString(first.returnValueSerializer ?: first.ejson.serializersModule.serializerOrRealmBuiltInSerializer(), second) } +//with(this as FunctionsImpl) { +// +// CallBuilder(app.configuration.ejson) +// .apply(callBuilderBlock) +// .run { +// val serializedEjsonArgs: String = Bson.toJson(arguments) +// +// val encodedResult: String = callInternal(name, serviceName = serviceName, serializedEjsonArgs = serializedEjsonArgs) +// +// val returnValueSerializer: KSerializer = +// returnValueSerializer +// ?: ejson.serializersModule.serializerOrRealmBuiltInSerializer() +// +// val r: T = ejson.decodeFromString(returnValueSerializer, encodedResult) +// r +// } + +//} /** * Builder used to construct a call defining serializers for the different arguments and return value. @@ -122,6 +148,9 @@ internal constructor( @PublishedApi internal val ejson: EJson, ) { +// internal val entries: MutableMap = mutableMapOf() +@PublishedApi +internal var serviceName: String? = null /** * Contains all given arguments transformed as [BsonValue]. The encoding is done on each [add] call * as in that context we have type information from the reified type. @@ -167,4 +196,12 @@ internal constructor( public inline fun add(argument: T, serializer: KSerializer) { arguments.add(ejson.encodeToBsonValue(serializer, argument)) } + +// public fun entry(key: String, value: String) { +// entries.put(key, value) +// } + @PublishedApi + internal fun serviceName(serviceName: String) { + this.serviceName = serviceName + } } diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/UserExt.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/UserExt.kt index 5f3a1527a9..f118ac0ce9 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/UserExt.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/UserExt.kt @@ -62,7 +62,7 @@ public inline fun User.customDataAsBsonDocument(): BsonDocument? = */ @ExperimentalRealmSerializerApi @OptIn(ExperimentalKBsonSerializerApi::class) -public fun User.profile(serializer: KSerializer): T = +public inline fun User.profile(serializer: KSerializer = (this as UserImpl).app.configuration.ejson.serializersModule.serializerOrRealmBuiltInSerializer()): T = (this as UserImpl).app.configuration.ejson.let { ejson: EJson -> profileInternal { ejsonEncodedProfile -> ejson.decodeFromString(serializer, ejsonEncodedProfile) @@ -78,17 +78,17 @@ public fun User.profile(serializer: KSerializer): T = * @param T the type to decoded the user profile. * @return The profile for this user. */ -@ExperimentalRealmSerializerApi -@OptIn(ExperimentalKBsonSerializerApi::class) -public inline fun User.profile(): T = - profile( - (this as UserImpl).app - .configuration - .ejson - .serializersModule - .serializerOrRealmBuiltInSerializer() - ) - +//@ExperimentalRealmSerializerApi +//@OptIn(ExperimentalKBsonSerializerApi::class) +//public inline fun User.profile(): T = +// profile( +// (this as UserImpl).app +// .configuration +// .ejson +// .serializersModule +// .serializerOrRealmBuiltInSerializer() +// ) +// /** * Returns the custom user data associated with the user in the Realm App as [T]. * @@ -101,16 +101,16 @@ public inline fun User.profile(): T = * @param T the type to decoded the user custom data. * @return The custom user data associated with the user. */ -@ExperimentalRealmSerializerApi -@OptIn(ExperimentalKBsonSerializerApi::class) -public inline fun User.customData(): T? = - customData( - (this as UserImpl).app - .configuration - .ejson - .serializersModule - .serializerOrRealmBuiltInSerializer() - ) +//@ExperimentalRealmSerializerApi +//@OptIn(ExperimentalKBsonSerializerApi::class) +//public inline fun User.customData(): T? = +// customData( +// (this as UserImpl).app +// .configuration +// .ejson +// .serializersModule +// .serializerOrRealmBuiltInSerializer() +// ) /** * Returns the custom user data associated with the user in the Realm App as [T]. @@ -127,7 +127,7 @@ public inline fun User.customData(): T? = */ @ExperimentalRealmSerializerApi @OptIn(ExperimentalKBsonSerializerApi::class) -public fun User.customData(serializer: KSerializer): T? = +public inline fun User.customData(serializer: KSerializer = (this as UserImpl).app.configuration.ejson.serializersModule.serializerOrRealmBuiltInSerializer()): T? = (this as UserImpl).app.configuration.ejson.let { ejson: EJson -> customDataInternal { ejsonEncodedCustomData -> ejson.decodeFromString( @@ -136,3 +136,4 @@ public fun User.customData(serializer: KSerializer): T? = ) } } + diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/FunctionsImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/FunctionsImpl.kt index 1a1cad2331..2ae96646ad 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/FunctionsImpl.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/FunctionsImpl.kt @@ -28,12 +28,14 @@ internal class FunctionsImpl( @PublishedApi internal suspend fun callInternal( name: String, + serviceName: String? = null, serializedEjsonArgs: String ): String = Channel>(1).use { channel -> RealmInterop.realm_app_call_function( app = app.nativePointer, user = user.nativePointer, name = name, + serviceName = serviceName, serializedEjsonArgs = serializedEjsonArgs, callback = channelResultCallback(channel) { ejsonEncodedObject: String -> // First we decode from ejson -> BsonValue diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoClientImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoClientImpl.kt new file mode 100644 index 0000000000..3ba5277d7b --- /dev/null +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoClientImpl.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2023 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.realm.kotlin.mongodb.internal + +import io.realm.kotlin.mongodb.mongo.MongoClient +import io.realm.kotlin.mongodb.mongo.MongoDatabase + +@PublishedApi +internal class MongoClientImpl( + @PublishedApi + internal val user: UserImpl, override val serviceName: String) : MongoClient { + override fun database(databaseName: String): MongoDatabase = MongoDatabaseImpl(this, databaseName) + +} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt new file mode 100644 index 0000000000..1c196dbf4c --- /dev/null +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt @@ -0,0 +1,65 @@ +/* + * Copyright 2023 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.realm.kotlin.mongodb.internal + +import io.realm.kotlin.annotations.ExperimentalRealmSerializerApi +import io.realm.kotlin.mongodb.ext.CallBuilder +import io.realm.kotlin.mongodb.ext.call +import io.realm.kotlin.mongodb.mongo.MongoCollection +import org.mongodb.kbson.BsonDocument +import org.mongodb.kbson.BsonInt64 +import org.mongodb.kbson.BsonString +import org.mongodb.kbson.BsonValue + +@PublishedApi +internal class MongoCollectionImpl( + + @PublishedApi internal val database: MongoDatabaseImpl, + override val name: String, +) : MongoCollection { + + val client = this.database.client + val user = client.user + val functions = user.functions + + val defaults: Map = mapOf( + "database" to BsonString(database.name), + "collection" to BsonString(name), + ) + + override suspend fun count(filter: BsonDocument?, limit: Long?): Int { + @OptIn(ExperimentalRealmSerializerApi::class) + return user.functions.call("count") { + serviceName(client.serviceName) + val args = defaults.toMutableMap() + limit?.let { args.put("limit", BsonInt64(limit)) } + filter?.let { args.put("query", it) } + add(BsonDocument(args)) + } + } +} + +@ExperimentalRealmSerializerApi +@PublishedApi +internal suspend inline fun MongoCollectionImpl.call(name: String, crossinline document: MutableMap.()-> Unit): BsonValue { + return user.functions.call(name) { + serviceName(client.serviceName) + val doc = this@call.defaults.toMutableMap() + document(doc) + add(doc) + } +} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDatabaseImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDatabaseImpl.kt new file mode 100644 index 0000000000..fd124bb2fa --- /dev/null +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDatabaseImpl.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2023 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.realm.kotlin.mongodb.internal + +import io.realm.kotlin.mongodb.mongo.MongoCollection +import io.realm.kotlin.mongodb.mongo.MongoDatabase + +@PublishedApi +internal class MongoDatabaseImpl( + @PublishedApi + internal val client: MongoClientImpl, + override val name: String, +) : MongoDatabase { + override fun collection(collectionName: String): MongoCollection = + MongoCollectionImpl(this, collectionName) +} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/UserImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/UserImpl.kt index 14be010962..0cfb36b840 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/UserImpl.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/UserImpl.kt @@ -28,6 +28,9 @@ import io.realm.kotlin.mongodb.UserIdentity import io.realm.kotlin.mongodb.auth.ApiKeyAuth import io.realm.kotlin.mongodb.exceptions.CredentialsCannotBeLinkedException import io.realm.kotlin.mongodb.exceptions.ServiceException +import io.realm.kotlin.mongodb.mongo.MongoClient +import io.realm.kotlin.mongodb.mongo.MongoCollection +import io.realm.kotlin.mongodb.mongo.MongoDatabase import kotlinx.coroutines.channels.Channel // TODO Public due to being a transitive dependency to SyncConfigurationImpl @@ -182,6 +185,8 @@ public class UserImpl( } } + override fun mongoClient(serviceName: String): MongoClient = MongoClientImpl(this, serviceName) + override fun equals(other: Any?): Boolean { if (this === other) return true if (other == null || this::class != other::class) return false diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt new file mode 100644 index 0000000000..93cd022da0 --- /dev/null +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2023 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.realm.kotlin.mongodb.mongo + +/** + * The remote MongoClient used for working with data in MongoDB remotely via Realm. + */ +public interface MongoClient { + + public val serviceName: String + + public fun database(databaseName: String): MongoDatabase +} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt new file mode 100644 index 0000000000..454a4b4c7c --- /dev/null +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt @@ -0,0 +1,343 @@ +/* + * Copyright 2023 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.realm.kotlin.mongodb.mongo + +import io.realm.kotlin.annotations.ExperimentalRealmSerializerApi +import io.realm.kotlin.mongodb.internal.MongoCollectionImpl +import io.realm.kotlin.mongodb.internal.call +import io.realm.kotlin.mongodb.internal.serializerOrRealmBuiltInSerializer +import kotlinx.serialization.KSerializer +import org.mongodb.kbson.BsonBoolean +import org.mongodb.kbson.BsonDocument +import org.mongodb.kbson.BsonInt64 +import org.mongodb.kbson.BsonValue +import org.mongodb.kbson.ExperimentalKBsonSerializerApi +import org.mongodb.kbson.serialization.EJson +import org.mongodb.kbson.serialization.decodeFromBsonValue + +public interface MongoCollection { + + public val name: String + + public suspend fun count(filter: BsonDocument? = null, limit: Long? = null): Int +// public suspend fun findOne(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null): BsonValue { +// TODO() +// } +} + +public interface TypedMongoCollection { + +} + + +@OptIn(ExperimentalKBsonSerializerApi::class) +@ExperimentalRealmSerializerApi +public suspend inline fun MongoCollection.findOne(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null): R { + return EJson.decodeFromBsonValue((this as MongoCollectionImpl).call("findOne") { + filter?.let { put("query", it) } + projection?.let { put("projection", it) } + sort?.let { put("sort", it) } + }) +} + +public class ClientOption { + +} + +@PublishedApi +internal interface FilterOptionInternal { + var filter: BsonDocument? +} +@PublishedApi +internal interface LimitOptionInternal { + var limit: Long? +} + +public interface LimitOption { } +public fun LimitOption.limit(limit: Long) { + (this as LimitOptionInternal).limit = limit +} +public interface FilterOption { } +public fun FilterOption.filter(json: String) { + (this as FilterOptionInternal).filter = BsonDocument(json) +} +@ExperimentalKBsonSerializerApi +public inline fun FilterOption.filter(argument: T) { + filter(argument, EJson.serializersModule.serializerOrRealmBuiltInSerializer()) +} +@OptIn(ExperimentalKBsonSerializerApi::class) +public inline fun FilterOption.filter(argument: T, serializer: KSerializer) { + (this as FilterOptionInternal).filter = EJson.encodeToBsonValue(serializer, argument).asDocument() +} +public interface ProjectionOption +public interface SortOption + +public interface CountOptions : LimitOption, FilterOption +public interface FindOneOptions : LimitOption, FilterOption, SortOption, ProjectionOption + +@PublishedApi +internal class FindOptionsInternal: FindOneOptions, FilterOptionInternal, LimitOptionInternal { + override var filter: BsonDocument? = null + override var limit: Long? = null +} + +//public suspend inline fun count(filter: Bson = {}, limit: Int = 0 ): Long +// query +// limit +@OptIn(ExperimentalKBsonSerializerApi::class) +@ExperimentalRealmSerializerApi +public suspend inline fun MongoCollection.count(filter: BsonDocument, limit: Long): Long { + return EJson.decodeFromBsonValue((this as MongoCollectionImpl).call("findOne") { + put("query", filter) + put("limit", BsonInt64(limit)) + }) +} + +//public suspend inline fun findOne(filter: Bson = {}, options: FindOptions(limit, projection, sort)): T +// query +// findoptions +// limit +// projection +// sort + +@OptIn(ExperimentalKBsonSerializerApi::class) +@ExperimentalRealmSerializerApi +public suspend inline fun MongoCollection.findOne(configuration: FindOneOptions.() -> Unit): T { + val options = FindOptionsInternal() + configuration(options) + val response = (this as MongoCollectionImpl).call("findOne") { + options.filter?.let { put("query", it) } +// options.projection?.let { put("projection", it) } +// options.sort?.let { put("sort", it) } + } + return EJson.decodeFromBsonValue(response) +} + +//@ExperimentalRealmSerializerApi +//@OptIn(ExperimentalKBsonSerializerApi::class) +//public suspend inline fun MongoCollection.insertOne(document: T): R { +// val value: KSerializer = EJson.serializersModule.serializerOrRealmBuiltInSerializer() +// val x: BsonValue = EJson.encodeToBsonValue(value, document) +// return (this as MongoCollectionImpl).call("insertOne") { +// put("document", x) +// } +//} + +//@ExperimentalRealmSerializerApi +//@OptIn(ExperimentalKBsonSerializerApi::class) +//public suspend inline fun MongoCollection.insertOneReturnId(document: T): ObjectId { +// val encodedDocument: BsonValue = EJson.encodeToBsonValue( +// EJson.serializersModule.serializerOrRealmBuiltInSerializer(), +// document +// ) +// return (this as MongoCollectionImpl).call("insertOne") { +// put("document", encodedDocument) +// }.asDocument().get("insertedId")!!.asObjectId() +//} + +// insertOne(doc:): Bson(insertedId) +// document +@ExperimentalRealmSerializerApi +@OptIn(ExperimentalKBsonSerializerApi::class) +public suspend inline fun MongoCollection.insertOne(document: T): R { + val encodedDocument: BsonValue = EJson.encodeToBsonValue( EJson.serializersModule.serializerOrRealmBuiltInSerializer(), document ) + val insertedId: BsonValue = (this as MongoCollectionImpl).call("insertOne") { + put("document", encodedDocument) + }.asDocument().get("insertedId")!! + return if (insertedId is R) { insertedId } else { EJson.decodeFromBsonValue(insertedId) } +} + + +// insertMany(docList): Map MongoCollection.insertMany(documents: Collection): List { + val encodedDocument: BsonValue = EJson.encodeToBsonValue(EJson.serializersModule.serializerOrRealmBuiltInSerializer(), documents) + val insertedId: List = (this as MongoCollectionImpl).call("insertMany") { + put("documents", encodedDocument) + }.asDocument().get("insertedIds")!!.asArray().toList() + return if (R::class == BsonValue::class) { insertedId as List } else { insertedId.map { EJson.decodeFromBsonValue(it) } } +} + +// deleteOne(filter): Count +// query +@ExperimentalRealmSerializerApi +@OptIn(ExperimentalKBsonSerializerApi::class) +public suspend inline fun MongoCollection.deleteOne(filter: BsonDocument): Boolean { +// val encodedDocument: BsonValue = EJson.encodeToBsonValue(EJson.serializersModule.serializerOrRealmBuiltInSerializer(), documents) + val insertedId: BsonValue = (this as MongoCollectionImpl).call("deleteOne") { + put("query", filter) + }.asDocument().get("deletedCount")!! + val decodeFromBsonValue = EJson.decodeFromBsonValue(insertedId) + return when(decodeFromBsonValue) { + 0L -> false + 1L -> true + else -> TODO("Unexpected $decodeFromBsonValue") + } +} + + +// deleteMany(filter): Count +// query +@ExperimentalRealmSerializerApi +@OptIn(ExperimentalKBsonSerializerApi::class) +public suspend inline fun MongoCollection.deleteMany(filter: BsonDocument): Long { + val insertedId: BsonValue = (this as MongoCollectionImpl).call("deleteMany") { + put("query", filter) + }.asDocument().get("deletedCount")!! + return EJson.decodeFromBsonValue(insertedId) +} + +// updateOne(filter, updateDoc, updateOptions(upsert: Boolean)): UpdateResult(matchCount, modifiedCount, upsertedId) +// updateMany(filter, updateDoc, updateOptions(upsert: Boolean)): UpdateResult(matchCount, modifiedCount, upsertedId) +// query +// update : BsonDocument +// upsert: Boolean + +// FIXME Would we also allow filter and update to be serializables? +// FIXME Could just return Boolean, since matchedCount=1,modifiedCount=1 even if multiple documents should be matching :thinking: +// FIXME Should we split into upsertOne, since response only contains 'upsertedId' if call has 'upsert:true` +@ExperimentalRealmSerializerApi +@OptIn(ExperimentalKBsonSerializerApi::class) +public suspend inline fun MongoCollection.updateOne( + filter: BsonDocument, + update: BsonDocument, + upsert: Boolean = false +): R { + val insertedId: BsonValue = (this as MongoCollectionImpl).call("updateOne") { + put("query", filter) + put("update", update) + put("upsert", BsonBoolean(upsert)) + }.asDocument()//.get("insertedId")!!.asArray().toList() + // {"matchedCount":{"$numberInt":"0"},"modifiedCount":{"$numberInt":"0"}} + + return if (R::class == BsonValue::class) { insertedId as R } else { EJson.decodeFromBsonValue(insertedId) } +} +@ExperimentalRealmSerializerApi +@OptIn(ExperimentalKBsonSerializerApi::class) +public suspend inline fun MongoCollection.updateMany( + filter: BsonDocument, + update: BsonDocument, + upsert: Boolean = false +): R { + val insertedId: BsonValue = (this as MongoCollectionImpl).call("updateMany") { + put("query", filter) + put("update", update) + put("upsert", BsonBoolean(upsert)) + }.asDocument().get("upsertedId")!! + // {"matchedCount":{"$numberInt":"0"},"modifiedCount":{"$numberInt":"0"}} + + return if (R::class == BsonValue::class) { insertedId as R } else { EJson.decodeFromBsonValue(insertedId) } +} + +// findOneAndUpdate(filter, update, options(projections, sort, upsert: Boolean, returnNewDoc)): T +@ExperimentalRealmSerializerApi +@OptIn(ExperimentalKBsonSerializerApi::class) +public suspend inline fun MongoCollection.findOneAndUpdate( + filter: BsonDocument, + update: BsonDocument, + projection: BsonDocument? = null, + sort: BsonDocument? = null, + upsert: Boolean = false, + returnNewDoc: Boolean = false, +): R { + val insertedId: BsonValue = (this as MongoCollectionImpl).call("findOneAndUpdate") { + put("filter", filter) + put("update", update) + projection?.let { put("projection", projection)} + sort?.let { put("sort", sort)} + put("upsert", BsonBoolean(upsert)) + put("returnNewDoc", BsonBoolean(returnNewDoc)) + }//.get("insertedIds")!!.asArray().toList() + // {"matchedCount":{"$numberInt":"0"},"modifiedCount":{"$numberInt":"0"}} + + return if (R::class == BsonValue::class) { + insertedId as R + } else { + EJson.decodeFromBsonValue(insertedId) + } +} +// findOneAndReplace(filter, update, options(projections, sort, upsert: Boolean, returnNewDoc)): T +// filter +// update : BsonDocu +// upsert: Boolean +// returnNewDoc: Boolean +// projection +// sort +@ExperimentalRealmSerializerApi +@OptIn(ExperimentalKBsonSerializerApi::class) +public suspend inline fun MongoCollection.findOneAndReplace( + filter: BsonDocument, + update: BsonDocument, + projection: BsonDocument? = null, + sort: BsonDocument? = null, + upsert: Boolean = false, + returnNewDoc: Boolean = false, +): R { + val insertedId: BsonValue = (this as MongoCollectionImpl).call("findOneAndReplace") { + put("filter", filter) + put("update", update) + projection?.let { put("projection", projection)} + sort?.let { put("sort", sort)} + put("upsert", BsonBoolean(upsert)) + put("returnNewDoc", BsonBoolean(returnNewDoc)) + }//.get("insertedIds")!!.asArray().toList() + // {"matchedCount":{"$numberInt":"0"},"modifiedCount":{"$numberInt":"0"}} + + return if (R::class == BsonValue::class) { + insertedId as R + } else { + EJson.decodeFromBsonValue(insertedId) + } +} + +// findOneAndDelete(filter, options(projections, sort, upsert: Boolean, returnNewDoc)): T +// filter +// upsert: Boolean +// returnNewDoc: Boolean +// projection +// sort +@ExperimentalRealmSerializerApi +@OptIn(ExperimentalKBsonSerializerApi::class) +public suspend inline fun MongoCollection.findOneAndDelete( + filter: BsonDocument, + update: BsonDocument, + projection: BsonDocument? = null, + sort: BsonDocument? = null, + upsert: Boolean = false, +): R { + val insertedId: BsonValue = (this as MongoCollectionImpl).call("findOneAndDelete") { + put("filter", filter) + put("update", update) + projection?.let { put("projection", projection)} + sort?.let { put("sort", sort)} + put("upsert", BsonBoolean(upsert)) + }//.get("insertedIds")!!.asArray().toList() + // {"matchedCount":{"$numberInt":"0"},"modifiedCount":{"$numberInt":"0"}} + + return if (R::class == BsonValue::class) { + insertedId as R + } else { + EJson.decodeFromBsonValue(insertedId) + } +} + +// watch?? diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoDatabase.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoDatabase.kt new file mode 100644 index 0000000000..4534000d2b --- /dev/null +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoDatabase.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2023 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.realm.kotlin.mongodb.mongo + +/** + * A handle to a remote **Atlas App Service database** that provides access to its [MongoCollection]s. + */ +public interface MongoDatabase { + + /** + * Name of the remote database. + */ + public val name: String + + public fun collection(collectionName: String): MongoCollection + +} diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppAdmin.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppAdmin.kt index 8e79a606df..479d4c6e7e 100644 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppAdmin.kt +++ b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppAdmin.kt @@ -96,6 +96,8 @@ interface AppAdmin { */ suspend fun countDocuments(clazz: String): Int + suspend fun deleteDocuments(database: String, clazz: String, query: String): JsonObject? + fun closeClient() } @@ -191,6 +193,11 @@ class AppAdminImpl( app.countDocuments(clazz) } + override suspend fun deleteDocuments(database: String, clazz: String, query: String): JsonObject? = + baasClient.run { + app.deleteDocument(database, clazz, query) + } + override fun closeClient() { baasClient.closeClient() } diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppServicesClient.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppServicesClient.kt index 8cd6982fd1..e9f715a894 100644 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppServicesClient.kt +++ b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppServicesClient.kt @@ -623,7 +623,7 @@ class AppServicesClient( } ?: throw IllegalStateException("Unexpected result: $result") } - private suspend fun BaasApp.deleteDocument( + suspend fun BaasApp.deleteDocument( db: String, clazz: String, query: String diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTest.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTest.kt new file mode 100644 index 0000000000..5eb16d866c --- /dev/null +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTest.kt @@ -0,0 +1,1429 @@ +/* + * Copyright 2023 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:OptIn(ExperimentalRealmSerializerApi::class, ExperimentalRealmSerializerApi::class) + +package io.realm.kotlin.test.mongodb.common.mongo + +import io.realm.kotlin.annotations.ExperimentalRealmSerializerApi +import io.realm.kotlin.internal.platform.runBlocking +import io.realm.kotlin.log.LogLevel +import io.realm.kotlin.log.RealmLog +import io.realm.kotlin.mongodb.AppConfiguration +import io.realm.kotlin.mongodb.exceptions.ServiceException +import io.realm.kotlin.mongodb.mongo.MongoClient +import io.realm.kotlin.mongodb.mongo.MongoCollection +import io.realm.kotlin.mongodb.mongo.MongoDatabase +import io.realm.kotlin.mongodb.mongo.deleteMany +import io.realm.kotlin.mongodb.mongo.deleteOne +import io.realm.kotlin.mongodb.mongo.findOne +import io.realm.kotlin.mongodb.mongo.findOneAndDelete +import io.realm.kotlin.mongodb.mongo.findOneAndReplace +import io.realm.kotlin.mongodb.mongo.findOneAndUpdate +import io.realm.kotlin.mongodb.mongo.insertMany +import io.realm.kotlin.mongodb.mongo.insertOne +import io.realm.kotlin.mongodb.mongo.updateMany +import io.realm.kotlin.mongodb.mongo.updateOne +import io.realm.kotlin.test.mongodb.TestApp +import io.realm.kotlin.test.mongodb.asTestApp + +import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage +import io.realm.kotlin.types.RealmObject +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import org.mongodb.kbson.BsonDocument +import org.mongodb.kbson.BsonString +import org.mongodb.kbson.BsonValue +import org.mongodb.kbson.ExperimentalKBsonSerializerApi +import org.mongodb.kbson.ObjectId +import org.mongodb.kbson.serialization.Bson +import org.mongodb.kbson.serialization.EJson +import org.mongodb.kbson.serialization.encodeToBsonValue +import kotlin.random.Random +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertIs +import kotlin.test.assertNull +import kotlin.test.assertTrue + +private const val SERVICE_NAME = "BackingDB" + +@OptIn(ExperimentalRealmSerializerApi::class) +class MongoClientTest { + + lateinit var app: TestApp + lateinit var client: MongoClient + lateinit var database: MongoDatabase + lateinit var collection: MongoCollection + + @BeforeTest + fun setUp() { + app = TestApp( + this::class.simpleName, + builder = { builder: AppConfiguration.Builder -> + builder.httpLogObfuscator(null) + } + ) + val databaseName = app.configuration.appId + + app.asTestApp.run { + runBlocking { + deleteDocuments(databaseName, "SyncDog", "{}") + } + } + val user = app.createUserAndLogin() + client = user.mongoClient(SERVICE_NAME) + database = client.database(databaseName) + collection = database.collection("SyncDog") + } + + @AfterTest + fun teadDown() { + RealmLog.level = LogLevel.WARN + if (this::app.isInitialized) { + app.close() + } + } + + @Test + fun properties() { + assertEquals("SyncDog", collection.name) + assertEquals(app.configuration.appId, database.name) + assertEquals(SERVICE_NAME, client.serviceName) + } + + @Test + fun count() = runBlocking { + assertEquals(0, collection.count()) + + app.asTestApp.run { + (1..10).forEach { + insertDocument( + "SyncDog", + Json.encodeToString(SyncDog.serializer(), SyncDog("dog-${it % 5}")) + ) + } + assertEquals(10, countDocuments("SyncDog")) + } + + assertEquals(10, collection.count()) + assertEquals(5, collection.count(limit = 5)) + assertEquals(2, collection.count(filter = BsonDocument("name" to BsonString("dog-0")))) + assertEquals(2, collection.count(filter = BsonDocument("name", "dog-0"))) + assertEquals( + 1, + collection.count(filter = BsonDocument("name" to BsonString("dog-0")), limit = 1) + ) + } + + @Test + fun count_invalidFilter() = runBlocking { + assertFailsWithMessage("operator") { + collection.count(filter = BsonDocument("\$who", "dog-0")) + } + } + + @Test + fun findOne() = runBlocking { + assertNull(collection.findOne()) + + app.asTestApp.run { + (1..10).forEach { + insertDocument( + "SyncDog", + Json.encodeToString(SyncDog.serializer(), SyncDog("dog-${it % 5}")) + ) + } + assertEquals(10, countDocuments("SyncDog")) + } + + // No match + assertNull(collection.findOne(filter = BsonDocument("name", "cat"))) + + // Only on document even though multiple matches + assertIs(collection.findOne(filter = BsonDocument("name", "dog-0"))) + + // Projection select field + // Limit + // Sort + } + + @OptIn(ExperimentalKBsonSerializerApi::class) +// @Test +// fun findOne2() = runBlocking { +// val dog = SyncDog("dog-1") +// val dog1 = collection.findOne() +// assertNull(dog1) +// +// app.asTestApp.run { +// (1..10).forEach { +// insertDocument( +// "SyncDog", +// Json.encodeToString(SyncDog.serializer(), SyncDog("dog-${it % 5}")) +// ) +// } +// assertEquals(10, countDocuments("SyncDog")) +// } +// +// // No match +// assertNull(collection.findOne(filter = BsonDocument("name" , "cat"))) +// +// // Only on document even though multiple matches +// assertIs(collection.findOne(filter = BsonDocument("name", "dog-0"))) +// +// val dog2: SyncDog = collection.findOne { +// filter("""{ "name": "dog-1" }""") +//// arg(SyncDog()) +//// filter(BsonDocument("""{ "name": "dog-1" }""")) +//// filter(BsonDocument("asdf")) +//// projection(SyncDog::members) +// +//// sort() +// } +// assertEquals(dog, dog2) +// +// val dog3: SyncDog = collection.findOne { +// filter(BsonDocument("name" to BsonString("dog-1"))) +//// arg(SyncDog()) +//// filter(BsonDocument("""{ "name": "dog-1" }""")) +//// filter(BsonDocument("asdf")) +//// projection(SyncDog::members) +// +//// sort() +// } +// assertEquals(dog, dog3) +// +// @Serializable +// data class Filter(val name: String) +// +// val dog4: SyncDog = collection.findOne { +// filter(Filter("dog-1")) +//// arg(SyncDog()) +//// filter(BsonDocument("""{ "name": "dog-1" }""")) +//// filter(BsonDocument("asdf")) +//// projection(SyncDog::members) +// +//// sort() +// } +// assertEquals(dog, dog4) +// +//// val dog5 = collection.findOne { +//// query = """{ "name": "dog-1" }""" +//// query = BsonDocument("""{ "name": "dog-1" }""") +//// } +// } +// with(getCollectionInternal()) { +// // Test findOne() on empty collection with no filter and no options +// assertNull(findOne().get()) +// +// // Test findOne() with filter that does not match any documents and no options +// assertNull(findOne(Document("hello", "worldDNE")).get()) +// +// val doc1 = Document("hello", "world1") +// insertOne(doc1).get() +// assertEquals(1, count().get()) +// +// // Test findOne() with filter that does not match any documents and no options +// assertNull(findOne(Document("hello", "worldDNE")).get()) +// } +// } +// +// @Test +// fun findOne_singleDocument() { +// with(getCollectionInternal()) { +// val doc1 = Document("hello", "world1") +// +// // Insert one document +// insertOne(doc1).get() +// assertEquals(1, count().get()) +// +// // No filter and no options +// assertEquals(doc1, findOne().get()!!.withoutId()) +// +// // Projection (remove "_id") options +// val projection = Document("hello", 1).apply { this["_id"] = 0 } +// var options = FindOptions() +// .limit(2) +// .projection(projection) +// assertEquals(doc1, findOne(Document(), options).get()!!) +// +// // Projection (remove "_id") and sort (by desc "hello") options +// options = FindOptions() +// .limit(2) +// .projection(projection) +// .sort(Document("hello", -1)) +// assertEquals(doc1, findOne(Document(), options).get()!!) +// } +// } +// +// @Test +// fun findOne_multipleDocuments() { +// with(getCollectionInternal()) { +// val doc1 = Document("hello", "world1") +// val doc2 = Document("hello", "world2") +// val doc3 = Document("hello", "world3") +// +// // Insert 3 documents +// insertMany(listOf(doc1, doc2, doc3)).get() +// assertEquals(3, count().get()) +// +// // Projection (remove "_id") and sort (by asc "hello") options +// val projection = Document("hello", 1).apply { this["_id"] = 0 } +// var options = FindOptions() +// .limit(2) +// .projection(projection) +// .sort(Document("hello", 1)) +// assertEquals(doc1, findOne(Document(), options).get()!!) +// +// // Projection (remove "_id") and sort (by desc "hello") options +// options = FindOptions() +// .limit(2) +// .projection(projection) +// .sort(Document("hello", -1)) +// assertEquals(doc3, findOne(Document(), options).get()!!) +// } +// } +// +// @Test +// fun findOne_fails() { +// with(getCollectionInternal()) { +// assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { +// findOne(Document("\$who", 1)).get() +// }.also { e -> +// assertTrue(e.errorMessage!!.contains("operator", true)) +// } +// } +// } +// +// @Test +// fun find() { +// with(getCollectionInternal()) { +// // Find on an empty collection returns false on hasNext and null on first +// var iter = find() +// assertFalse(iter.iterator().get()!!.hasNext()) +// assertNull(iter.first().get()) +// +// val doc1 = Document("hello", "world") +// val doc2 = Document("hello", "friend") +// doc2["proj"] = "field" +// insertMany(listOf(doc1, doc2)).get() +// +// // Iterate after inserting two documents +// assertTrue(iter.iterator().get()!!.hasNext()) +// assertEquals(doc1, iter.first().get()!!.withoutId()) +// +// // Get next with sort by desc "_id" and limit to 1 document +// assertEquals(doc2, +// iter.limit(1) +// .sort(Document("_id", -1)) +// .iterator().get()!! +// .next().withoutId()) +// +// // Find first document +// iter = find(doc1) +// assertTrue(iter.iterator().get()!!.hasNext()) +// assertEquals(doc1, +// iter.iterator().get()!! +// .next().withoutId()) +// +// // Find with filter for first document +// iter = find().filter(doc1) +// assertTrue(iter.iterator().get()!!.hasNext()) +// assertEquals(doc1, +// iter.iterator().get()!! +// .next().withoutId()) +// +// // Find with projection shows "proj" in result +// val expected = Document("proj", "field") +// assertEquals(expected, +// find(doc2) +// .projection(Document("proj", 1)) +// .iterator().get()!! +// .next().withoutId()) +// +// // Getting a new iterator returns first element on tryNext +// val asyncIter = iter.iterator().get()!! +// assertEquals(doc1, asyncIter.tryNext().withoutId()) +// } +// } +// +// @Test +// fun find_fails() { +// with(getCollectionInternal()) { +// assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { +// find(Document("\$who", 1)).first().get() +// }.also { e -> +// assertTrue(e.errorMessage!!.contains("operator", true)) +// } +// } +// } +// +// @Test +// fun aggregate() { +// with(getCollectionInternal()) { +// // Aggregate on an empty collection returns false on hasNext and null on first +// var iter = aggregate(listOf()) +// assertFalse(iter.iterator().get()!!.hasNext()) +// assertNull(iter.first().get()) +// +// // Iterate after inserting two documents +// val doc1 = Document("hello", "world") +// val doc2 = Document("hello", "friend") +// insertMany(listOf(doc1, doc2)).get() +// assertTrue(iter.iterator().get()!!.hasNext()) +// assertEquals(doc1.withoutId(), iter.first().get()!!.withoutId()) +// +// // Aggregate with pipeline, sort by desc "_id" and limit to 1 document +// iter = aggregate(listOf(Document("\$sort", Document("_id", -1)), Document("\$limit", 1))) +// assertEquals(doc2.withoutId(), +// iter.iterator().get()!! +// .next().withoutId()) +// +// // Aggregate with pipeline, match first document +// iter = aggregate(listOf(Document("\$match", doc1))) +// assertTrue(iter.iterator().get()!!.hasNext()) +// assertEquals(doc1.withoutId(), iter.iterator().get()!!.next().withoutId()) +// } +// } +// +// @Test +// fun aggregate_fails() { +// with(getCollectionInternal()) { +// assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { +// aggregate(listOf(Document("\$who", 1))).first().get() +// }.also { e -> +// assertTrue(e.errorMessage!!.contains("pipeline", true)) +// } +// } +// } +// + +// @Test +// fun insertOne() = runBlocking { +// with(getCollectionInternal()) { +// val doc1 = Document("hello", "world").apply { this["_id"] = ObjectId() } +// assertEquals(doc1.getObjectId("_id"), insertOne(doc1).get()!!.insertedId.asObjectId().value) +// assertEquals(1, count().get()) +// +// val doc2 = Document("hello", "world") +// val insertOneResult = insertOne(doc2).get()!! +// assertNotNull(insertOneResult.insertedId.asObjectId().value) +// assertEquals(2, count().get()) +// } +// } + + + @Test + fun insertOne() = runBlocking { +// with(getCollectionInternal()) { +// val doc1 = Document("hello", "world").apply { this["_id"] = ObjectId() } +// assertEquals(doc1.getObjectId("_id"), insertOne(doc1).get()!!.insertedId.asObjectId().value) +// assertEquals(1, count().get()) +// +// val doc2 = Document("hello", "world") +// val insertOneResult = insertOne(doc2).get()!! +// assertNotNull(insertOneResult.insertedId.asObjectId().value) +// assertEquals(2, count().get()) +// } + + // Option 1 - Typed return value - BsonValue + val insertedIdDocument: BsonValue = collection.insertOne(BsonDocument("name", "sadffds")) + val insertedIdDocument2: BsonValue = collection.insertOne(SyncDog("sadf")) + + // Option 2 - Explicit generic arguments to enabling fluent API + val id = collection.insertOne(SyncDog("sadf")) + println(id) + + // Option 3 - Automatically serialized object + val x2: ObjectId = collection.insertOne(SyncDog("sadf")) + println(x2) + } + +// +// @Test +// fun insertOne_throwsWhenMixingIdTypes() { +// with(getCollectionInternal()) { +// // The default collection uses ObjectId for "_id" +// val doc1 = Document("hello", "world").apply { this["_id"] = 666 } +// assertFailsWith { +// insertOne(doc1).get()!! +// }.let { e -> +// assertEquals("insert not permitted", e.errorMessage) +// } +// } +// } +// +// @Test +// fun insertOne_integerId() { +// with(getCollectionInternal(COLLECTION_NAME_ALT)) { +// val doc1 = Document("hello", "world").apply { this["_id"] = 666 } +// val insertOneResult = insertOne(doc1).get()!! +// assertEquals(doc1.getInteger("_id"), insertOneResult.insertedId.asInt32().value) +// assertEquals(1, count().get()) +// } +// } +// +// @Test +// fun insertOne_fails() { +// with(getCollectionInternal()) { +// val doc1 = Document("hello", "world").apply { this["_id"] = ObjectId() } +// insertOne(doc1).get() +// +// assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { +// insertOne(doc1).get() +// }.also { e -> +// assertTrue(e.errorMessage!!.contains("duplicate", true)) +// } +// } +// } + + @Test + fun insertMany() = runBlocking { + RealmLog.level = LogLevel.ALL + val x: List = collection.insertMany(listOf(SyncDog("a"))) + val syncDogIntIdCollection = database.collection("SyncDogIntId") + val elements = SyncDogIntId("a", Random.nextInt()) + val y: List = syncDogIntIdCollection.insertMany(listOf(elements)) + + assertFailsWithMessage("dup key") { + val z: List = syncDogIntIdCollection.insertMany(listOf(elements)) + } + println(x) + println(y) + + } +// +// @Test +// fun insertMany_singleDocument() { +// with(getCollectionInternal()) { +// val doc1 = Document("hello", "world").apply { this["_id"] = ObjectId() } +// +// assertEquals(doc1.getObjectId("_id"), +// insertMany(listOf(doc1)).get()!!.insertedIds[0]!!.asObjectId().value) +// val doc2 = Document("hello", "world") +// +// assertNotEquals(doc1.getObjectId("_id"), insertMany(listOf(doc2)).get()!!.insertedIds[0]!!.asObjectId().value) +// +// val doc3 = Document("one", "two") +// val doc4 = Document("three", 4) +// +// insertMany(listOf(doc3, doc4)).get() +// } +// } +// +// @Test +// fun insertMany_singleDocument_fails() { +// with(getCollectionInternal()) { +// val doc1 = Document("hello", "world").apply { this["_id"] = ObjectId() } +// insertMany(listOf(doc1)).get() +// +// assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { +// insertMany(listOf(doc1)).get() +// }.also { e -> +// assertTrue(e.errorMessage!!.contains("duplicate", true)) +// } +// } +// } +// +// @Test +// fun insertMany_multipleDocuments() { +// with(getCollectionInternal()) { +// val doc1 = Document("hello", "world").apply { this["_id"] = ObjectId() } +// val doc2 = Document("hello", "world").apply { this["_id"] = ObjectId() } +// val documents = listOf(doc1, doc2) +// +// insertMany(documents).get()!! +// .insertedIds +// .forEach { entry -> +// assertEquals(documents[entry.key.toInt()]["_id"], entry.value.asObjectId().value) +// } +// +// val doc3 = Document("one", "two") +// val doc4 = Document("three", 4) +// +// insertMany(listOf(doc3, doc4)).get() +// assertEquals(4, count().get()) +// } +// } +// +// @Test +// fun insertMany_multipleDocuments_IntegerId() { +// with(getCollectionInternal(COLLECTION_NAME_ALT)) { +// val doc1 = Document("hello", "world").apply { this["_id"] = 42 } +// val doc2 = Document("hello", "world").apply { this["_id"] = 42 + 1 } +// val documents = listOf(doc1, doc2) +// +// insertMany(documents).get()!! +// .insertedIds +// .forEach { entry -> +// assertEquals(documents[entry.key.toInt()]["_id"], entry.value.asInt32().value) +// } +// +// val doc3 = Document("one", "two") +// val doc4 = Document("three", 4) +// +// insertMany(listOf(doc3, doc4)).get() +// assertEquals(4, count().get()) +// } +// } +// +// @Test +// fun insertMany_throwsWhenMixingIdTypes() { +// with(getCollectionInternal()) { +// val doc1 = Document("hello", "world").apply { this["_id"] = 42 } +// val doc2 = Document("hello", "world").apply { this["_id"] = 42 + 1 } +// val documents = listOf(doc1, doc2) +// +// assertFailsWith { +// insertMany(documents).get()!! +// }.let { e -> +// assertEquals("insert not permitted", e.errorMessage) +// } +// } +// } +// +// @Test +// fun insertMany_multipleDocuments_fails() { +// with(getCollectionInternal()) { +// val doc1 = Document("hello", "world").apply { this["_id"] = ObjectId() } +// val doc2 = Document("hello", "world").apply { this["_id"] = ObjectId() } +// val documents = listOf(doc1, doc2) +// insertMany(documents).get() +// +// assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { +// insertMany(documents).get() +// }.also { e -> +// assertTrue(e.errorMessage!!.contains("duplicate", true)) +// } +// } +// } +// + + @Test + fun deleteOne() = runBlocking { + // Argument wrapper DSL + RealmLog.level = LogLevel.ALL + assertFalse { collection.deleteOne(BsonDocument()) } + + // A bit ann + val elements = SyncDog("x") + assertEquals(2, collection.insertMany(listOf(elements, elements)).size) + assertTrue { collection.deleteOne(BsonDocument("""{ "name": "x" }""")) } + } + + // @Test +// fun deleteOne_singleDocument() { +// with(getCollectionInternal()) { +// assertEquals(0, deleteOne(Document()).get()!!.deletedCount) +// assertEquals(0, deleteOne(Document("hello", "world")).get()!!.deletedCount) +// +// val doc1 = Document("hello", "world") +// +// insertOne(doc1).get() +// assertEquals(1, deleteOne(doc1).get()!!.deletedCount) +// assertEquals(0, count().get()) +// } +// } +// +// @Test +// fun deleteOne_fails() { +// with(getCollectionInternal()) { +// assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { +// deleteOne(Document("\$who", 1)).get() +// }.also { e -> +// assertTrue(e.errorMessage!!.contains("operator", true)) +// } +// } +// } +// +// @Test +// fun deleteOne_multipleDocuments() { +// with(getCollectionInternal()) { +// assertEquals(0, count().get()) +// +// val rawDoc = Document("hello", "world") +// val doc1 = Document(rawDoc) +// val doc1b = Document(rawDoc) +// val doc2 = Document("foo", "bar") +// val doc3 = Document("42", "666") +// insertMany(listOf(doc1, doc1b, doc2, doc3)).get() +// assertEquals(1, deleteOne(rawDoc).get()!!.deletedCount) +// assertEquals(1, deleteOne(Document()).get()!!.deletedCount) +// assertEquals(2, count().get()) +// } +// } +// + @Test + fun deleteMany() = runBlocking { + // Argument wrapper DSL + RealmLog.level = LogLevel.ALL + assertEquals(0, collection.deleteMany(BsonDocument())) + + // A bit ann + val elements = SyncDog("x") + assertEquals(2, collection.insertMany(listOf(elements, elements)).size) + assertEquals(2, collection.deleteMany(BsonDocument("""{ "name": "x" }"""))) + + assertEquals( + 3, + collection.insertMany(listOf(elements, elements, elements)).size + ) + assertEquals(3, collection.deleteMany(BsonDocument())) + } + + // @Test +// fun deleteMany_singleDocument() { +// with(getCollectionInternal()) { +// assertEquals(0, count().get()) +// +// val rawDoc = Document("hello", "world") +// val doc1 = Document(rawDoc) +// +// insertOne(doc1).get() +// assertEquals(1, count().get()) +// assertEquals(1, deleteMany(doc1).get()!!.deletedCount) +// assertEquals(0, count().get()) +// } +// } +// +// @Test +// fun deleteMany_multipleDocuments() { +// with(getCollectionInternal()) { +// assertEquals(0, count().get()) +// +// val rawDoc = Document("hello", "world") +// val doc1 = Document(rawDoc) +// val doc1b = Document(rawDoc) +// val doc2 = Document("foo", "bar") +// val doc3 = Document("42", "666") +// insertMany(listOf(doc1, doc1b, doc2, doc3)).get() +// assertEquals(2, deleteMany(rawDoc).get()!!.deletedCount) // two docs will be deleted +// assertEquals(2, count().get()) // two docs still present +// assertEquals(2, deleteMany(Document()).get()!!.deletedCount) // delete all +// assertEquals(0, count().get()) +// +// insertMany(listOf(doc1, doc1b, doc2, doc3)).get() +// assertEquals(4, deleteMany(Document()).get()!!.deletedCount) // delete all +// assertEquals(0, count().get()) +// } +// } +// +// @Test +// fun deleteMany_fails() { +// with(getCollectionInternal()) { +// assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { +// deleteMany(Document("\$who", 1)).get() +// }.also { e -> +// assertTrue(e.errorMessage!!.contains("operator", true)) +// } +// } +// } +// + @Test + fun updateOne() = runBlocking { + // Argument wrapper DSL + RealmLog.level = LogLevel.ALL + + val elements = SyncDog("x") + assertEquals(2, collection.insertMany(listOf(elements, elements)).size) + assertEquals(2, collection.count(filter = BsonDocument("""{"name": "x"}"""))) + + // A bit ann + collection.updateOne( + BsonDocument("""{ "name": "x"}"""), + BsonDocument("""{ "name": "y"}"""), + true + ) +// assertEquals(2, collection.deleteMany(BsonDocument("""{ "name": "x" }""" )) ) + +// assertEquals(3, collection.insertMany(listOf(elements, elements, elements)).size) +// assertEquals(3, collection.deleteMany(BsonDocument()) ) + + collection.updateOne( + BsonDocument("""{ "name": "z"}"""), + BsonDocument(""" { "name": "y"}"""), + upsert = true + ) + } + + // @Test +// fun updateOne_emptyCollection() { +// with(getCollectionInternal()) { +// val doc1 = Document("hello", "world") +// +// // Update on an empty collection +// updateOne(Document(), doc1) +// .get()!! +// .let { +// assertEquals(0, it.matchedCount) +// assertEquals(0, it.modifiedCount) +// assertNull(it.upsertedId) +// } +// +// // Update on an empty collection adding some values +// val doc2 = Document("\$set", Document("woof", "meow")) +// updateOne(Document(), doc2) +// .get()!! +// .let { +// assertEquals(0, it.matchedCount) +// assertEquals(0, it.modifiedCount) +// assertNull(it.upsertedId) +// assertEquals(0, count().get()) +// } +// } +// } +// +// @Test +// fun updateOne_emptyCollectionWithUpsert() { +// with(getCollectionInternal()) { +// val doc1 = Document("hello", "world") +// +// // Update on empty collection with upsert +// val options = UpdateOptions().upsert(true) +// updateOne(Document(), doc1, options) +// .get()!! +// .let { +// assertEquals(0, it.matchedCount) +// assertEquals(0, it.modifiedCount) +// assertFalse(it.upsertedId!!.isNull) +// } +// assertEquals(1, count().get()) +// +// assertEquals(doc1, find(Document()).first().get()!!.withoutId()) +// } +// } +// +// @Test +// fun updateOne_fails() { +// with(getCollectionInternal()) { +// assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { +// updateOne(Document("\$who", 1), Document()).get() +// }.also { e -> +// assertTrue(e.errorMessage!!.contains("operator", true)) +// } +// } +// } +// + @Test + fun updateMany() = runBlocking { + RealmLog.level = LogLevel.ALL + assertEquals( + 4, + collection.insertMany( + listOf( + SyncDog("x"), + SyncDog("x"), + SyncDog("y"), + SyncDog("z") + ) + ).size + ) + assertEquals(2, collection.count(filter = BsonDocument("""{"name": "x"}"""))) + collection.updateMany( + BsonDocument("""{"name": "x2"}"""), + BsonDocument("""{"name": "x1"}"""), + true + ) + } + + // @Test +// fun updateMany_emptyCollection() { +// with(getCollectionInternal()) { +// val doc1 = Document("hello", "world") +// +// // Update on empty collection +// updateMany(Document(), doc1) +// .get()!! +// .let { +// assertEquals(0, it.matchedCount) +// assertEquals(0, it.modifiedCount) +// assertNull(it.upsertedId) +// } +// assertEquals(0, count().get()) +// } +// } +// +// @Test +// fun updateMany_emptyCollectionWithUpsert() { +// with(getCollectionInternal()) { +// val doc1 = Document("hello", "world") +// +// // Update on empty collection with upsert +// updateMany(Document(), doc1, UpdateOptions().upsert(true)) +// .get()!! +// .let { +// assertEquals(0, it.matchedCount) +// assertEquals(0, it.modifiedCount) +// assertNotNull(it.upsertedId) +// } +// assertEquals(1, count().get()) +// +// // Add new value using update +// val update = Document("woof", "meow") +// updateMany(Document(), Document("\$set", update)) +// .get()!! +// .let { +// assertEquals(1, it.matchedCount) +// assertEquals(1, it.modifiedCount) +// assertNull(it.upsertedId) +// } +// assertEquals(1, count().get()) +// val expected = Document(doc1).apply { this["woof"] = "meow" } +// assertEquals(expected, find().first().get()!!.withoutId()) +// +// // Insert empty document, add ["woof", "meow"] to it and check it worked +// insertOne(Document()).get() +// updateMany(Document(), Document("\$set", update)) +// .get()!! +// .let { +// assertEquals(2, it.matchedCount) +// assertEquals(2, it.modifiedCount) +// } +// assertEquals(2, count().get()) +// find().iterator() +// .get()!! +// .let { +// assertEquals(expected, it.next().withoutId()) +// assertEquals(update, it.next().withoutId()) +// assertFalse(it.hasNext()) +// } +// } +// } +// +// @Test +// fun updateMany_fails() { +// with(getCollectionInternal()) { +// assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { +// updateMany(Document("\$who", 1), Document()).get() +// }.also { e -> +// assertTrue(e.errorMessage!!.contains("operator", true)) +// } +// } +// } +// + @Test + fun findOneAndUpdate() = runBlocking { + RealmLog.level = LogLevel.ALL + assertNull(collection.findOneAndUpdate(BsonDocument(), BsonDocument())) + collection.insertMany( + listOf( + SyncDog("dog1"), + SyncDog("dog1"), + SyncDog("dog2") + ) + ) + collection.findOneAndUpdate( + BsonDocument(), + BsonDocument("""{ "name": "dog1" }"""), + upsert = true + ) + } +// @Test +// fun findOneAndUpdate_emptyCollection() { +// with(getCollectionInternal()) { +// // Test null return format +// assertNull(findOneAndUpdate(Document(), Document()).get()) +// } +// } +// +// @Test +// fun findOneAndUpdate_noUpdates() { +// with(getCollectionInternal()) { +// assertNull(findOneAndUpdate(Document(), Document()).get()) +// assertEquals(0, count().get()) +// } +// } +// +// @Test +// fun findOneAndUpdate_noUpsert() { +// with(getCollectionInternal()) { +// val sampleDoc = Document("hello", "world1") +// sampleDoc["num"] = 2 +// +// // Insert a sample Document +// insertOne(sampleDoc).get() +// assertEquals(1, count().get()) +// +// // Sample call to findOneAndUpdate() where we get the previous document back +// val sampleUpdate = Document("\$set", Document("hello", "hellothere")).apply { +// this["\$inc"] = Document("num", 1) +// } +// findOneAndUpdate(Document("hello", "world1"), sampleUpdate) +// .get()!! +// .withoutId() +// .let { +// assertEquals(sampleDoc.withoutId(), it) +// } +// assertEquals(1, count().get()) +// +// // Make sure the update took place +// val expectedDoc = Document("hello", "hellothere") +// expectedDoc["num"] = 3 +// assertEquals(expectedDoc.withoutId(), find().first().get()!!.withoutId()) +// assertEquals(1, count().get()) +// +// // Call findOneAndUpdate() again but get the new document +// sampleUpdate.remove("\$set") +// expectedDoc["num"] = 4 +// val options = FindOneAndModifyOptions() +// .returnNewDocument(true) +// findOneAndUpdate(Document("hello", "hellothere"), sampleUpdate, options) +// .get()!! +// .withoutId() +// .let { +// assertEquals(expectedDoc.withoutId(), it) +// } +// assertEquals(1, count().get()) +// +// // Test null behaviour again with a filter that should not match any documents +// assertNull(findOneAndUpdate(Document("hello", "zzzzz"), Document()).get()) +// assertEquals(1, count().get()) +// } +// } +// +// @Test +// fun findOneAndUpdate_upsert() { +// with(getCollectionInternal()) { +// val doc1 = Document("hello", "world1").apply { this["num"] = 1 } +// val doc2 = Document("hello", "world2").apply { this["num"] = 2 } +// val doc3 = Document("hello", "world3").apply { this["num"] = 3 } +// +// val filter = Document("hello", "hellothere") +// +// // Test the upsert option where it should not actually be invoked +// var options = FindOneAndModifyOptions() +// .returnNewDocument(true) +// .upsert(true) +// val update1 = Document("\$set", doc1) +// assertEquals(doc1, +// findOneAndUpdate(filter, update1, options) +// .get()!! +// .withoutId()) +// assertEquals(1, count().get()) +// assertEquals(doc1.withoutId(), +// find().first() +// .get()!! +// .withoutId()) +// +// // Test the upsert option where the server should perform upsert and return new document +// val update2 = Document("\$set", doc2) +// assertEquals(doc2, +// findOneAndUpdate(filter, update2, options) +// .get()!! +// .withoutId()) +// assertEquals(2, count().get()) +// +// // Test the upsert option where the server should perform upsert and return old document +// // The old document should be empty +// options = FindOneAndModifyOptions() +// .upsert(true) +// val update = Document("\$set", doc3) +// assertNull(findOneAndUpdate(filter, update, options).get()) +// assertEquals(3, count().get()) +// } +// } +// +// @Test +// fun findOneAndUpdate_withProjectionAndSort() { +// with(getCollectionInternal()) { +// insertMany(listOf( +// Document(mapOf(Pair("team", "Fearful Mallards"), Pair("score", 25000))), +// Document(mapOf(Pair("team", "Tactful Mooses"), Pair("score", 23500))), +// Document(mapOf(Pair("team", "Aquatic Ponies"), Pair("score", 19250))), +// Document(mapOf(Pair("team", "Cuddly Zebras"), Pair("score", 15235))), +// Document(mapOf(Pair("team", "Garrulous Bears"), Pair("score", 18000))) +// )).get() +// +// assertEquals(5, count().get()) +// assertNotNull(findOne(Document("team", "Cuddly Zebras"))) +// +// // Project: team, hide _id; Sort: score ascending +// val project = Document(mapOf(Pair("_id", 0), Pair("team", 1), Pair("score", 1))) +// val sort = Document("score", 1) +// +// // This results in the update of Cuddly Zebras +// val updatedDocument = findOneAndUpdate( +// Document("score", Document("\$lt", 22250)), +// Document("\$inc", Document("score", 1)), +// FindOneAndModifyOptions() +// .projection(project) +// .sort(sort) +// ).get() +// +// assertEquals(5, count().get()) +// assertEquals( +// Document(mapOf(Pair("team", "Cuddly Zebras"), Pair("score", 15235))), +// updatedDocument +// ) +// assertEquals( +// Document(mapOf(Pair("team", "Cuddly Zebras"), Pair("score", 15235 + 1))), +// findOne(Document("team", "Cuddly Zebras")).get().withoutId() +// ) +// } +// } +// +// @Test +// fun findOneAndUpdate_fails() { +// with(getCollectionInternal()) { +// assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { +// findOneAndUpdate(Document(), Document("\$who", 1)).get() +// }.also { e -> +// assertTrue(e.errorMessage!!.contains("modifier", true)) +// } +// +// assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { +// findOneAndUpdate(Document(), Document("\$who", 1), FindOneAndModifyOptions().upsert(true)).get() +// }.also { e -> +// assertTrue(e.errorMessage!!.contains("modifier", true)) +// } +// } +// } + + // FIXME Invalid fields?~? + @Test + fun findOneAndReplace() = runBlocking { + RealmLog.level = LogLevel.ALL + assertNull(collection.findOneAndReplace(BsonDocument(), BsonDocument())) + collection.insertMany( + listOf( + SyncDog("dog1"), + SyncDog("dog1"), + SyncDog("dog2") + ) + ) + val x = collection.findOneAndReplace( + BsonDocument(), + BsonDocument("""{ "name": "dog1" }"""), + upsert = true + ) + println(x) + } +// +// @Test +// fun findOneAndReplace_noUpdates() { +// with(getCollectionInternal()) { +// // Test null behaviour again with a filter that should not match any documents +// assertNull(findOneAndReplace(Document("hello", "zzzzz"), Document()).get()) +// assertEquals(0, count().get()) +// assertNull(findOneAndReplace(Document(), Document()).get()) +// assertEquals(0, count().get()) +// } +// } +// +// @Test +// fun findOneAndReplace_noUpsert() { +// with(getCollectionInternal()) { +// val sampleDoc = Document("hello", "world1").apply { this["num"] = 2 } +// +// // Insert a sample Document +// insertOne(sampleDoc).get() +// assertEquals(1, count().get()) +// +// // Sample call to findOneAndReplace() where we get the previous document back +// var sampleUpdate = Document("hello", "world2").apply { this["num"] = 2 } +// assertEquals(sampleDoc.withoutId(), +// findOneAndReplace(Document("hello", "world1"), sampleUpdate).get()!!.withoutId()) +// assertEquals(1, count().get()) +// +// // Make sure the update took place +// val expectedDoc = Document("hello", "world2").apply { this["num"] = 2 } +// assertEquals(expectedDoc.withoutId(), find().first().get()!!.withoutId()) +// assertEquals(1, count().get()) +// +// // Call findOneAndReplace() again but get the new document +// sampleUpdate = Document("hello", "world3").apply { this["num"] = 3 } +// val options = FindOneAndModifyOptions().returnNewDocument(true) +// assertEquals(sampleUpdate.withoutId(), +// findOneAndReplace(Document(), sampleUpdate, options).get()!!.withoutId()) +// assertEquals(1, count().get()) +// +// // Test null behaviour again with a filter that should not match any documents +// assertNull(findOneAndReplace(Document("hello", "zzzzz"), Document()).get()) +// assertEquals(1, count().get()) +// } +// } +// +// @Test +// fun findOneAndReplace_upsert() { +// with(getCollectionInternal()) { +// val doc4 = Document("hello", "world4").apply { this["num"] = 4 } +// val doc5 = Document("hello", "world5").apply { this["num"] = 5 } +// val doc6 = Document("hello", "world6").apply { this["num"] = 6 } +// +// // Test the upsert option where it should not actually be invoked +// val sampleUpdate = Document("hello", "world4").apply { this["num"] = 4 } +// var options = FindOneAndModifyOptions() +// .returnNewDocument(true) +// .upsert(true) +// assertEquals(doc4.withoutId(), +// findOneAndReplace(Document("hello", "world3"), doc4, options) +// .get()!! +// .withoutId()) +// assertEquals(1, count().get()) +// assertEquals(doc4.withoutId(), find().first().get()!!.withoutId()) +// +// // Test the upsert option where the server should perform upsert and return new document +// options = FindOneAndModifyOptions().returnNewDocument(true).upsert(true) +// assertEquals(doc5.withoutId(), findOneAndReplace(Document("hello", "hellothere"), doc5, options).get()!!.withoutId()) +// assertEquals(2, count().get()) +// +// // Test the upsert option where the server should perform upsert and return old document +// // The old document should be empty +// options = FindOneAndModifyOptions().upsert(true) +// assertNull(findOneAndReplace(Document("hello", "hellothere"), doc6, options).get()) +// assertEquals(3, count().get()) +// } +// } +// +// @Test +// fun findOneAndReplace_withProjectionAndSort() { +// with(getCollectionInternal()) { +// insertMany(listOf( +// Document(mapOf(Pair("team", "Fearful Mallards"), Pair("score", 25000))), +// Document(mapOf(Pair("team", "Tactful Mooses"), Pair("score", 23500))), +// Document(mapOf(Pair("team", "Aquatic Ponies"), Pair("score", 19250))), +// Document(mapOf(Pair("team", "Cuddly Zebras"), Pair("score", 15235))), +// Document(mapOf(Pair("team", "Garrulous Bears"), Pair("score", 18000))) +// )).get() +// +// assertEquals(5, count().get()) +// assertNotNull(findOne(Document("team", "Cuddly Zebras"))) +// +// // Project: team, hide _id; Sort: score ascending +// val project = Document(mapOf(Pair("_id", 0), Pair("team", 1))) +// val sort = Document("score", 1) +// +// // This results in the replacement of Cuddly Zebras +// val replacedDocument = findOneAndReplace( +// Document("score", Document("\$lt", 22250)), +// Document(mapOf(Pair("team", "Therapeutic Hamsters"), Pair("score", 22250))), +// FindOneAndModifyOptions() +// .projection(project) +// .sort(sort) +// ).get() +// +// assertEquals(5, count().get()) +// assertEquals(Document("team", "Cuddly Zebras"), replacedDocument) +// assertNull(findOne(Document("team", "Cuddly Zebras")).get()) +// assertNotNull(findOne(Document("team", "Therapeutic Hamsters")).get()) +// +// // Check returnNewDocument +// val newDocument = findOneAndReplace( +// Document("score", 22250), +// Document(mapOf(Pair("team", "New Therapeutic Hamsters"), Pair("score", 30000))), +// FindOneAndModifyOptions().returnNewDocument(true) +// ).get() +// +// assertEquals(Document(mapOf(Pair("team", "New Therapeutic Hamsters"), Pair("score", 30000))), newDocument.withoutId()) +// } +// } +// +// @Test +// fun findOneAndReplace_fails() { +// with(getCollectionInternal()) { +// assertFailsWithErrorCode(ErrorCode.INVALID_PARAMETER) { +// findOneAndReplace(Document(), Document("\$who", 1)).get() +// } +// +// assertFailsWithErrorCode(ErrorCode.INVALID_PARAMETER) { +// findOneAndReplace(Document(), Document("\$who", 1), FindOneAndModifyOptions().upsert(true)).get() +// } +// } +// } + + @Test + fun findOneAndDelete() = runBlocking { + RealmLog.level = LogLevel.ALL + assertNull(collection.findOneAndDelete(BsonDocument(), BsonDocument())) + collection.insertMany( + listOf( + SyncDog("dog1"), + SyncDog("dog1"), + SyncDog("dog2") + ) + ) + val x: SyncDog = collection.findOneAndDelete( + BsonDocument(), + BsonDocument("""{ "name": "dog1" }"""), + upsert = true + ) + } + // +// @Test +// fun findOneAndDelete() { +// with(getCollectionInternal()) { +// val sampleDoc = Document("hello", "world1").apply { this["num"] = 1 } +// +// // Collection should start out empty +// // This also tests the null return format +// assertNull(findOneAndDelete(Document()).get()) +// +// // Insert a sample Document +// insertOne(sampleDoc).get() +// assertEquals(1, count().get()) +// +// // Sample call to findOneAndDelete() where we delete the only doc in the collection +// assertEquals(sampleDoc.withoutId(), +// findOneAndDelete(Document()).get()!!.withoutId()) +// +// // There should be no documents in the collection now +// assertEquals(0, count().get()) +// +// // Insert a sample Document +// insertOne(sampleDoc).get() +// assertEquals(1, count().get()) +// +// // Call findOneAndDelete() again but this time with a filter +// assertEquals(sampleDoc.withoutId(), +// findOneAndDelete(Document("hello", "world1")).get()!!.withoutId()) +// +// // There should be no documents in the collection now +// assertEquals(0, count().get()) +// +// // Insert a sample Document +// insertOne(sampleDoc).get() +// assertEquals(1, count().get()) +// +// // Test null behaviour again with a filter that should not match any documents +// assertNull(findOneAndDelete(Document("hello", "zzzzz")).get()) +// assertEquals(1, count().get()) +// +// val doc2 = Document("hello", "world2").apply { this["num"] = 2 } +// val doc3 = Document("hello", "world3").apply { this["num"] = 3 } +// +// // Insert new documents +// insertMany(listOf(doc2, doc3)).get() +// assertEquals(3, count().get()) +// } +// } +// +// @Test +// fun findOneAndDelete_withProjectionAndSort() { +// with(getCollectionInternal()) { +// insertMany(listOf( +// Document(mapOf(Pair("team", "Fearful Mallards"), Pair("score", 25000))), +// Document(mapOf(Pair("team", "Tactful Mooses"), Pair("score", 23500))), +// Document(mapOf(Pair("team", "Aquatic Ponies"), Pair("score", 19250))), +// Document(mapOf(Pair("team", "Cuddly Zebras"), Pair("score", 15235))), +// Document(mapOf(Pair("team", "Garrulous Bears"), Pair("score", 18000))) +// )).get() +// +// assertEquals(5, count().get()) +// assertNotNull(findOne(Document("team", "Cuddly Zebras"))) +// +// // Project: team, hide _id; Sort: score ascending +// val project = Document(mapOf(Pair("_id", 0), Pair("team", 1))) +// val sort = Document("score", 1) +// +// // This results in the deletion of Cuddly Zebras +// val deletedDocument = findOneAndDelete( +// Document("score", Document("\$lt", 22250)), +// FindOneAndModifyOptions() +// .projection(project) +// .sort(sort) +// ).get() +// +// assertEquals(4, count().get()) +// assertEquals(Document("team", "Cuddly Zebras"), deletedDocument.withoutId()) +// assertNull(findOne(Document("team", "Cuddly Zebras")).get()) +// } +// } + + +} + +@Serializable +class SyncDog : RealmObject { + constructor() { + this.name = "Default" + } + + constructor(name: String) { + this.name = name + } + + var name: String +} + +@Serializable +data class SyncDogIntId(val name: String, val _id: Int) + +//@OptIn(ExperimentalKBsonSerializerApi::class) +//inline fun EJson.BsonDocument(key: String, value: T): BsonDocument { +// return BsonDocument(key to this.encodeToBsonValue(value)) +//} +@OptIn(ExperimentalKBsonSerializerApi::class) +inline operator fun BsonDocument.Companion.invoke( + key: String, + value: T, +): BsonDocument { + return BsonDocument(key to EJson.Default.encodeToBsonValue(value)) +} + +//fun BsonDocument2(vararg args: Pair): BsonDocument = +// BsonDocument(args.toMap().mapValues { anyToBsonValue(it.value) }) +//private fun anyToBsonValue(value: Any?): BsonValue { +//return when (value) { +// // BsonNull -> TODO() +// null -> BsonNull +// +// // is BsonString -> TODO() +// is String -> BsonString(value) +// +// // is BsonBoolean -> TODO() +// is Boolean -> BsonBoolean(value) +// +// // is BsonNumber -> TODO() +// is Int -> BsonInt32(value) +// is Long -> BsonInt64(value) +// is Double -> BsonDouble(value) +// +// // is BsonObjectId -> TODO() +// is ObjectId -> value +// // is BsonDecimal128 -> TODO() +// is Decimal128 -> value +// +// // is BsonBinary -> TODO() +// is ByteArray -> BsonBinary(value) +// +// // is BsonTimestamp -> TODO() +// is RealmInstant -> { +// if (value.epochSeconds.let { it >= Int.MIN_VALUE && it <= Int.MAX_VALUE }) { +// BsonTimestamp(value.epochSeconds.toInt(), value.nanosecondsOfSecond) +// } else throw IllegalArgumentException("Cannot represent RealmInstant in BsonTimestamp: $$value") +// } +// +// // is BsonArray -> TODO() +// is List<*> -> BsonArray(value.map { anyToBsonValue(it) } ) +// +// // is BsonDocument -> TODO() +// is Map<*, *> -> BsonDocument(value.mapValues { anyToBsonValue(it) } as Map) +// // is BsonDBPointer -> TODO() +// // is BsonDateTime -> TODO() +// // is BsonJavaScript -> TODO() +// // is BsonJavaScriptWithScope -> TODO() +// // BsonMaxKey -> TODO() +// // BsonMinKey -> TODO() +// // is BsonRegularExpression -> TODO() +// // is BsonSymbol -> TODO() +// // BsonUndefined -> TODO() +// else -> TODO("$value ${value::class}") +// } +// +//} diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/utils/Utils.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/utils/Utils.kt index 526af13e85..d8a4e05b8a 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/utils/Utils.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/utils/Utils.kt @@ -15,6 +15,12 @@ */ package io.realm.kotlin.test.mongodb.common.utils +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.async +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext +import kotlin.coroutines.CoroutineContext import kotlin.reflect.KClass import kotlin.test.assertFailsWith @@ -47,3 +53,10 @@ fun assertFailsWithMessage(exceptionClass: KClass, exceptionM inline fun assertFailsWithMessage(exceptionMessage: String, noinline block: () -> Unit): T = assertFailsWithMessage(T::class, exceptionMessage, block) + +inline fun CoroutineScope.assertFailsWithMessage(exceptionMessage: String, noinline block: suspend CoroutineScope.() -> Unit): T = + assertFailsWithMessage(T::class, exceptionMessage) { + runBlocking(this.coroutineContext) { + block() + } + } From 8e37043a65264c648119247c89cbd18db0be9aa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 5 Dec 2023 14:23:41 +0100 Subject: [PATCH 02/36] API experiments --- .../mongodb/internal/MongoCollectionImpl.kt | 2 +- .../mongodb/internal/MongoDatabaseImpl.kt | 11 ++ .../kotlin/mongodb/mongo/MongoCollection.kt | 130 +++++++++++------- .../kotlin/mongodb/mongo/MongoDatabase.kt | 6 + .../mongodb/common/mongo/MongoClientTest.kt | 47 ++++++- 5 files changed, 145 insertions(+), 51 deletions(-) diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt index 1c196dbf4c..172f083d76 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt @@ -26,7 +26,7 @@ import org.mongodb.kbson.BsonString import org.mongodb.kbson.BsonValue @PublishedApi -internal class MongoCollectionImpl( +internal open class MongoCollectionImpl( @PublishedApi internal val database: MongoDatabaseImpl, override val name: String, diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDatabaseImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDatabaseImpl.kt index fd124bb2fa..69c79a68d3 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDatabaseImpl.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDatabaseImpl.kt @@ -18,6 +18,9 @@ package io.realm.kotlin.mongodb.internal import io.realm.kotlin.mongodb.mongo.MongoCollection import io.realm.kotlin.mongodb.mongo.MongoDatabase +import io.realm.kotlin.mongodb.mongo.TypedMongoCollection +import io.realm.kotlin.mongodb.mongo.TypedMongoCollectionImpl +import org.mongodb.kbson.BsonValue @PublishedApi internal class MongoDatabaseImpl( @@ -27,4 +30,12 @@ internal class MongoDatabaseImpl( ) : MongoDatabase { override fun collection(collectionName: String): MongoCollection = MongoCollectionImpl(this, collectionName) + + override fun typedCollectionbson(collectionName: String): TypedMongoCollection { + return TypedMongoCollectionImpl(collection(collectionName) as MongoCollectionImpl) + } + + override fun typedCollection(collectionName: String): TypedMongoCollection { + return TypedMongoCollectionImpl(collection(collectionName) as MongoCollectionImpl) + } } diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt index 454a4b4c7c..195060396a 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt @@ -14,6 +14,8 @@ * limitations under the License. */ +// TODO - QUESTIONS +// - should we allow serialization of update, sort and projection arguments? package io.realm.kotlin.mongodb.mongo import io.realm.kotlin.annotations.ExperimentalRealmSerializerApi @@ -21,6 +23,7 @@ import io.realm.kotlin.mongodb.internal.MongoCollectionImpl import io.realm.kotlin.mongodb.internal.call import io.realm.kotlin.mongodb.internal.serializerOrRealmBuiltInSerializer import kotlinx.serialization.KSerializer +import org.mongodb.kbson.BsonArray import org.mongodb.kbson.BsonBoolean import org.mongodb.kbson.BsonDocument import org.mongodb.kbson.BsonInt64 @@ -28,6 +31,7 @@ import org.mongodb.kbson.BsonValue import org.mongodb.kbson.ExperimentalKBsonSerializerApi import org.mongodb.kbson.serialization.EJson import org.mongodb.kbson.serialization.decodeFromBsonValue +import kotlin.jvm.JvmName public interface MongoCollection { @@ -37,23 +41,34 @@ public interface MongoCollection { // public suspend fun findOne(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null): BsonValue { // TODO() // } + @ExperimentalRealmSerializerApi + public fun typedCollection(): TypedMongoCollection = TypedMongoCollectionImpl(this as MongoCollectionImpl) } -public interface TypedMongoCollection { - -} +public interface TypedMongoCollection: MongoCollection { } +@PublishedApi +internal class TypedMongoCollectionImpl(collectionImpl: MongoCollectionImpl): TypedMongoCollection, MongoCollectionImpl(collectionImpl.database, collectionImpl.name) @OptIn(ExperimentalKBsonSerializerApi::class) @ExperimentalRealmSerializerApi -public suspend inline fun MongoCollection.findOne(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null): R { - return EJson.decodeFromBsonValue((this as MongoCollectionImpl).call("findOne") { +public suspend inline fun MongoCollection.findOne(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null, serializer: KSerializer? = null): R { + val value = (this as MongoCollectionImpl).call("findOne") { filter?.let { put("query", it) } projection?.let { put("projection", it) } sort?.let { put("sort", it) } - }) + } + return decodeFromBsonValue(value, serializer) } +@OptIn(ExperimentalKBsonSerializerApi::class) +@PublishedApi +internal inline fun MongoCollection.decodeFromBsonValue(bsonValue: BsonValue, serializer: KSerializer? = null): R { + val serializer = serializer ?: (this as MongoCollectionImpl).functions.app.configuration.ejson.serializersModule.serializerOrRealmBuiltInSerializer() + return (this as MongoCollectionImpl).functions.app.configuration.ejson.decodeFromBsonValue(serializer, bsonValue) +} + + public class ClientOption { } @@ -95,9 +110,6 @@ internal class FindOptionsInternal: FindOneOptions, FilterOptionInternal, LimitO override var limit: Long? = null } -//public suspend inline fun count(filter: Bson = {}, limit: Int = 0 ): Long -// query -// limit @OptIn(ExperimentalKBsonSerializerApi::class) @ExperimentalRealmSerializerApi public suspend inline fun MongoCollection.count(filter: BsonDocument, limit: Long): Long { @@ -127,6 +139,18 @@ public suspend inline fun MongoCollection.findOne(configuration: Fin return EJson.decodeFromBsonValue(response) } +@OptIn(ExperimentalKBsonSerializerApi::class) +@ExperimentalRealmSerializerApi +public suspend inline fun MongoCollection.find(filter: BsonDocument? = null, limit: Long? = null, projection: BsonDocument? = null, sort: BsonDocument? = null): List { + val objects = (this as MongoCollectionImpl).call("find") { + filter?.let { put("query", it) } + projection?.let { put("projection", it) } + sort?.let { put("sort", it) } + limit?.let { put("limit", BsonInt64(limit)) } + }.asArray().toList() + return if (T::class == BsonValue::class) { objects as List } else {objects.map {EJson.decodeFromBsonValue(it) } } +} + //@ExperimentalRealmSerializerApi //@OptIn(ExperimentalKBsonSerializerApi::class) //public suspend inline fun MongoCollection.insertOne(document: T): R { @@ -149,8 +173,15 @@ public suspend inline fun MongoCollection.findOne(configuration: Fin // }.asDocument().get("insertedId")!!.asObjectId() //} -// insertOne(doc:): Bson(insertedId) -// document +@ExperimentalRealmSerializerApi +@OptIn(ExperimentalKBsonSerializerApi::class) +public suspend inline fun MongoCollection.aggregate(pipeline: List): List { + val insertedId: List = (this as MongoCollectionImpl).call("aggregate") { + put("pipeline", BsonArray(pipeline)) + }.asArray().toList() + return if (T::class == BsonValue::class) { insertedId as List } else { insertedId.map { EJson.decodeFromBsonValue(it) } } +} + @ExperimentalRealmSerializerApi @OptIn(ExperimentalKBsonSerializerApi::class) public suspend inline fun MongoCollection.insertOne(document: T): R { @@ -167,6 +198,30 @@ public suspend inline fun MongoCollection.insertOne // Annoying that you cannot to this without assignment/return type // collection.insertMany(listOf(SyncDog())) +@OptIn(ExperimentalKBsonSerializerApi::class) +@JvmName("asdf") +@ExperimentalRealmSerializerApi +public suspend inline fun TypedMongoCollection.insertMany( + documents: Collection, +): List { + val encodedDocument: BsonValue = EJson.encodeToBsonValue( + EJson.serializersModule.serializerOrRealmBuiltInSerializer(), + documents + ) + val insertedId: List = (this as MongoCollectionImpl).call("insertMany") { + put("documents", encodedDocument) + }.asDocument().get("insertedIds")!!.asArray().toList() + return if (R::class == BsonValue::class) { + insertedId as List + } else { + insertedId.map { decodeFromBsonValue(it) } + } +} + +@ExperimentalRealmSerializerApi +public suspend inline fun TypedMongoCollection<*, *>.insertMany(documents: Collection): List = + (this as TypedMongoCollectionImpl).insertMany(documents) + @ExperimentalRealmSerializerApi @OptIn(ExperimentalKBsonSerializerApi::class) public suspend inline fun MongoCollection.insertMany(documents: Collection): List { @@ -248,7 +303,6 @@ public suspend inline fun MongoCollection.updateMany( return if (R::class == BsonValue::class) { insertedId as R } else { EJson.decodeFromBsonValue(insertedId) } } -// findOneAndUpdate(filter, update, options(projections, sort, upsert: Boolean, returnNewDoc)): T @ExperimentalRealmSerializerApi @OptIn(ExperimentalKBsonSerializerApi::class) public suspend inline fun MongoCollection.findOneAndUpdate( @@ -259,29 +313,21 @@ public suspend inline fun MongoCollection.findOneAndUpdate( upsert: Boolean = false, returnNewDoc: Boolean = false, ): R { - val insertedId: BsonValue = (this as MongoCollectionImpl).call("findOneAndUpdate") { + val updatedDocument: BsonValue = (this as MongoCollectionImpl).call("findOneAndUpdate") { put("filter", filter) put("update", update) projection?.let { put("projection", projection)} sort?.let { put("sort", sort)} put("upsert", BsonBoolean(upsert)) put("returnNewDoc", BsonBoolean(returnNewDoc)) - }//.get("insertedIds")!!.asArray().toList() - // {"matchedCount":{"$numberInt":"0"},"modifiedCount":{"$numberInt":"0"}} - + } return if (R::class == BsonValue::class) { - insertedId as R + updatedDocument as R } else { - EJson.decodeFromBsonValue(insertedId) + EJson.decodeFromBsonValue(updatedDocument) } } -// findOneAndReplace(filter, update, options(projections, sort, upsert: Boolean, returnNewDoc)): T -// filter -// update : BsonDocu -// upsert: Boolean -// returnNewDoc: Boolean -// projection -// sort + @ExperimentalRealmSerializerApi @OptIn(ExperimentalKBsonSerializerApi::class) public suspend inline fun MongoCollection.findOneAndReplace( @@ -292,52 +338,38 @@ public suspend inline fun MongoCollection.findOneAndReplace( upsert: Boolean = false, returnNewDoc: Boolean = false, ): R { - val insertedId: BsonValue = (this as MongoCollectionImpl).call("findOneAndReplace") { + // If returnNewDoc==true then the returned document is after the update otherwise it is from + // before the update + val updatedDocument: BsonValue = (this as MongoCollectionImpl).call("findOneAndReplace") { put("filter", filter) put("update", update) projection?.let { put("projection", projection)} sort?.let { put("sort", sort)} put("upsert", BsonBoolean(upsert)) put("returnNewDoc", BsonBoolean(returnNewDoc)) - }//.get("insertedIds")!!.asArray().toList() - // {"matchedCount":{"$numberInt":"0"},"modifiedCount":{"$numberInt":"0"}} - + } return if (R::class == BsonValue::class) { - insertedId as R + updatedDocument as R } else { - EJson.decodeFromBsonValue(insertedId) + EJson.decodeFromBsonValue(updatedDocument) } } -// findOneAndDelete(filter, options(projections, sort, upsert: Boolean, returnNewDoc)): T -// filter -// upsert: Boolean -// returnNewDoc: Boolean -// projection -// sort @ExperimentalRealmSerializerApi @OptIn(ExperimentalKBsonSerializerApi::class) public suspend inline fun MongoCollection.findOneAndDelete( filter: BsonDocument, - update: BsonDocument, projection: BsonDocument? = null, sort: BsonDocument? = null, - upsert: Boolean = false, ): R { - val insertedId: BsonValue = (this as MongoCollectionImpl).call("findOneAndDelete") { + val deletedDocument: BsonValue = (this as MongoCollectionImpl).call("findOneAndDelete") { put("filter", filter) - put("update", update) projection?.let { put("projection", projection)} sort?.let { put("sort", sort)} - put("upsert", BsonBoolean(upsert)) - }//.get("insertedIds")!!.asArray().toList() - // {"matchedCount":{"$numberInt":"0"},"modifiedCount":{"$numberInt":"0"}} - + } return if (R::class == BsonValue::class) { - insertedId as R + deletedDocument as R } else { - EJson.decodeFromBsonValue(insertedId) + EJson.decodeFromBsonValue(deletedDocument) } } - -// watch?? diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoDatabase.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoDatabase.kt index 4534000d2b..4fbfbadc09 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoDatabase.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoDatabase.kt @@ -16,6 +16,9 @@ package io.realm.kotlin.mongodb.mongo +import org.mongodb.kbson.BsonValue +import kotlin.jvm.JvmName + /** * A handle to a remote **Atlas App Service database** that provides access to its [MongoCollection]s. */ @@ -27,5 +30,8 @@ public interface MongoDatabase { public val name: String public fun collection(collectionName: String): MongoCollection + public fun typedCollectionbson(collectionName: String): TypedMongoCollection + public fun typedCollection(collectionName: String): TypedMongoCollection } + diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTest.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTest.kt index 5eb16d866c..1ccd687acd 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTest.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTest.kt @@ -27,8 +27,11 @@ import io.realm.kotlin.mongodb.exceptions.ServiceException import io.realm.kotlin.mongodb.mongo.MongoClient import io.realm.kotlin.mongodb.mongo.MongoCollection import io.realm.kotlin.mongodb.mongo.MongoDatabase +import io.realm.kotlin.mongodb.mongo.TypedMongoCollection +import io.realm.kotlin.mongodb.mongo.aggregate import io.realm.kotlin.mongodb.mongo.deleteMany import io.realm.kotlin.mongodb.mongo.deleteOne +import io.realm.kotlin.mongodb.mongo.find import io.realm.kotlin.mongodb.mongo.findOne import io.realm.kotlin.mongodb.mongo.findOneAndDelete import io.realm.kotlin.mongodb.mongo.findOneAndReplace @@ -162,6 +165,11 @@ class MongoClientTest { // Projection select field // Limit // Sort + val y: BsonDocument = collection.findOne(filter = BsonDocument("name", "dog-0")) + + val y2: BsonValue = collection.findOne(filter = BsonDocument("name", "dog-0")) + println(y) + println(y2) } @OptIn(ExperimentalKBsonSerializerApi::class) @@ -310,6 +318,16 @@ class MongoClientTest { // } // } // } + + @Test + fun find() = runBlocking { + RealmLog.level = LogLevel.ALL + assertTrue { collection.find().isEmpty() } + + val x: List = collection.insertMany(listOf(SyncDog("dog1"), SyncDog("dog2"))) + assertEquals(2, collection.find().size) + } + // // @Test // fun find() { @@ -374,6 +392,18 @@ class MongoClientTest { // } // } // + + @Test + fun aggregate() = runBlocking { + RealmLog.level = LogLevel.ALL + collection.aggregate(listOf()) + + val x: List = collection.insertMany(listOf(SyncDog(name = "dog1"), SyncDog(name = "dog2"))) + collection.aggregate(listOf()) + + + collection.aggregate(listOf(BsonDocument("\$sort", BsonDocument("name", -1)), BsonDocument("\$limit", 1))) + } // @Test // fun aggregate() { // with(getCollectionInternal()) { @@ -453,6 +483,9 @@ class MongoClientTest { // Option 3 - Automatically serialized object val x2: ObjectId = collection.insertOne(SyncDog("sadf")) println(x2) + + val x3: ObjectId = collection.insertOne(BsonDocument("""{ "name" : "asdf" }""")) + println(x2) } // @@ -507,7 +540,20 @@ class MongoClientTest { println(x) println(y) + val typedCollection = collection.typedCollection() + val z: List = typedCollection.insertMany(listOf(SyncDog("sadf"))) + val tyz = typedCollection.insertMany(listOf(SyncDog("sadf"))) + + val bsonSyncDogs /*: TypedMongoCollection */ = database.typedCollectionbson("SyncDog") + val insertMany /*: List */ = bsonSyncDogs.insertMany(listOf(BsonDocument("name", "x"))) + + val syncDogs: TypedMongoCollection = database.typedCollection("SyncDog") + + val objectIds = syncDogs.insertMany(listOf(SyncDog("name"))) + + val objectIds2: List = syncDogs.insertMany(listOf(BsonDocument("name", "asdf"))) } + // // @Test // fun insertMany_singleDocument() { @@ -1261,7 +1307,6 @@ class MongoClientTest { val x: SyncDog = collection.findOneAndDelete( BsonDocument(), BsonDocument("""{ "name": "dog1" }"""), - upsert = true ) } // From 77c5a68c206dfb29df0e703e4d7436cb4c875ba2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 5 Dec 2023 19:19:59 +0100 Subject: [PATCH 03/36] More API experiments --- .../kotlin/io/realm/kotlin/mongodb/User.kt | 6 +- .../mongodb/internal/MongoClientImpl.kt | 13 +- .../mongodb/internal/MongoCollectionImpl.kt | 83 +- .../mongodb/internal/MongoDatabaseImpl.kt | 19 +- .../realm/kotlin/mongodb/internal/UserImpl.kt | 5 +- .../realm/kotlin/mongodb/mongo/MongoClient.kt | 6 +- .../kotlin/mongodb/mongo/MongoCollection.kt | 549 +++---- .../kotlin/mongodb/mongo/MongoDatabase.kt | 10 +- .../mongodb/common/mongo/MongoClientTest.kt | 1456 +++++++++-------- 9 files changed, 1078 insertions(+), 1069 deletions(-) diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/User.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/User.kt index c276cdfb00..64accd35ff 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/User.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/User.kt @@ -22,6 +22,8 @@ import io.realm.kotlin.mongodb.ext.customDataAsBsonDocument import io.realm.kotlin.mongodb.ext.profileAsBsonDocument import io.realm.kotlin.mongodb.mongo.MongoClient import io.realm.kotlin.mongodb.sync.SyncConfiguration +import org.mongodb.kbson.ExperimentalKBsonSerializerApi +import org.mongodb.kbson.serialization.EJson /** * A **user** holds the user's metadata and tokens for accessing App Services and Device Sync @@ -203,8 +205,8 @@ public interface User { public suspend fun linkCredentials(credentials: Credentials): User // TODO Doc - // TODO serializer argument - public fun mongoClient(serviceName: String): MongoClient + @OptIn(ExperimentalKBsonSerializerApi::class) + public fun mongoClient(serviceName: String, eJson: EJson? = null): MongoClient /** * Two Users are considered equal if they have the same user identity and are associated diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoClientImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoClientImpl.kt index 3ba5277d7b..77ff03f9d6 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoClientImpl.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoClientImpl.kt @@ -18,11 +18,18 @@ package io.realm.kotlin.mongodb.internal import io.realm.kotlin.mongodb.mongo.MongoClient import io.realm.kotlin.mongodb.mongo.MongoDatabase +import org.mongodb.kbson.ExperimentalKBsonSerializerApi +import org.mongodb.kbson.serialization.EJson +@OptIn(ExperimentalKBsonSerializerApi::class) @PublishedApi internal class MongoClientImpl( - @PublishedApi - internal val user: UserImpl, override val serviceName: String) : MongoClient { - override fun database(databaseName: String): MongoDatabase = MongoDatabaseImpl(this, databaseName) + internal val user: UserImpl, + override val serviceName: String, + val eJson: EJson, +) : MongoClient { + + override fun database(databaseName: String, eJson: EJson?): MongoDatabase = + MongoDatabaseImpl(this, databaseName, eJson ?: this.eJson) } diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt index 172f083d76..e8e541fcc9 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt @@ -17,49 +17,94 @@ package io.realm.kotlin.mongodb.internal import io.realm.kotlin.annotations.ExperimentalRealmSerializerApi -import io.realm.kotlin.mongodb.ext.CallBuilder import io.realm.kotlin.mongodb.ext.call import io.realm.kotlin.mongodb.mongo.MongoCollection +import io.realm.kotlin.mongodb.mongo.insertMany +import org.mongodb.kbson.BsonArray import org.mongodb.kbson.BsonDocument import org.mongodb.kbson.BsonInt64 import org.mongodb.kbson.BsonString import org.mongodb.kbson.BsonValue +import org.mongodb.kbson.ExperimentalKBsonSerializerApi +import org.mongodb.kbson.serialization.EJson +import org.mongodb.kbson.serialization.decodeFromBsonValue +import org.mongodb.kbson.serialization.encodeToBsonValue +@OptIn(ExperimentalKBsonSerializerApi::class, ExperimentalRealmSerializerApi::class) @PublishedApi -internal open class MongoCollectionImpl( - +internal class MongoCollectionImpl @OptIn(ExperimentalKBsonSerializerApi::class) constructor( @PublishedApi internal val database: MongoDatabaseImpl, override val name: String, -) : MongoCollection { + val eJson: EJson, +): MongoCollection { val client = this.database.client val user = client.user val functions = user.functions + val defaults: Map = mapOf( "database" to BsonString(database.name), "collection" to BsonString(name), ) - override suspend fun count(filter: BsonDocument?, limit: Long?): Int { - @OptIn(ExperimentalRealmSerializerApi::class) - return user.functions.call("count") { + @ExperimentalRealmSerializerApi + private suspend inline fun call(name: String, crossinline document: MutableMap.()-> Unit): R { + return user.functions.call(name) { serviceName(client.serviceName) - val args = defaults.toMutableMap() - limit?.let { args.put("limit", BsonInt64(limit)) } - filter?.let { args.put("query", it) } - add(BsonDocument(args)) + val doc = defaults.toMutableMap() + document(doc) + add(doc) + } + } + + @PublishedApi + internal suspend fun count(filter: BsonDocument? = null, limit: Long? = null): Long { + return call("count") { + filter?.let { put("query", it) } + limit?.let { put("limit", BsonInt64(it)) } } } + + @PublishedApi + internal suspend fun findOne(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null): BsonValue = + call("findOne") { + filter?.let { put("query", it) } + projection?.let { put("projection", it) } + sort?.let { put("sort", it) } + } + + @PublishedApi + internal suspend fun find(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null, limit: Long? = null): BsonValue = + call("find") { + filter?.let { put("query", it) } + projection?.let { put("projection", it) } + sort?.let { put("sort", it) } + limit?.let { put("limit", BsonInt64(it)) } + } + + @PublishedApi + internal suspend fun aggregate(pipeline: List): List = + call("aggregate") { put("pipeline", BsonArray(pipeline)) }.asArray().toList() + + @PublishedApi + internal suspend fun insertOne(document: BsonDocument): BsonValue = + call("insertOne") { put("document", document) }.asDocument()["insertedId"]!! + + @PublishedApi + internal suspend fun insertMany(documents: List): List = + call("insertMany") { + put("documents", BsonArray(documents)) + }.asDocument().get("insertedIds")!!.asArray().toList() } -@ExperimentalRealmSerializerApi +@OptIn(ExperimentalKBsonSerializerApi::class) @PublishedApi -internal suspend inline fun MongoCollectionImpl.call(name: String, crossinline document: MutableMap.()-> Unit): BsonValue { - return user.functions.call(name) { - serviceName(client.serviceName) - val doc = this@call.defaults.toMutableMap() - document(doc) - add(doc) - } +internal inline fun MongoCollectionImpl<*, *>.decodeFromBsonValue(bsonValue: BsonValue): R { + return eJson.decodeFromBsonValue(bsonValue) +} +@OptIn(ExperimentalKBsonSerializerApi::class) +@PublishedApi +internal inline fun MongoCollectionImpl<*, *>.encodeToBsonValue(value: R): BsonValue { + return eJson.encodeToBsonValue(value) } diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDatabaseImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDatabaseImpl.kt index 69c79a68d3..9c71dfeb82 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDatabaseImpl.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDatabaseImpl.kt @@ -18,24 +18,23 @@ package io.realm.kotlin.mongodb.internal import io.realm.kotlin.mongodb.mongo.MongoCollection import io.realm.kotlin.mongodb.mongo.MongoDatabase -import io.realm.kotlin.mongodb.mongo.TypedMongoCollection -import io.realm.kotlin.mongodb.mongo.TypedMongoCollectionImpl import org.mongodb.kbson.BsonValue +import org.mongodb.kbson.ExperimentalKBsonSerializerApi +import org.mongodb.kbson.serialization.EJson @PublishedApi -internal class MongoDatabaseImpl( +@OptIn(ExperimentalKBsonSerializerApi::class) +internal class MongoDatabaseImpl constructor( @PublishedApi internal val client: MongoClientImpl, override val name: String, + val eJson: EJson, ) : MongoDatabase { - override fun collection(collectionName: String): MongoCollection = - MongoCollectionImpl(this, collectionName) - override fun typedCollectionbson(collectionName: String): TypedMongoCollection { - return TypedMongoCollectionImpl(collection(collectionName) as MongoCollectionImpl) + override fun collection(collectionName: String): MongoCollection { + return MongoCollectionImpl(this, collectionName, this.eJson) } - override fun typedCollection(collectionName: String): TypedMongoCollection { - return TypedMongoCollectionImpl(collection(collectionName) as MongoCollectionImpl) - } + override fun collection(collectionName: String, eJson: EJson?): MongoCollection = + MongoCollectionImpl(this, collectionName, eJson ?: this.eJson) } diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/UserImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/UserImpl.kt index 0cfb36b840..d429dd78d8 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/UserImpl.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/UserImpl.kt @@ -32,6 +32,8 @@ import io.realm.kotlin.mongodb.mongo.MongoClient import io.realm.kotlin.mongodb.mongo.MongoCollection import io.realm.kotlin.mongodb.mongo.MongoDatabase import kotlinx.coroutines.channels.Channel +import org.mongodb.kbson.ExperimentalKBsonSerializerApi +import org.mongodb.kbson.serialization.EJson // TODO Public due to being a transitive dependency to SyncConfigurationImpl public class UserImpl( @@ -185,7 +187,8 @@ public class UserImpl( } } - override fun mongoClient(serviceName: String): MongoClient = MongoClientImpl(this, serviceName) + @OptIn(ExperimentalKBsonSerializerApi::class) + override fun mongoClient(serviceName: String, eJson: EJson?): MongoClient = MongoClientImpl(this, serviceName, eJson ?: app.configuration.ejson) override fun equals(other: Any?): Boolean { if (this === other) return true diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt index 93cd022da0..b890b49551 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt @@ -16,6 +16,9 @@ package io.realm.kotlin.mongodb.mongo +import org.mongodb.kbson.ExperimentalKBsonSerializerApi +import org.mongodb.kbson.serialization.EJson + /** * The remote MongoClient used for working with data in MongoDB remotely via Realm. */ @@ -23,5 +26,6 @@ public interface MongoClient { public val serviceName: String - public fun database(databaseName: String): MongoDatabase + @OptIn(ExperimentalKBsonSerializerApi::class) + public fun database(databaseName: String, eJson: EJson? = null): MongoDatabase } diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt index 195060396a..6109d5d5be 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt @@ -16,360 +16,299 @@ // TODO - QUESTIONS // - should we allow serialization of update, sort and projection arguments? + package io.realm.kotlin.mongodb.mongo -import io.realm.kotlin.annotations.ExperimentalRealmSerializerApi +import io.realm.kotlin.internal.util.Validation import io.realm.kotlin.mongodb.internal.MongoCollectionImpl -import io.realm.kotlin.mongodb.internal.call -import io.realm.kotlin.mongodb.internal.serializerOrRealmBuiltInSerializer -import kotlinx.serialization.KSerializer -import org.mongodb.kbson.BsonArray -import org.mongodb.kbson.BsonBoolean +import io.realm.kotlin.mongodb.internal.decodeFromBsonValue import org.mongodb.kbson.BsonDocument -import org.mongodb.kbson.BsonInt64 import org.mongodb.kbson.BsonValue import org.mongodb.kbson.ExperimentalKBsonSerializerApi import org.mongodb.kbson.serialization.EJson import org.mongodb.kbson.serialization.decodeFromBsonValue +import org.mongodb.kbson.serialization.encodeToBsonValue import kotlin.jvm.JvmName -public interface MongoCollection { +public interface MongoCollection { public val name: String - public suspend fun count(filter: BsonDocument? = null, limit: Long? = null): Int -// public suspend fun findOne(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null): BsonValue { -// TODO() -// } - @ExperimentalRealmSerializerApi - public fun typedCollection(): TypedMongoCollection = TypedMongoCollectionImpl(this as MongoCollectionImpl) -} - -public interface TypedMongoCollection: MongoCollection { } - -@PublishedApi -internal class TypedMongoCollectionImpl(collectionImpl: MongoCollectionImpl): TypedMongoCollection, MongoCollectionImpl(collectionImpl.database, collectionImpl.name) - -@OptIn(ExperimentalKBsonSerializerApi::class) -@ExperimentalRealmSerializerApi -public suspend inline fun MongoCollection.findOne(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null, serializer: KSerializer? = null): R { - val value = (this as MongoCollectionImpl).call("findOne") { - filter?.let { put("query", it) } - projection?.let { put("projection", it) } - sort?.let { put("sort", it) } + @OptIn(ExperimentalKBsonSerializerApi::class) + public fun typedCollection(eJson: EJson? = null): MongoCollection { + val source = this as MongoCollectionImpl<*, *> + return MongoCollectionImpl(source.database, source.name, eJson ?: source.eJson) } - return decodeFromBsonValue(value, serializer) } -@OptIn(ExperimentalKBsonSerializerApi::class) -@PublishedApi -internal inline fun MongoCollection.decodeFromBsonValue(bsonValue: BsonValue, serializer: KSerializer? = null): R { - val serializer = serializer ?: (this as MongoCollectionImpl).functions.app.configuration.ejson.serializersModule.serializerOrRealmBuiltInSerializer() - return (this as MongoCollectionImpl).functions.app.configuration.ejson.decodeFromBsonValue(serializer, bsonValue) +public suspend inline fun MongoCollection<*, *>.count(filter: BsonDocument? = null, limit: Long? = null): Long { + return (this as MongoCollectionImpl).count(filter, limit) } - -public class ClientOption { - +public suspend inline fun MongoCollection.findOne(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null): T { + return (this as MongoCollectionImpl).decodeFromBsonValue(findOne(filter, projection, sort)) } -@PublishedApi -internal interface FilterOptionInternal { - var filter: BsonDocument? -} -@PublishedApi -internal interface LimitOptionInternal { - var limit: Long? +@JvmName("findOneTyped") +public suspend inline fun MongoCollection<*, *>.findOne(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null): T { + return (this as MongoCollection).findOne(filter, projection, sort) } -public interface LimitOption { } -public fun LimitOption.limit(limit: Long) { - (this as LimitOptionInternal).limit = limit -} -public interface FilterOption { } -public fun FilterOption.filter(json: String) { - (this as FilterOptionInternal).filter = BsonDocument(json) -} -@ExperimentalKBsonSerializerApi -public inline fun FilterOption.filter(argument: T) { - filter(argument, EJson.serializersModule.serializerOrRealmBuiltInSerializer()) -} @OptIn(ExperimentalKBsonSerializerApi::class) -public inline fun FilterOption.filter(argument: T, serializer: KSerializer) { - (this as FilterOptionInternal).filter = EJson.encodeToBsonValue(serializer, argument).asDocument() -} -public interface ProjectionOption -public interface SortOption - -public interface CountOptions : LimitOption, FilterOption -public interface FindOneOptions : LimitOption, FilterOption, SortOption, ProjectionOption - -@PublishedApi -internal class FindOptionsInternal: FindOneOptions, FilterOptionInternal, LimitOptionInternal { - override var filter: BsonDocument? = null - override var limit: Long? = null -} - -@OptIn(ExperimentalKBsonSerializerApi::class) -@ExperimentalRealmSerializerApi -public suspend inline fun MongoCollection.count(filter: BsonDocument, limit: Long): Long { - return EJson.decodeFromBsonValue((this as MongoCollectionImpl).call("findOne") { - put("query", filter) - put("limit", BsonInt64(limit)) - }) +public suspend inline fun MongoCollection.find(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null, limit: Long? = null): List { + Validation.isType>(this, "") + val objects = find(filter, projection, sort, limit).asArray().toList() + return if (T::class == BsonValue::class) { objects as List } else {objects.map {EJson.decodeFromBsonValue(it) } } } - -//public suspend inline fun findOne(filter: Bson = {}, options: FindOptions(limit, projection, sort)): T -// query -// findoptions -// limit -// projection -// sort - -@OptIn(ExperimentalKBsonSerializerApi::class) -@ExperimentalRealmSerializerApi -public suspend inline fun MongoCollection.findOne(configuration: FindOneOptions.() -> Unit): T { - val options = FindOptionsInternal() - configuration(options) - val response = (this as MongoCollectionImpl).call("findOne") { - options.filter?.let { put("query", it) } -// options.projection?.let { put("projection", it) } -// options.sort?.let { put("sort", it) } - } - return EJson.decodeFromBsonValue(response) +@JvmName("findTyped") +public suspend inline fun MongoCollection<*, *>.find(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null, limit: Long? = null ): List { + return (this as MongoCollection).find(filter, projection, sort, limit) } @OptIn(ExperimentalKBsonSerializerApi::class) -@ExperimentalRealmSerializerApi -public suspend inline fun MongoCollection.find(filter: BsonDocument? = null, limit: Long? = null, projection: BsonDocument? = null, sort: BsonDocument? = null): List { - val objects = (this as MongoCollectionImpl).call("find") { - filter?.let { put("query", it) } - projection?.let { put("projection", it) } - sort?.let { put("sort", it) } - limit?.let { put("limit", BsonInt64(limit)) } - }.asArray().toList() - return if (T::class == BsonValue::class) { objects as List } else {objects.map {EJson.decodeFromBsonValue(it) } } +public suspend inline fun MongoCollection.aggregate(pipeline: List): List { + Validation.isType>(this, "") + val objects = aggregate(pipeline) + return if (T::class == BsonValue::class) { objects as List } else { objects.map { EJson.decodeFromBsonValue(it) } } } -//@ExperimentalRealmSerializerApi -//@OptIn(ExperimentalKBsonSerializerApi::class) -//public suspend inline fun MongoCollection.insertOne(document: T): R { -// val value: KSerializer = EJson.serializersModule.serializerOrRealmBuiltInSerializer() -// val x: BsonValue = EJson.encodeToBsonValue(value, document) -// return (this as MongoCollectionImpl).call("insertOne") { -// put("document", x) -// } -//} - -//@ExperimentalRealmSerializerApi -//@OptIn(ExperimentalKBsonSerializerApi::class) -//public suspend inline fun MongoCollection.insertOneReturnId(document: T): ObjectId { -// val encodedDocument: BsonValue = EJson.encodeToBsonValue( -// EJson.serializersModule.serializerOrRealmBuiltInSerializer(), -// document -// ) -// return (this as MongoCollectionImpl).call("insertOne") { -// put("document", encodedDocument) -// }.asDocument().get("insertedId")!!.asObjectId() -//} - -@ExperimentalRealmSerializerApi -@OptIn(ExperimentalKBsonSerializerApi::class) -public suspend inline fun MongoCollection.aggregate(pipeline: List): List { - val insertedId: List = (this as MongoCollectionImpl).call("aggregate") { - put("pipeline", BsonArray(pipeline)) - }.asArray().toList() - return if (T::class == BsonValue::class) { insertedId as List } else { insertedId.map { EJson.decodeFromBsonValue(it) } } +@JvmName("aggregateTyped") +public suspend inline fun MongoCollection<*, *>.aggregate(pipeline: List): List { + return (this as MongoCollection).aggregate(pipeline) } -@ExperimentalRealmSerializerApi @OptIn(ExperimentalKBsonSerializerApi::class) -public suspend inline fun MongoCollection.insertOne(document: T): R { - val encodedDocument: BsonValue = EJson.encodeToBsonValue( EJson.serializersModule.serializerOrRealmBuiltInSerializer(), document ) - val insertedId: BsonValue = (this as MongoCollectionImpl).call("insertOne") { - put("document", encodedDocument) - }.asDocument().get("insertedId")!! +public suspend inline fun MongoCollection.insertOne(document: T): R { + Validation.isType>(this, "") + val encodedDocument: BsonDocument = eJson.encodeToBsonValue(document).asDocument() + val insertedId = insertOne(encodedDocument) return if (insertedId is R) { insertedId } else { EJson.decodeFromBsonValue(insertedId) } } +@JvmName("insertOneTyped") +public suspend inline fun MongoCollection<*, *>.insertOne(document: T): R { + return (this as MongoCollection).insertOne(document) +} -// insertMany(docList): Map TypedMongoCollection.insertMany( +public suspend inline fun MongoCollection.insertMany( documents: Collection, ): List { - val encodedDocument: BsonValue = EJson.encodeToBsonValue( - EJson.serializersModule.serializerOrRealmBuiltInSerializer(), - documents - ) - val insertedId: List = (this as MongoCollectionImpl).call("insertMany") { - put("documents", encodedDocument) - }.asDocument().get("insertedIds")!!.asArray().toList() + Validation.isType>(this, "") + val encodedDocuments: List = documents.map { eJson.encodeToBsonValue(it).asDocument() } + val insertedIds: List = insertMany(encodedDocuments) return if (R::class == BsonValue::class) { - insertedId as List + insertedIds as List } else { - insertedId.map { decodeFromBsonValue(it) } + insertedIds.map { (this as MongoCollectionImpl).decodeFromBsonValue(it) } } } -@ExperimentalRealmSerializerApi -public suspend inline fun TypedMongoCollection<*, *>.insertMany(documents: Collection): List = - (this as TypedMongoCollectionImpl).insertMany(documents) - -@ExperimentalRealmSerializerApi -@OptIn(ExperimentalKBsonSerializerApi::class) -public suspend inline fun MongoCollection.insertMany(documents: Collection): List { - val encodedDocument: BsonValue = EJson.encodeToBsonValue(EJson.serializersModule.serializerOrRealmBuiltInSerializer(), documents) - val insertedId: List = (this as MongoCollectionImpl).call("insertMany") { - put("documents", encodedDocument) - }.asDocument().get("insertedIds")!!.asArray().toList() - return if (R::class == BsonValue::class) { insertedId as List } else { insertedId.map { EJson.decodeFromBsonValue(it) } } -} - -// deleteOne(filter): Count -// query -@ExperimentalRealmSerializerApi -@OptIn(ExperimentalKBsonSerializerApi::class) -public suspend inline fun MongoCollection.deleteOne(filter: BsonDocument): Boolean { -// val encodedDocument: BsonValue = EJson.encodeToBsonValue(EJson.serializersModule.serializerOrRealmBuiltInSerializer(), documents) - val insertedId: BsonValue = (this as MongoCollectionImpl).call("deleteOne") { - put("query", filter) - }.asDocument().get("deletedCount")!! - val decodeFromBsonValue = EJson.decodeFromBsonValue(insertedId) - return when(decodeFromBsonValue) { - 0L -> false - 1L -> true - else -> TODO("Unexpected $decodeFromBsonValue") - } +@JvmName("insertManyTyped") +public suspend inline fun MongoCollection<*, *>.insertMany(documents: Collection): List { + return (this as MongoCollection).insertMany(documents) } +//@OptIn(ExperimentalKBsonSerializerApi::class) +//public suspend inline fun MongoCollection<*, *>.deleteOne(filter: BsonDocument): Boolean { +//// val encodedDocument: BsonValue = EJson.encodeToBsonValue(EJson.serializersModule.serializerOrRealmBuiltInSerializer(), documents) +// val insertedId: BsonValue = (this as BsonMongoCollection).call("deleteOne") { +// put("query", filter) +// }.asDocument().get("deletedCount")!! +// val decodeFromBsonValue = EJson.decodeFromBsonValue(insertedId) +// return when(decodeFromBsonValue) { +// 0L -> false +// 1L -> true +// else -> TODO("Unexpected $decodeFromBsonValue") +// } +//} +// +// +//// deleteMany(filter): Count +//// query +//@ExperimentalRealmSerializerApi +//@OptIn(ExperimentalKBsonSerializerApi::class) +//public suspend inline fun MongoCollection<*, *>.deleteMany(filter: BsonDocument): Long { +// val insertedId: BsonValue = (this as BsonMongoCollection).call("deleteMany") { +// put("query", filter) +// }.asDocument().get("deletedCount")!! +// return EJson.decodeFromBsonValue(insertedId) +//} +// +//// updateOne(filter, updateDoc, updateOptions(upsert: Boolean)): UpdateResult(matchCount, modifiedCount, upsertedId) +//// updateMany(filter, updateDoc, updateOptions(upsert: Boolean)): UpdateResult(matchCount, modifiedCount, upsertedId) +//// query +//// update : BsonDocument +//// upsert: Boolean +// +//// FIXME Would we also allow filter and update to be serializables? +//// FIXME Could just return Boolean, since matchedCount=1,modifiedCount=1 even if multiple documents should be matching :thinking: +//// FIXME Should we split into upsertOne, since response only contains 'upsertedId' if call has 'upsert:true` +//@ExperimentalRealmSerializerApi +//@OptIn(ExperimentalKBsonSerializerApi::class) +//public suspend inline fun MongoCollection<*, *>.updateOne( +// filter: BsonDocument, +// update: BsonDocument, +// upsert: Boolean = false +//): R { +// val insertedId: BsonValue = (this as BsonMongoCollection).call("updateOne") { +// put("query", filter) +// put("update", update) +// put("upsert", BsonBoolean(upsert)) +// }.asDocument()//.get("insertedId")!!.asArray().toList() +// // {"matchedCount":{"$numberInt":"0"},"modifiedCount":{"$numberInt":"0"}} +// +// return if (R::class == BsonValue::class) { insertedId as R } else { EJson.decodeFromBsonValue(insertedId) } +//} +//@ExperimentalRealmSerializerApi +//@OptIn(ExperimentalKBsonSerializerApi::class) +//public suspend inline fun MongoCollection<*, *>.updateMany( +// filter: BsonDocument, +// update: BsonDocument, +// upsert: Boolean = false +//): R { +// val insertedId: BsonValue = (this as BsonMongoCollection).call("updateMany") { +// put("query", filter) +// put("update", update) +// put("upsert", BsonBoolean(upsert)) +// }.asDocument().get("upsertedId")!! +// // {"matchedCount":{"$numberInt":"0"},"modifiedCount":{"$numberInt":"0"}} +// +// return if (R::class == BsonValue::class) { insertedId as R } else { EJson.decodeFromBsonValue(insertedId) } +//} +// +//@ExperimentalRealmSerializerApi +//@OptIn(ExperimentalKBsonSerializerApi::class) +//public suspend inline fun MongoCollection<*, *>.findOneAndUpdate( +// filter: BsonDocument, +// update: BsonDocument, +// projection: BsonDocument? = null, +// sort: BsonDocument? = null, +// upsert: Boolean = false, +// returnNewDoc: Boolean = false, +//): R { +// val updatedDocument: BsonValue = (this as BsonMongoCollection).call("findOneAndUpdate") { +// put("filter", filter) +// put("update", update) +// projection?.let { put("projection", projection)} +// sort?.let { put("sort", sort)} +// put("upsert", BsonBoolean(upsert)) +// put("returnNewDoc", BsonBoolean(returnNewDoc)) +// } +// return if (R::class == BsonValue::class) { +// updatedDocument as R +// } else { +// EJson.decodeFromBsonValue(updatedDocument) +// } +//} +// +//@ExperimentalRealmSerializerApi +//@OptIn(ExperimentalKBsonSerializerApi::class) +//public suspend inline fun MongoCollection<*, *>.findOneAndReplace( +// filter: BsonDocument, +// update: BsonDocument, +// projection: BsonDocument? = null, +// sort: BsonDocument? = null, +// upsert: Boolean = false, +// returnNewDoc: Boolean = false, +//): R { +// // If returnNewDoc==true then the returned document is after the update otherwise it is from +// // before the update +// val updatedDocument: BsonValue = (this as BsonMongoCollection).call("findOneAndReplace") { +// put("filter", filter) +// put("update", update) +// projection?.let { put("projection", projection)} +// sort?.let { put("sort", sort)} +// put("upsert", BsonBoolean(upsert)) +// put("returnNewDoc", BsonBoolean(returnNewDoc)) +// } +// return if (R::class == BsonValue::class) { +// updatedDocument as R +// } else { +// EJson.decodeFromBsonValue(updatedDocument) +// } +//} +// +//@ExperimentalRealmSerializerApi +//@OptIn(ExperimentalKBsonSerializerApi::class) +//public suspend inline fun MongoCollection<*, *>.findOneAndDelete( +// filter: BsonDocument, +// projection: BsonDocument? = null, +// sort: BsonDocument? = null, +//): R { +// val deletedDocument: BsonValue = (this as BsonMongoCollection).call("findOneAndDelete") { +// put("filter", filter) +// projection?.let { put("projection", projection)} +// sort?.let { put("sort", sort)} +// } +// return if (R::class == BsonValue::class) { +// deletedDocument as R +// } else { +// EJson.decodeFromBsonValue(deletedDocument) +// } +//} -// deleteMany(filter): Count -// query -@ExperimentalRealmSerializerApi -@OptIn(ExperimentalKBsonSerializerApi::class) -public suspend inline fun MongoCollection.deleteMany(filter: BsonDocument): Long { - val insertedId: BsonValue = (this as MongoCollectionImpl).call("deleteMany") { - put("query", filter) - }.asDocument().get("deletedCount")!! - return EJson.decodeFromBsonValue(insertedId) -} +// +// +//public class ClientOption { +// +//} +// +//@PublishedApi +//internal interface FilterOptionInternal { +// var filter: BsonDocument? +//} +//@PublishedApi +//internal interface LimitOptionInternal { +// var limit: Long? +//} +// +//public interface LimitOption { } +//public fun LimitOption.limit(limit: Long) { +// (this as LimitOptionInternal).limit = limit +//} +//public interface FilterOption { } +//public fun FilterOption.filter(json: String) { +// (this as FilterOptionInternal).filter = BsonDocument(json) +//} +//@ExperimentalKBsonSerializerApi +//public inline fun FilterOption.filter(argument: T) { +// filter(argument, EJson.serializersModule.serializerOrRealmBuiltInSerializer()) +//} +//@OptIn(ExperimentalKBsonSerializerApi::class) +//public inline fun FilterOption.filter(argument: T, serializer: KSerializer) { +// (this as FilterOptionInternal).filter = EJson.encodeToBsonValue(serializer, argument).asDocument() +//} +//public interface ProjectionOption +//public interface SortOption +// +//public interface CountOptions : LimitOption, FilterOption +//public interface FindOneOptions : LimitOption, FilterOption, SortOption, ProjectionOption +// +//@PublishedApi +//internal class FindOptionsInternal: FindOneOptions, FilterOptionInternal, LimitOptionInternal { +// override var filter: BsonDocument? = null +// override var limit: Long? = null +//} -// updateOne(filter, updateDoc, updateOptions(upsert: Boolean)): UpdateResult(matchCount, modifiedCount, upsertedId) -// updateMany(filter, updateDoc, updateOptions(upsert: Boolean)): UpdateResult(matchCount, modifiedCount, upsertedId) +//public suspend inline fun findOne(filter: Bson = {}, options: FindOptions(limit, projection, sort)): T // query -// update : BsonDocument -// upsert: Boolean - -// FIXME Would we also allow filter and update to be serializables? -// FIXME Could just return Boolean, since matchedCount=1,modifiedCount=1 even if multiple documents should be matching :thinking: -// FIXME Should we split into upsertOne, since response only contains 'upsertedId' if call has 'upsert:true` -@ExperimentalRealmSerializerApi -@OptIn(ExperimentalKBsonSerializerApi::class) -public suspend inline fun MongoCollection.updateOne( - filter: BsonDocument, - update: BsonDocument, - upsert: Boolean = false -): R { - val insertedId: BsonValue = (this as MongoCollectionImpl).call("updateOne") { - put("query", filter) - put("update", update) - put("upsert", BsonBoolean(upsert)) - }.asDocument()//.get("insertedId")!!.asArray().toList() - // {"matchedCount":{"$numberInt":"0"},"modifiedCount":{"$numberInt":"0"}} - - return if (R::class == BsonValue::class) { insertedId as R } else { EJson.decodeFromBsonValue(insertedId) } -} -@ExperimentalRealmSerializerApi -@OptIn(ExperimentalKBsonSerializerApi::class) -public suspend inline fun MongoCollection.updateMany( - filter: BsonDocument, - update: BsonDocument, - upsert: Boolean = false -): R { - val insertedId: BsonValue = (this as MongoCollectionImpl).call("updateMany") { - put("query", filter) - put("update", update) - put("upsert", BsonBoolean(upsert)) - }.asDocument().get("upsertedId")!! - // {"matchedCount":{"$numberInt":"0"},"modifiedCount":{"$numberInt":"0"}} - - return if (R::class == BsonValue::class) { insertedId as R } else { EJson.decodeFromBsonValue(insertedId) } -} - -@ExperimentalRealmSerializerApi -@OptIn(ExperimentalKBsonSerializerApi::class) -public suspend inline fun MongoCollection.findOneAndUpdate( - filter: BsonDocument, - update: BsonDocument, - projection: BsonDocument? = null, - sort: BsonDocument? = null, - upsert: Boolean = false, - returnNewDoc: Boolean = false, -): R { - val updatedDocument: BsonValue = (this as MongoCollectionImpl).call("findOneAndUpdate") { - put("filter", filter) - put("update", update) - projection?.let { put("projection", projection)} - sort?.let { put("sort", sort)} - put("upsert", BsonBoolean(upsert)) - put("returnNewDoc", BsonBoolean(returnNewDoc)) - } - return if (R::class == BsonValue::class) { - updatedDocument as R - } else { - EJson.decodeFromBsonValue(updatedDocument) - } -} +// findoptions +// limit +// projection +// sort -@ExperimentalRealmSerializerApi -@OptIn(ExperimentalKBsonSerializerApi::class) -public suspend inline fun MongoCollection.findOneAndReplace( - filter: BsonDocument, - update: BsonDocument, - projection: BsonDocument? = null, - sort: BsonDocument? = null, - upsert: Boolean = false, - returnNewDoc: Boolean = false, -): R { - // If returnNewDoc==true then the returned document is after the update otherwise it is from - // before the update - val updatedDocument: BsonValue = (this as MongoCollectionImpl).call("findOneAndReplace") { - put("filter", filter) - put("update", update) - projection?.let { put("projection", projection)} - sort?.let { put("sort", sort)} - put("upsert", BsonBoolean(upsert)) - put("returnNewDoc", BsonBoolean(returnNewDoc)) - } - return if (R::class == BsonValue::class) { - updatedDocument as R - } else { - EJson.decodeFromBsonValue(updatedDocument) - } -} +//@OptIn(ExperimentalKBsonSerializerApi::class) +//@ExperimentalRealmSerializerApi +//public suspend inline fun MongoCollection.findOne(configuration: FindOneOptions.() -> Unit): T { +// val options = FindOptionsInternal() +// configuration(options) +// val response = (this as BsonMongoCollection).call("findOne") { +// options.filter?.let { put("query", it) } +//// options.projection?.let { put("projection", it) } +//// options.sort?.let { put("sort", it) } +// } +// return EJson.decodeFromBsonValue(response) +//} -@ExperimentalRealmSerializerApi -@OptIn(ExperimentalKBsonSerializerApi::class) -public suspend inline fun MongoCollection.findOneAndDelete( - filter: BsonDocument, - projection: BsonDocument? = null, - sort: BsonDocument? = null, -): R { - val deletedDocument: BsonValue = (this as MongoCollectionImpl).call("findOneAndDelete") { - put("filter", filter) - projection?.let { put("projection", projection)} - sort?.let { put("sort", sort)} - } - return if (R::class == BsonValue::class) { - deletedDocument as R - } else { - EJson.decodeFromBsonValue(deletedDocument) - } -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoDatabase.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoDatabase.kt index 4fbfbadc09..6a1b34fb1d 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoDatabase.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoDatabase.kt @@ -16,7 +16,10 @@ package io.realm.kotlin.mongodb.mongo +import io.realm.kotlin.annotations.ExperimentalRealmSerializerApi import org.mongodb.kbson.BsonValue +import org.mongodb.kbson.ExperimentalKBsonSerializerApi +import org.mongodb.kbson.serialization.EJson import kotlin.jvm.JvmName /** @@ -29,9 +32,10 @@ public interface MongoDatabase { */ public val name: String - public fun collection(collectionName: String): MongoCollection - public fun typedCollectionbson(collectionName: String): TypedMongoCollection - public fun typedCollection(collectionName: String): TypedMongoCollection + public fun collection(collectionName: String): MongoCollection + + @OptIn(ExperimentalKBsonSerializerApi::class) + public fun collection(collectionName: String, eJson: EJson? = null): MongoCollection } diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTest.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTest.kt index 1ccd687acd..154fd791ab 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTest.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTest.kt @@ -27,22 +27,21 @@ import io.realm.kotlin.mongodb.exceptions.ServiceException import io.realm.kotlin.mongodb.mongo.MongoClient import io.realm.kotlin.mongodb.mongo.MongoCollection import io.realm.kotlin.mongodb.mongo.MongoDatabase -import io.realm.kotlin.mongodb.mongo.TypedMongoCollection import io.realm.kotlin.mongodb.mongo.aggregate -import io.realm.kotlin.mongodb.mongo.deleteMany -import io.realm.kotlin.mongodb.mongo.deleteOne +import io.realm.kotlin.mongodb.mongo.count +//import io.realm.kotlin.mongodb.mongo.deleteMany +//import io.realm.kotlin.mongodb.mongo.deleteOne import io.realm.kotlin.mongodb.mongo.find import io.realm.kotlin.mongodb.mongo.findOne -import io.realm.kotlin.mongodb.mongo.findOneAndDelete -import io.realm.kotlin.mongodb.mongo.findOneAndReplace -import io.realm.kotlin.mongodb.mongo.findOneAndUpdate +//import io.realm.kotlin.mongodb.mongo.findOneAndDelete +//import io.realm.kotlin.mongodb.mongo.findOneAndReplace +//import io.realm.kotlin.mongodb.mongo.findOneAndUpdate import io.realm.kotlin.mongodb.mongo.insertMany import io.realm.kotlin.mongodb.mongo.insertOne -import io.realm.kotlin.mongodb.mongo.updateMany -import io.realm.kotlin.mongodb.mongo.updateOne +//import io.realm.kotlin.mongodb.mongo.updateMany +//import io.realm.kotlin.mongodb.mongo.updateOne import io.realm.kotlin.test.mongodb.TestApp import io.realm.kotlin.test.mongodb.asTestApp - import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage import io.realm.kotlin.types.RealmObject import kotlinx.serialization.Serializable @@ -52,7 +51,6 @@ import org.mongodb.kbson.BsonString import org.mongodb.kbson.BsonValue import org.mongodb.kbson.ExperimentalKBsonSerializerApi import org.mongodb.kbson.ObjectId -import org.mongodb.kbson.serialization.Bson import org.mongodb.kbson.serialization.EJson import org.mongodb.kbson.serialization.encodeToBsonValue import kotlin.random.Random @@ -67,13 +65,13 @@ import kotlin.test.assertTrue private const val SERVICE_NAME = "BackingDB" -@OptIn(ExperimentalRealmSerializerApi::class) +@OptIn(ExperimentalRealmSerializerApi::class, ExperimentalKBsonSerializerApi::class) class MongoClientTest { lateinit var app: TestApp lateinit var client: MongoClient lateinit var database: MongoDatabase - lateinit var collection: MongoCollection + lateinit var collection: MongoCollection @BeforeTest fun setUp() { @@ -93,7 +91,7 @@ class MongoClientTest { val user = app.createUserAndLogin() client = user.mongoClient(SERVICE_NAME) database = client.database(databaseName) - collection = database.collection("SyncDog") + collection = database.collection("SyncDog") } @AfterTest @@ -113,7 +111,9 @@ class MongoClientTest { @Test fun count() = runBlocking { - assertEquals(0, collection.count()) + RealmLog.level = LogLevel.ALL + val actual = collection.count() + assertEquals(0, actual) app.asTestApp.run { (1..10).forEach { @@ -144,7 +144,10 @@ class MongoClientTest { @Test fun findOne() = runBlocking { - assertNull(collection.findOne()) + val actual = collection.findOne() + assertNull(actual) + val actual1 = collection.findOne() + assertNull(actual1) app.asTestApp.run { (1..10).forEach { @@ -322,6 +325,7 @@ class MongoClientTest { @Test fun find() = runBlocking { RealmLog.level = LogLevel.ALL + assertTrue { collection.find().isEmpty() } assertTrue { collection.find().isEmpty() } val x: List = collection.insertMany(listOf(SyncDog("dog1"), SyncDog("dog2"))) @@ -396,7 +400,9 @@ class MongoClientTest { @Test fun aggregate() = runBlocking { RealmLog.level = LogLevel.ALL + collection.aggregate(listOf()) collection.aggregate(listOf()) + collection.aggregate(listOf()) val x: List = collection.insertMany(listOf(SyncDog(name = "dog1"), SyncDog(name = "dog2"))) collection.aggregate(listOf()) @@ -530,7 +536,7 @@ class MongoClientTest { fun insertMany() = runBlocking { RealmLog.level = LogLevel.ALL val x: List = collection.insertMany(listOf(SyncDog("a"))) - val syncDogIntIdCollection = database.collection("SyncDogIntId") + val syncDogIntIdCollection = database.collection("SyncDogIntId") val elements = SyncDogIntId("a", Random.nextInt()) val y: List = syncDogIntIdCollection.insertMany(listOf(elements)) @@ -544,10 +550,10 @@ class MongoClientTest { val z: List = typedCollection.insertMany(listOf(SyncDog("sadf"))) val tyz = typedCollection.insertMany(listOf(SyncDog("sadf"))) - val bsonSyncDogs /*: TypedMongoCollection */ = database.typedCollectionbson("SyncDog") + val bsonSyncDogs: MongoCollection = database.collection("SyncDog") val insertMany /*: List */ = bsonSyncDogs.insertMany(listOf(BsonDocument("name", "x"))) - val syncDogs: TypedMongoCollection = database.typedCollection("SyncDog") + val syncDogs: MongoCollection = database.collection("SyncDog") val objectIds = syncDogs.insertMany(listOf(SyncDog("name"))) @@ -661,735 +667,735 @@ class MongoClientTest { // } // - @Test - fun deleteOne() = runBlocking { - // Argument wrapper DSL - RealmLog.level = LogLevel.ALL - assertFalse { collection.deleteOne(BsonDocument()) } - - // A bit ann - val elements = SyncDog("x") - assertEquals(2, collection.insertMany(listOf(elements, elements)).size) - assertTrue { collection.deleteOne(BsonDocument("""{ "name": "x" }""")) } - } - - // @Test -// fun deleteOne_singleDocument() { -// with(getCollectionInternal()) { -// assertEquals(0, deleteOne(Document()).get()!!.deletedCount) -// assertEquals(0, deleteOne(Document("hello", "world")).get()!!.deletedCount) -// -// val doc1 = Document("hello", "world") -// -// insertOne(doc1).get() -// assertEquals(1, deleteOne(doc1).get()!!.deletedCount) -// assertEquals(0, count().get()) -// } -// } -// -// @Test -// fun deleteOne_fails() { -// with(getCollectionInternal()) { -// assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { -// deleteOne(Document("\$who", 1)).get() -// }.also { e -> -// assertTrue(e.errorMessage!!.contains("operator", true)) -// } -// } -// } -// -// @Test -// fun deleteOne_multipleDocuments() { -// with(getCollectionInternal()) { -// assertEquals(0, count().get()) -// -// val rawDoc = Document("hello", "world") -// val doc1 = Document(rawDoc) -// val doc1b = Document(rawDoc) -// val doc2 = Document("foo", "bar") -// val doc3 = Document("42", "666") -// insertMany(listOf(doc1, doc1b, doc2, doc3)).get() -// assertEquals(1, deleteOne(rawDoc).get()!!.deletedCount) -// assertEquals(1, deleteOne(Document()).get()!!.deletedCount) -// assertEquals(2, count().get()) -// } -// } -// - @Test - fun deleteMany() = runBlocking { - // Argument wrapper DSL - RealmLog.level = LogLevel.ALL - assertEquals(0, collection.deleteMany(BsonDocument())) - - // A bit ann - val elements = SyncDog("x") - assertEquals(2, collection.insertMany(listOf(elements, elements)).size) - assertEquals(2, collection.deleteMany(BsonDocument("""{ "name": "x" }"""))) - - assertEquals( - 3, - collection.insertMany(listOf(elements, elements, elements)).size - ) - assertEquals(3, collection.deleteMany(BsonDocument())) - } - - // @Test -// fun deleteMany_singleDocument() { -// with(getCollectionInternal()) { -// assertEquals(0, count().get()) -// -// val rawDoc = Document("hello", "world") -// val doc1 = Document(rawDoc) -// -// insertOne(doc1).get() -// assertEquals(1, count().get()) -// assertEquals(1, deleteMany(doc1).get()!!.deletedCount) -// assertEquals(0, count().get()) -// } -// } -// -// @Test -// fun deleteMany_multipleDocuments() { -// with(getCollectionInternal()) { -// assertEquals(0, count().get()) -// -// val rawDoc = Document("hello", "world") -// val doc1 = Document(rawDoc) -// val doc1b = Document(rawDoc) -// val doc2 = Document("foo", "bar") -// val doc3 = Document("42", "666") -// insertMany(listOf(doc1, doc1b, doc2, doc3)).get() -// assertEquals(2, deleteMany(rawDoc).get()!!.deletedCount) // two docs will be deleted -// assertEquals(2, count().get()) // two docs still present -// assertEquals(2, deleteMany(Document()).get()!!.deletedCount) // delete all -// assertEquals(0, count().get()) -// -// insertMany(listOf(doc1, doc1b, doc2, doc3)).get() -// assertEquals(4, deleteMany(Document()).get()!!.deletedCount) // delete all -// assertEquals(0, count().get()) -// } -// } -// -// @Test -// fun deleteMany_fails() { -// with(getCollectionInternal()) { -// assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { -// deleteMany(Document("\$who", 1)).get() -// }.also { e -> -// assertTrue(e.errorMessage!!.contains("operator", true)) -// } -// } -// } -// - @Test - fun updateOne() = runBlocking { - // Argument wrapper DSL - RealmLog.level = LogLevel.ALL - - val elements = SyncDog("x") - assertEquals(2, collection.insertMany(listOf(elements, elements)).size) - assertEquals(2, collection.count(filter = BsonDocument("""{"name": "x"}"""))) - - // A bit ann - collection.updateOne( - BsonDocument("""{ "name": "x"}"""), - BsonDocument("""{ "name": "y"}"""), - true - ) -// assertEquals(2, collection.deleteMany(BsonDocument("""{ "name": "x" }""" )) ) - -// assertEquals(3, collection.insertMany(listOf(elements, elements, elements)).size) -// assertEquals(3, collection.deleteMany(BsonDocument()) ) - - collection.updateOne( - BsonDocument("""{ "name": "z"}"""), - BsonDocument(""" { "name": "y"}"""), - upsert = true - ) - } - - // @Test -// fun updateOne_emptyCollection() { -// with(getCollectionInternal()) { -// val doc1 = Document("hello", "world") -// -// // Update on an empty collection -// updateOne(Document(), doc1) -// .get()!! -// .let { -// assertEquals(0, it.matchedCount) -// assertEquals(0, it.modifiedCount) -// assertNull(it.upsertedId) -// } -// -// // Update on an empty collection adding some values -// val doc2 = Document("\$set", Document("woof", "meow")) -// updateOne(Document(), doc2) -// .get()!! -// .let { -// assertEquals(0, it.matchedCount) -// assertEquals(0, it.modifiedCount) -// assertNull(it.upsertedId) -// assertEquals(0, count().get()) -// } -// } -// } -// -// @Test -// fun updateOne_emptyCollectionWithUpsert() { -// with(getCollectionInternal()) { -// val doc1 = Document("hello", "world") -// -// // Update on empty collection with upsert -// val options = UpdateOptions().upsert(true) -// updateOne(Document(), doc1, options) -// .get()!! -// .let { -// assertEquals(0, it.matchedCount) -// assertEquals(0, it.modifiedCount) -// assertFalse(it.upsertedId!!.isNull) -// } -// assertEquals(1, count().get()) -// -// assertEquals(doc1, find(Document()).first().get()!!.withoutId()) -// } -// } -// -// @Test -// fun updateOne_fails() { -// with(getCollectionInternal()) { -// assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { -// updateOne(Document("\$who", 1), Document()).get() -// }.also { e -> -// assertTrue(e.errorMessage!!.contains("operator", true)) -// } -// } -// } -// - @Test - fun updateMany() = runBlocking { - RealmLog.level = LogLevel.ALL - assertEquals( - 4, - collection.insertMany( - listOf( - SyncDog("x"), - SyncDog("x"), - SyncDog("y"), - SyncDog("z") - ) - ).size - ) - assertEquals(2, collection.count(filter = BsonDocument("""{"name": "x"}"""))) - collection.updateMany( - BsonDocument("""{"name": "x2"}"""), - BsonDocument("""{"name": "x1"}"""), - true - ) - } - - // @Test -// fun updateMany_emptyCollection() { -// with(getCollectionInternal()) { -// val doc1 = Document("hello", "world") -// -// // Update on empty collection -// updateMany(Document(), doc1) -// .get()!! -// .let { -// assertEquals(0, it.matchedCount) -// assertEquals(0, it.modifiedCount) -// assertNull(it.upsertedId) -// } -// assertEquals(0, count().get()) -// } -// } -// -// @Test -// fun updateMany_emptyCollectionWithUpsert() { -// with(getCollectionInternal()) { -// val doc1 = Document("hello", "world") -// -// // Update on empty collection with upsert -// updateMany(Document(), doc1, UpdateOptions().upsert(true)) -// .get()!! -// .let { -// assertEquals(0, it.matchedCount) -// assertEquals(0, it.modifiedCount) -// assertNotNull(it.upsertedId) -// } -// assertEquals(1, count().get()) -// -// // Add new value using update -// val update = Document("woof", "meow") -// updateMany(Document(), Document("\$set", update)) -// .get()!! -// .let { -// assertEquals(1, it.matchedCount) -// assertEquals(1, it.modifiedCount) -// assertNull(it.upsertedId) -// } -// assertEquals(1, count().get()) -// val expected = Document(doc1).apply { this["woof"] = "meow" } -// assertEquals(expected, find().first().get()!!.withoutId()) -// -// // Insert empty document, add ["woof", "meow"] to it and check it worked -// insertOne(Document()).get() -// updateMany(Document(), Document("\$set", update)) -// .get()!! -// .let { -// assertEquals(2, it.matchedCount) -// assertEquals(2, it.modifiedCount) -// } -// assertEquals(2, count().get()) -// find().iterator() -// .get()!! -// .let { -// assertEquals(expected, it.next().withoutId()) -// assertEquals(update, it.next().withoutId()) -// assertFalse(it.hasNext()) -// } -// } -// } -// // @Test -// fun updateMany_fails() { -// with(getCollectionInternal()) { -// assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { -// updateMany(Document("\$who", 1), Document()).get() -// }.also { e -> -// assertTrue(e.errorMessage!!.contains("operator", true)) -// } -// } -// } -// - @Test - fun findOneAndUpdate() = runBlocking { - RealmLog.level = LogLevel.ALL - assertNull(collection.findOneAndUpdate(BsonDocument(), BsonDocument())) - collection.insertMany( - listOf( - SyncDog("dog1"), - SyncDog("dog1"), - SyncDog("dog2") - ) - ) - collection.findOneAndUpdate( - BsonDocument(), - BsonDocument("""{ "name": "dog1" }"""), - upsert = true - ) - } -// @Test -// fun findOneAndUpdate_emptyCollection() { -// with(getCollectionInternal()) { -// // Test null return format -// assertNull(findOneAndUpdate(Document(), Document()).get()) -// } +// fun deleteOne() = runBlocking { +// // Argument wrapper DSL +// RealmLog.level = LogLevel.ALL +// assertFalse { collection.deleteOne(BsonDocument()) } +// +// // A bit ann +// val elements = SyncDog("x") +// assertEquals(2, collection.insertMany(listOf(elements, elements)).size) +// assertTrue { collection.deleteOne(BsonDocument("""{ "name": "x" }""")) } // } // +// // @Test +//// fun deleteOne_singleDocument() { +//// with(getCollectionInternal()) { +//// assertEquals(0, deleteOne(Document()).get()!!.deletedCount) +//// assertEquals(0, deleteOne(Document("hello", "world")).get()!!.deletedCount) +//// +//// val doc1 = Document("hello", "world") +//// +//// insertOne(doc1).get() +//// assertEquals(1, deleteOne(doc1).get()!!.deletedCount) +//// assertEquals(0, count().get()) +//// } +//// } +//// +//// @Test +//// fun deleteOne_fails() { +//// with(getCollectionInternal()) { +//// assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { +//// deleteOne(Document("\$who", 1)).get() +//// }.also { e -> +//// assertTrue(e.errorMessage!!.contains("operator", true)) +//// } +//// } +//// } +//// +//// @Test +//// fun deleteOne_multipleDocuments() { +//// with(getCollectionInternal()) { +//// assertEquals(0, count().get()) +//// +//// val rawDoc = Document("hello", "world") +//// val doc1 = Document(rawDoc) +//// val doc1b = Document(rawDoc) +//// val doc2 = Document("foo", "bar") +//// val doc3 = Document("42", "666") +//// insertMany(listOf(doc1, doc1b, doc2, doc3)).get() +//// assertEquals(1, deleteOne(rawDoc).get()!!.deletedCount) +//// assertEquals(1, deleteOne(Document()).get()!!.deletedCount) +//// assertEquals(2, count().get()) +//// } +//// } +//// // @Test -// fun findOneAndUpdate_noUpdates() { -// with(getCollectionInternal()) { -// assertNull(findOneAndUpdate(Document(), Document()).get()) -// assertEquals(0, count().get()) -// } +// fun deleteMany() = runBlocking { +// // Argument wrapper DSL +// RealmLog.level = LogLevel.ALL +// assertEquals(0, collection.deleteMany(BsonDocument())) +// +// // A bit ann +// val elements = SyncDog("x") +// assertEquals(2, collection.insertMany(listOf(elements, elements)).size) +// assertEquals(2, collection.deleteMany(BsonDocument("""{ "name": "x" }"""))) +// +// assertEquals( +// 3, +// collection.insertMany(listOf(elements, elements, elements)).size +// ) +// assertEquals(3, collection.deleteMany(BsonDocument())) // } // +// // @Test +//// fun deleteMany_singleDocument() { +//// with(getCollectionInternal()) { +//// assertEquals(0, count().get()) +//// +//// val rawDoc = Document("hello", "world") +//// val doc1 = Document(rawDoc) +//// +//// insertOne(doc1).get() +//// assertEquals(1, count().get()) +//// assertEquals(1, deleteMany(doc1).get()!!.deletedCount) +//// assertEquals(0, count().get()) +//// } +//// } +//// +//// @Test +//// fun deleteMany_multipleDocuments() { +//// with(getCollectionInternal()) { +//// assertEquals(0, count().get()) +//// +//// val rawDoc = Document("hello", "world") +//// val doc1 = Document(rawDoc) +//// val doc1b = Document(rawDoc) +//// val doc2 = Document("foo", "bar") +//// val doc3 = Document("42", "666") +//// insertMany(listOf(doc1, doc1b, doc2, doc3)).get() +//// assertEquals(2, deleteMany(rawDoc).get()!!.deletedCount) // two docs will be deleted +//// assertEquals(2, count().get()) // two docs still present +//// assertEquals(2, deleteMany(Document()).get()!!.deletedCount) // delete all +//// assertEquals(0, count().get()) +//// +//// insertMany(listOf(doc1, doc1b, doc2, doc3)).get() +//// assertEquals(4, deleteMany(Document()).get()!!.deletedCount) // delete all +//// assertEquals(0, count().get()) +//// } +//// } +//// +//// @Test +//// fun deleteMany_fails() { +//// with(getCollectionInternal()) { +//// assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { +//// deleteMany(Document("\$who", 1)).get() +//// }.also { e -> +//// assertTrue(e.errorMessage!!.contains("operator", true)) +//// } +//// } +//// } +//// // @Test -// fun findOneAndUpdate_noUpsert() { -// with(getCollectionInternal()) { -// val sampleDoc = Document("hello", "world1") -// sampleDoc["num"] = 2 -// -// // Insert a sample Document -// insertOne(sampleDoc).get() -// assertEquals(1, count().get()) -// -// // Sample call to findOneAndUpdate() where we get the previous document back -// val sampleUpdate = Document("\$set", Document("hello", "hellothere")).apply { -// this["\$inc"] = Document("num", 1) -// } -// findOneAndUpdate(Document("hello", "world1"), sampleUpdate) -// .get()!! -// .withoutId() -// .let { -// assertEquals(sampleDoc.withoutId(), it) -// } -// assertEquals(1, count().get()) -// -// // Make sure the update took place -// val expectedDoc = Document("hello", "hellothere") -// expectedDoc["num"] = 3 -// assertEquals(expectedDoc.withoutId(), find().first().get()!!.withoutId()) -// assertEquals(1, count().get()) -// -// // Call findOneAndUpdate() again but get the new document -// sampleUpdate.remove("\$set") -// expectedDoc["num"] = 4 -// val options = FindOneAndModifyOptions() -// .returnNewDocument(true) -// findOneAndUpdate(Document("hello", "hellothere"), sampleUpdate, options) -// .get()!! -// .withoutId() -// .let { -// assertEquals(expectedDoc.withoutId(), it) -// } -// assertEquals(1, count().get()) -// -// // Test null behaviour again with a filter that should not match any documents -// assertNull(findOneAndUpdate(Document("hello", "zzzzz"), Document()).get()) -// assertEquals(1, count().get()) -// } +// fun updateOne() = runBlocking { +// // Argument wrapper DSL +// RealmLog.level = LogLevel.ALL +// +// val elements = SyncDog("x") +// assertEquals(2, collection.insertMany(listOf(elements, elements)).size) +// assertEquals(2, collection.count(filter = BsonDocument("""{"name": "x"}"""))) +// +// // A bit ann +// collection.updateOne( +// BsonDocument("""{ "name": "x"}"""), +// BsonDocument("""{ "name": "y"}"""), +// true +// ) +//// assertEquals(2, collection.deleteMany(BsonDocument("""{ "name": "x" }""" )) ) +// +//// assertEquals(3, collection.insertMany(listOf(elements, elements, elements)).size) +//// assertEquals(3, collection.deleteMany(BsonDocument()) ) +// +// collection.updateOne( +// BsonDocument("""{ "name": "z"}"""), +// BsonDocument(""" { "name": "y"}"""), +// upsert = true +// ) // } // +// // @Test +//// fun updateOne_emptyCollection() { +//// with(getCollectionInternal()) { +//// val doc1 = Document("hello", "world") +//// +//// // Update on an empty collection +//// updateOne(Document(), doc1) +//// .get()!! +//// .let { +//// assertEquals(0, it.matchedCount) +//// assertEquals(0, it.modifiedCount) +//// assertNull(it.upsertedId) +//// } +//// +//// // Update on an empty collection adding some values +//// val doc2 = Document("\$set", Document("woof", "meow")) +//// updateOne(Document(), doc2) +//// .get()!! +//// .let { +//// assertEquals(0, it.matchedCount) +//// assertEquals(0, it.modifiedCount) +//// assertNull(it.upsertedId) +//// assertEquals(0, count().get()) +//// } +//// } +//// } +//// +//// @Test +//// fun updateOne_emptyCollectionWithUpsert() { +//// with(getCollectionInternal()) { +//// val doc1 = Document("hello", "world") +//// +//// // Update on empty collection with upsert +//// val options = UpdateOptions().upsert(true) +//// updateOne(Document(), doc1, options) +//// .get()!! +//// .let { +//// assertEquals(0, it.matchedCount) +//// assertEquals(0, it.modifiedCount) +//// assertFalse(it.upsertedId!!.isNull) +//// } +//// assertEquals(1, count().get()) +//// +//// assertEquals(doc1, find(Document()).first().get()!!.withoutId()) +//// } +//// } +//// +//// @Test +//// fun updateOne_fails() { +//// with(getCollectionInternal()) { +//// assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { +//// updateOne(Document("\$who", 1), Document()).get() +//// }.also { e -> +//// assertTrue(e.errorMessage!!.contains("operator", true)) +//// } +//// } +//// } +//// // @Test -// fun findOneAndUpdate_upsert() { -// with(getCollectionInternal()) { -// val doc1 = Document("hello", "world1").apply { this["num"] = 1 } -// val doc2 = Document("hello", "world2").apply { this["num"] = 2 } -// val doc3 = Document("hello", "world3").apply { this["num"] = 3 } -// -// val filter = Document("hello", "hellothere") -// -// // Test the upsert option where it should not actually be invoked -// var options = FindOneAndModifyOptions() -// .returnNewDocument(true) -// .upsert(true) -// val update1 = Document("\$set", doc1) -// assertEquals(doc1, -// findOneAndUpdate(filter, update1, options) -// .get()!! -// .withoutId()) -// assertEquals(1, count().get()) -// assertEquals(doc1.withoutId(), -// find().first() -// .get()!! -// .withoutId()) -// -// // Test the upsert option where the server should perform upsert and return new document -// val update2 = Document("\$set", doc2) -// assertEquals(doc2, -// findOneAndUpdate(filter, update2, options) -// .get()!! -// .withoutId()) -// assertEquals(2, count().get()) -// -// // Test the upsert option where the server should perform upsert and return old document -// // The old document should be empty -// options = FindOneAndModifyOptions() -// .upsert(true) -// val update = Document("\$set", doc3) -// assertNull(findOneAndUpdate(filter, update, options).get()) -// assertEquals(3, count().get()) -// } +// fun updateMany() = runBlocking { +// RealmLog.level = LogLevel.ALL +// assertEquals( +// 4, +// collection.insertMany( +// listOf( +// SyncDog("x"), +// SyncDog("x"), +// SyncDog("y"), +// SyncDog("z") +// ) +// ).size +// ) +// assertEquals(2, collection.count(filter = BsonDocument("""{"name": "x"}"""))) +// collection.updateMany( +// BsonDocument("""{"name": "x2"}"""), +// BsonDocument("""{"name": "x1"}"""), +// true +// ) // } // +// // @Test +//// fun updateMany_emptyCollection() { +//// with(getCollectionInternal()) { +//// val doc1 = Document("hello", "world") +//// +//// // Update on empty collection +//// updateMany(Document(), doc1) +//// .get()!! +//// .let { +//// assertEquals(0, it.matchedCount) +//// assertEquals(0, it.modifiedCount) +//// assertNull(it.upsertedId) +//// } +//// assertEquals(0, count().get()) +//// } +//// } +//// +//// @Test +//// fun updateMany_emptyCollectionWithUpsert() { +//// with(getCollectionInternal()) { +//// val doc1 = Document("hello", "world") +//// +//// // Update on empty collection with upsert +//// updateMany(Document(), doc1, UpdateOptions().upsert(true)) +//// .get()!! +//// .let { +//// assertEquals(0, it.matchedCount) +//// assertEquals(0, it.modifiedCount) +//// assertNotNull(it.upsertedId) +//// } +//// assertEquals(1, count().get()) +//// +//// // Add new value using update +//// val update = Document("woof", "meow") +//// updateMany(Document(), Document("\$set", update)) +//// .get()!! +//// .let { +//// assertEquals(1, it.matchedCount) +//// assertEquals(1, it.modifiedCount) +//// assertNull(it.upsertedId) +//// } +//// assertEquals(1, count().get()) +//// val expected = Document(doc1).apply { this["woof"] = "meow" } +//// assertEquals(expected, find().first().get()!!.withoutId()) +//// +//// // Insert empty document, add ["woof", "meow"] to it and check it worked +//// insertOne(Document()).get() +//// updateMany(Document(), Document("\$set", update)) +//// .get()!! +//// .let { +//// assertEquals(2, it.matchedCount) +//// assertEquals(2, it.modifiedCount) +//// } +//// assertEquals(2, count().get()) +//// find().iterator() +//// .get()!! +//// .let { +//// assertEquals(expected, it.next().withoutId()) +//// assertEquals(update, it.next().withoutId()) +//// assertFalse(it.hasNext()) +//// } +//// } +//// } +//// +//// @Test +//// fun updateMany_fails() { +//// with(getCollectionInternal()) { +//// assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { +//// updateMany(Document("\$who", 1), Document()).get() +//// }.also { e -> +//// assertTrue(e.errorMessage!!.contains("operator", true)) +//// } +//// } +//// } +//// // @Test -// fun findOneAndUpdate_withProjectionAndSort() { -// with(getCollectionInternal()) { -// insertMany(listOf( -// Document(mapOf(Pair("team", "Fearful Mallards"), Pair("score", 25000))), -// Document(mapOf(Pair("team", "Tactful Mooses"), Pair("score", 23500))), -// Document(mapOf(Pair("team", "Aquatic Ponies"), Pair("score", 19250))), -// Document(mapOf(Pair("team", "Cuddly Zebras"), Pair("score", 15235))), -// Document(mapOf(Pair("team", "Garrulous Bears"), Pair("score", 18000))) -// )).get() -// -// assertEquals(5, count().get()) -// assertNotNull(findOne(Document("team", "Cuddly Zebras"))) -// -// // Project: team, hide _id; Sort: score ascending -// val project = Document(mapOf(Pair("_id", 0), Pair("team", 1), Pair("score", 1))) -// val sort = Document("score", 1) -// -// // This results in the update of Cuddly Zebras -// val updatedDocument = findOneAndUpdate( -// Document("score", Document("\$lt", 22250)), -// Document("\$inc", Document("score", 1)), -// FindOneAndModifyOptions() -// .projection(project) -// .sort(sort) -// ).get() -// -// assertEquals(5, count().get()) -// assertEquals( -// Document(mapOf(Pair("team", "Cuddly Zebras"), Pair("score", 15235))), -// updatedDocument +// fun findOneAndUpdate() = runBlocking { +// RealmLog.level = LogLevel.ALL +// assertNull(collection.findOneAndUpdate(BsonDocument(), BsonDocument())) +// collection.insertMany( +// listOf( +// SyncDog("dog1"), +// SyncDog("dog1"), +// SyncDog("dog2") // ) -// assertEquals( -// Document(mapOf(Pair("team", "Cuddly Zebras"), Pair("score", 15235 + 1))), -// findOne(Document("team", "Cuddly Zebras")).get().withoutId() -// ) -// } -// } -// -// @Test -// fun findOneAndUpdate_fails() { -// with(getCollectionInternal()) { -// assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { -// findOneAndUpdate(Document(), Document("\$who", 1)).get() -// }.also { e -> -// assertTrue(e.errorMessage!!.contains("modifier", true)) -// } -// -// assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { -// findOneAndUpdate(Document(), Document("\$who", 1), FindOneAndModifyOptions().upsert(true)).get() -// }.also { e -> -// assertTrue(e.errorMessage!!.contains("modifier", true)) -// } -// } -// } - - // FIXME Invalid fields?~? - @Test - fun findOneAndReplace() = runBlocking { - RealmLog.level = LogLevel.ALL - assertNull(collection.findOneAndReplace(BsonDocument(), BsonDocument())) - collection.insertMany( - listOf( - SyncDog("dog1"), - SyncDog("dog1"), - SyncDog("dog2") - ) - ) - val x = collection.findOneAndReplace( - BsonDocument(), - BsonDocument("""{ "name": "dog1" }"""), - upsert = true - ) - println(x) - } -// -// @Test -// fun findOneAndReplace_noUpdates() { -// with(getCollectionInternal()) { -// // Test null behaviour again with a filter that should not match any documents -// assertNull(findOneAndReplace(Document("hello", "zzzzz"), Document()).get()) -// assertEquals(0, count().get()) -// assertNull(findOneAndReplace(Document(), Document()).get()) -// assertEquals(0, count().get()) -// } -// } -// -// @Test -// fun findOneAndReplace_noUpsert() { -// with(getCollectionInternal()) { -// val sampleDoc = Document("hello", "world1").apply { this["num"] = 2 } -// -// // Insert a sample Document -// insertOne(sampleDoc).get() -// assertEquals(1, count().get()) -// -// // Sample call to findOneAndReplace() where we get the previous document back -// var sampleUpdate = Document("hello", "world2").apply { this["num"] = 2 } -// assertEquals(sampleDoc.withoutId(), -// findOneAndReplace(Document("hello", "world1"), sampleUpdate).get()!!.withoutId()) -// assertEquals(1, count().get()) -// -// // Make sure the update took place -// val expectedDoc = Document("hello", "world2").apply { this["num"] = 2 } -// assertEquals(expectedDoc.withoutId(), find().first().get()!!.withoutId()) -// assertEquals(1, count().get()) -// -// // Call findOneAndReplace() again but get the new document -// sampleUpdate = Document("hello", "world3").apply { this["num"] = 3 } -// val options = FindOneAndModifyOptions().returnNewDocument(true) -// assertEquals(sampleUpdate.withoutId(), -// findOneAndReplace(Document(), sampleUpdate, options).get()!!.withoutId()) -// assertEquals(1, count().get()) -// -// // Test null behaviour again with a filter that should not match any documents -// assertNull(findOneAndReplace(Document("hello", "zzzzz"), Document()).get()) -// assertEquals(1, count().get()) -// } -// } -// -// @Test -// fun findOneAndReplace_upsert() { -// with(getCollectionInternal()) { -// val doc4 = Document("hello", "world4").apply { this["num"] = 4 } -// val doc5 = Document("hello", "world5").apply { this["num"] = 5 } -// val doc6 = Document("hello", "world6").apply { this["num"] = 6 } -// -// // Test the upsert option where it should not actually be invoked -// val sampleUpdate = Document("hello", "world4").apply { this["num"] = 4 } -// var options = FindOneAndModifyOptions() -// .returnNewDocument(true) -// .upsert(true) -// assertEquals(doc4.withoutId(), -// findOneAndReplace(Document("hello", "world3"), doc4, options) -// .get()!! -// .withoutId()) -// assertEquals(1, count().get()) -// assertEquals(doc4.withoutId(), find().first().get()!!.withoutId()) -// -// // Test the upsert option where the server should perform upsert and return new document -// options = FindOneAndModifyOptions().returnNewDocument(true).upsert(true) -// assertEquals(doc5.withoutId(), findOneAndReplace(Document("hello", "hellothere"), doc5, options).get()!!.withoutId()) -// assertEquals(2, count().get()) -// -// // Test the upsert option where the server should perform upsert and return old document -// // The old document should be empty -// options = FindOneAndModifyOptions().upsert(true) -// assertNull(findOneAndReplace(Document("hello", "hellothere"), doc6, options).get()) -// assertEquals(3, count().get()) -// } +// ) +// collection.findOneAndUpdate( +// BsonDocument(), +// BsonDocument("""{ "name": "dog1" }"""), +// upsert = true +// ) // } +//// @Test +//// fun findOneAndUpdate_emptyCollection() { +//// with(getCollectionInternal()) { +//// // Test null return format +//// assertNull(findOneAndUpdate(Document(), Document()).get()) +//// } +//// } +//// +//// @Test +//// fun findOneAndUpdate_noUpdates() { +//// with(getCollectionInternal()) { +//// assertNull(findOneAndUpdate(Document(), Document()).get()) +//// assertEquals(0, count().get()) +//// } +//// } +//// +//// @Test +//// fun findOneAndUpdate_noUpsert() { +//// with(getCollectionInternal()) { +//// val sampleDoc = Document("hello", "world1") +//// sampleDoc["num"] = 2 +//// +//// // Insert a sample Document +//// insertOne(sampleDoc).get() +//// assertEquals(1, count().get()) +//// +//// // Sample call to findOneAndUpdate() where we get the previous document back +//// val sampleUpdate = Document("\$set", Document("hello", "hellothere")).apply { +//// this["\$inc"] = Document("num", 1) +//// } +//// findOneAndUpdate(Document("hello", "world1"), sampleUpdate) +//// .get()!! +//// .withoutId() +//// .let { +//// assertEquals(sampleDoc.withoutId(), it) +//// } +//// assertEquals(1, count().get()) +//// +//// // Make sure the update took place +//// val expectedDoc = Document("hello", "hellothere") +//// expectedDoc["num"] = 3 +//// assertEquals(expectedDoc.withoutId(), find().first().get()!!.withoutId()) +//// assertEquals(1, count().get()) +//// +//// // Call findOneAndUpdate() again but get the new document +//// sampleUpdate.remove("\$set") +//// expectedDoc["num"] = 4 +//// val options = FindOneAndModifyOptions() +//// .returnNewDocument(true) +//// findOneAndUpdate(Document("hello", "hellothere"), sampleUpdate, options) +//// .get()!! +//// .withoutId() +//// .let { +//// assertEquals(expectedDoc.withoutId(), it) +//// } +//// assertEquals(1, count().get()) +//// +//// // Test null behaviour again with a filter that should not match any documents +//// assertNull(findOneAndUpdate(Document("hello", "zzzzz"), Document()).get()) +//// assertEquals(1, count().get()) +//// } +//// } +//// +//// @Test +//// fun findOneAndUpdate_upsert() { +//// with(getCollectionInternal()) { +//// val doc1 = Document("hello", "world1").apply { this["num"] = 1 } +//// val doc2 = Document("hello", "world2").apply { this["num"] = 2 } +//// val doc3 = Document("hello", "world3").apply { this["num"] = 3 } +//// +//// val filter = Document("hello", "hellothere") +//// +//// // Test the upsert option where it should not actually be invoked +//// var options = FindOneAndModifyOptions() +//// .returnNewDocument(true) +//// .upsert(true) +//// val update1 = Document("\$set", doc1) +//// assertEquals(doc1, +//// findOneAndUpdate(filter, update1, options) +//// .get()!! +//// .withoutId()) +//// assertEquals(1, count().get()) +//// assertEquals(doc1.withoutId(), +//// find().first() +//// .get()!! +//// .withoutId()) +//// +//// // Test the upsert option where the server should perform upsert and return new document +//// val update2 = Document("\$set", doc2) +//// assertEquals(doc2, +//// findOneAndUpdate(filter, update2, options) +//// .get()!! +//// .withoutId()) +//// assertEquals(2, count().get()) +//// +//// // Test the upsert option where the server should perform upsert and return old document +//// // The old document should be empty +//// options = FindOneAndModifyOptions() +//// .upsert(true) +//// val update = Document("\$set", doc3) +//// assertNull(findOneAndUpdate(filter, update, options).get()) +//// assertEquals(3, count().get()) +//// } +//// } +//// +//// @Test +//// fun findOneAndUpdate_withProjectionAndSort() { +//// with(getCollectionInternal()) { +//// insertMany(listOf( +//// Document(mapOf(Pair("team", "Fearful Mallards"), Pair("score", 25000))), +//// Document(mapOf(Pair("team", "Tactful Mooses"), Pair("score", 23500))), +//// Document(mapOf(Pair("team", "Aquatic Ponies"), Pair("score", 19250))), +//// Document(mapOf(Pair("team", "Cuddly Zebras"), Pair("score", 15235))), +//// Document(mapOf(Pair("team", "Garrulous Bears"), Pair("score", 18000))) +//// )).get() +//// +//// assertEquals(5, count().get()) +//// assertNotNull(findOne(Document("team", "Cuddly Zebras"))) +//// +//// // Project: team, hide _id; Sort: score ascending +//// val project = Document(mapOf(Pair("_id", 0), Pair("team", 1), Pair("score", 1))) +//// val sort = Document("score", 1) +//// +//// // This results in the update of Cuddly Zebras +//// val updatedDocument = findOneAndUpdate( +//// Document("score", Document("\$lt", 22250)), +//// Document("\$inc", Document("score", 1)), +//// FindOneAndModifyOptions() +//// .projection(project) +//// .sort(sort) +//// ).get() +//// +//// assertEquals(5, count().get()) +//// assertEquals( +//// Document(mapOf(Pair("team", "Cuddly Zebras"), Pair("score", 15235))), +//// updatedDocument +//// ) +//// assertEquals( +//// Document(mapOf(Pair("team", "Cuddly Zebras"), Pair("score", 15235 + 1))), +//// findOne(Document("team", "Cuddly Zebras")).get().withoutId() +//// ) +//// } +//// } +//// +//// @Test +//// fun findOneAndUpdate_fails() { +//// with(getCollectionInternal()) { +//// assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { +//// findOneAndUpdate(Document(), Document("\$who", 1)).get() +//// }.also { e -> +//// assertTrue(e.errorMessage!!.contains("modifier", true)) +//// } +//// +//// assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { +//// findOneAndUpdate(Document(), Document("\$who", 1), FindOneAndModifyOptions().upsert(true)).get() +//// }.also { e -> +//// assertTrue(e.errorMessage!!.contains("modifier", true)) +//// } +//// } +//// } // +// // FIXME Invalid fields?~? // @Test -// fun findOneAndReplace_withProjectionAndSort() { -// with(getCollectionInternal()) { -// insertMany(listOf( -// Document(mapOf(Pair("team", "Fearful Mallards"), Pair("score", 25000))), -// Document(mapOf(Pair("team", "Tactful Mooses"), Pair("score", 23500))), -// Document(mapOf(Pair("team", "Aquatic Ponies"), Pair("score", 19250))), -// Document(mapOf(Pair("team", "Cuddly Zebras"), Pair("score", 15235))), -// Document(mapOf(Pair("team", "Garrulous Bears"), Pair("score", 18000))) -// )).get() -// -// assertEquals(5, count().get()) -// assertNotNull(findOne(Document("team", "Cuddly Zebras"))) -// -// // Project: team, hide _id; Sort: score ascending -// val project = Document(mapOf(Pair("_id", 0), Pair("team", 1))) -// val sort = Document("score", 1) -// -// // This results in the replacement of Cuddly Zebras -// val replacedDocument = findOneAndReplace( -// Document("score", Document("\$lt", 22250)), -// Document(mapOf(Pair("team", "Therapeutic Hamsters"), Pair("score", 22250))), -// FindOneAndModifyOptions() -// .projection(project) -// .sort(sort) -// ).get() -// -// assertEquals(5, count().get()) -// assertEquals(Document("team", "Cuddly Zebras"), replacedDocument) -// assertNull(findOne(Document("team", "Cuddly Zebras")).get()) -// assertNotNull(findOne(Document("team", "Therapeutic Hamsters")).get()) -// -// // Check returnNewDocument -// val newDocument = findOneAndReplace( -// Document("score", 22250), -// Document(mapOf(Pair("team", "New Therapeutic Hamsters"), Pair("score", 30000))), -// FindOneAndModifyOptions().returnNewDocument(true) -// ).get() -// -// assertEquals(Document(mapOf(Pair("team", "New Therapeutic Hamsters"), Pair("score", 30000))), newDocument.withoutId()) -// } +// fun findOneAndReplace() = runBlocking { +// RealmLog.level = LogLevel.ALL +// assertNull(collection.findOneAndReplace(BsonDocument(), BsonDocument())) +// collection.insertMany( +// listOf( +// SyncDog("dog1"), +// SyncDog("dog1"), +// SyncDog("dog2") +// ) +// ) +// val x = collection.findOneAndReplace( +// BsonDocument(), +// BsonDocument("""{ "name": "dog1" }"""), +// upsert = true +// ) +// println(x) // } +//// +//// @Test +//// fun findOneAndReplace_noUpdates() { +//// with(getCollectionInternal()) { +//// // Test null behaviour again with a filter that should not match any documents +//// assertNull(findOneAndReplace(Document("hello", "zzzzz"), Document()).get()) +//// assertEquals(0, count().get()) +//// assertNull(findOneAndReplace(Document(), Document()).get()) +//// assertEquals(0, count().get()) +//// } +//// } +//// +//// @Test +//// fun findOneAndReplace_noUpsert() { +//// with(getCollectionInternal()) { +//// val sampleDoc = Document("hello", "world1").apply { this["num"] = 2 } +//// +//// // Insert a sample Document +//// insertOne(sampleDoc).get() +//// assertEquals(1, count().get()) +//// +//// // Sample call to findOneAndReplace() where we get the previous document back +//// var sampleUpdate = Document("hello", "world2").apply { this["num"] = 2 } +//// assertEquals(sampleDoc.withoutId(), +//// findOneAndReplace(Document("hello", "world1"), sampleUpdate).get()!!.withoutId()) +//// assertEquals(1, count().get()) +//// +//// // Make sure the update took place +//// val expectedDoc = Document("hello", "world2").apply { this["num"] = 2 } +//// assertEquals(expectedDoc.withoutId(), find().first().get()!!.withoutId()) +//// assertEquals(1, count().get()) +//// +//// // Call findOneAndReplace() again but get the new document +//// sampleUpdate = Document("hello", "world3").apply { this["num"] = 3 } +//// val options = FindOneAndModifyOptions().returnNewDocument(true) +//// assertEquals(sampleUpdate.withoutId(), +//// findOneAndReplace(Document(), sampleUpdate, options).get()!!.withoutId()) +//// assertEquals(1, count().get()) +//// +//// // Test null behaviour again with a filter that should not match any documents +//// assertNull(findOneAndReplace(Document("hello", "zzzzz"), Document()).get()) +//// assertEquals(1, count().get()) +//// } +//// } +//// +//// @Test +//// fun findOneAndReplace_upsert() { +//// with(getCollectionInternal()) { +//// val doc4 = Document("hello", "world4").apply { this["num"] = 4 } +//// val doc5 = Document("hello", "world5").apply { this["num"] = 5 } +//// val doc6 = Document("hello", "world6").apply { this["num"] = 6 } +//// +//// // Test the upsert option where it should not actually be invoked +//// val sampleUpdate = Document("hello", "world4").apply { this["num"] = 4 } +//// var options = FindOneAndModifyOptions() +//// .returnNewDocument(true) +//// .upsert(true) +//// assertEquals(doc4.withoutId(), +//// findOneAndReplace(Document("hello", "world3"), doc4, options) +//// .get()!! +//// .withoutId()) +//// assertEquals(1, count().get()) +//// assertEquals(doc4.withoutId(), find().first().get()!!.withoutId()) +//// +//// // Test the upsert option where the server should perform upsert and return new document +//// options = FindOneAndModifyOptions().returnNewDocument(true).upsert(true) +//// assertEquals(doc5.withoutId(), findOneAndReplace(Document("hello", "hellothere"), doc5, options).get()!!.withoutId()) +//// assertEquals(2, count().get()) +//// +//// // Test the upsert option where the server should perform upsert and return old document +//// // The old document should be empty +//// options = FindOneAndModifyOptions().upsert(true) +//// assertNull(findOneAndReplace(Document("hello", "hellothere"), doc6, options).get()) +//// assertEquals(3, count().get()) +//// } +//// } +//// +//// @Test +//// fun findOneAndReplace_withProjectionAndSort() { +//// with(getCollectionInternal()) { +//// insertMany(listOf( +//// Document(mapOf(Pair("team", "Fearful Mallards"), Pair("score", 25000))), +//// Document(mapOf(Pair("team", "Tactful Mooses"), Pair("score", 23500))), +//// Document(mapOf(Pair("team", "Aquatic Ponies"), Pair("score", 19250))), +//// Document(mapOf(Pair("team", "Cuddly Zebras"), Pair("score", 15235))), +//// Document(mapOf(Pair("team", "Garrulous Bears"), Pair("score", 18000))) +//// )).get() +//// +//// assertEquals(5, count().get()) +//// assertNotNull(findOne(Document("team", "Cuddly Zebras"))) +//// +//// // Project: team, hide _id; Sort: score ascending +//// val project = Document(mapOf(Pair("_id", 0), Pair("team", 1))) +//// val sort = Document("score", 1) +//// +//// // This results in the replacement of Cuddly Zebras +//// val replacedDocument = findOneAndReplace( +//// Document("score", Document("\$lt", 22250)), +//// Document(mapOf(Pair("team", "Therapeutic Hamsters"), Pair("score", 22250))), +//// FindOneAndModifyOptions() +//// .projection(project) +//// .sort(sort) +//// ).get() +//// +//// assertEquals(5, count().get()) +//// assertEquals(Document("team", "Cuddly Zebras"), replacedDocument) +//// assertNull(findOne(Document("team", "Cuddly Zebras")).get()) +//// assertNotNull(findOne(Document("team", "Therapeutic Hamsters")).get()) +//// +//// // Check returnNewDocument +//// val newDocument = findOneAndReplace( +//// Document("score", 22250), +//// Document(mapOf(Pair("team", "New Therapeutic Hamsters"), Pair("score", 30000))), +//// FindOneAndModifyOptions().returnNewDocument(true) +//// ).get() +//// +//// assertEquals(Document(mapOf(Pair("team", "New Therapeutic Hamsters"), Pair("score", 30000))), newDocument.withoutId()) +//// } +//// } +//// +//// @Test +//// fun findOneAndReplace_fails() { +//// with(getCollectionInternal()) { +//// assertFailsWithErrorCode(ErrorCode.INVALID_PARAMETER) { +//// findOneAndReplace(Document(), Document("\$who", 1)).get() +//// } +//// +//// assertFailsWithErrorCode(ErrorCode.INVALID_PARAMETER) { +//// findOneAndReplace(Document(), Document("\$who", 1), FindOneAndModifyOptions().upsert(true)).get() +//// } +//// } +//// } // // @Test -// fun findOneAndReplace_fails() { -// with(getCollectionInternal()) { -// assertFailsWithErrorCode(ErrorCode.INVALID_PARAMETER) { -// findOneAndReplace(Document(), Document("\$who", 1)).get() -// } -// -// assertFailsWithErrorCode(ErrorCode.INVALID_PARAMETER) { -// findOneAndReplace(Document(), Document("\$who", 1), FindOneAndModifyOptions().upsert(true)).get() -// } -// } -// } - - @Test - fun findOneAndDelete() = runBlocking { - RealmLog.level = LogLevel.ALL - assertNull(collection.findOneAndDelete(BsonDocument(), BsonDocument())) - collection.insertMany( - listOf( - SyncDog("dog1"), - SyncDog("dog1"), - SyncDog("dog2") - ) - ) - val x: SyncDog = collection.findOneAndDelete( - BsonDocument(), - BsonDocument("""{ "name": "dog1" }"""), - ) - } - // -// @Test -// fun findOneAndDelete() { -// with(getCollectionInternal()) { -// val sampleDoc = Document("hello", "world1").apply { this["num"] = 1 } -// -// // Collection should start out empty -// // This also tests the null return format -// assertNull(findOneAndDelete(Document()).get()) -// -// // Insert a sample Document -// insertOne(sampleDoc).get() -// assertEquals(1, count().get()) -// -// // Sample call to findOneAndDelete() where we delete the only doc in the collection -// assertEquals(sampleDoc.withoutId(), -// findOneAndDelete(Document()).get()!!.withoutId()) -// -// // There should be no documents in the collection now -// assertEquals(0, count().get()) -// -// // Insert a sample Document -// insertOne(sampleDoc).get() -// assertEquals(1, count().get()) -// -// // Call findOneAndDelete() again but this time with a filter -// assertEquals(sampleDoc.withoutId(), -// findOneAndDelete(Document("hello", "world1")).get()!!.withoutId()) -// -// // There should be no documents in the collection now -// assertEquals(0, count().get()) -// -// // Insert a sample Document -// insertOne(sampleDoc).get() -// assertEquals(1, count().get()) -// -// // Test null behaviour again with a filter that should not match any documents -// assertNull(findOneAndDelete(Document("hello", "zzzzz")).get()) -// assertEquals(1, count().get()) -// -// val doc2 = Document("hello", "world2").apply { this["num"] = 2 } -// val doc3 = Document("hello", "world3").apply { this["num"] = 3 } -// -// // Insert new documents -// insertMany(listOf(doc2, doc3)).get() -// assertEquals(3, count().get()) -// } +// fun findOneAndDelete() = runBlocking { +// RealmLog.level = LogLevel.ALL +// assertNull(collection.findOneAndDelete(BsonDocument(), BsonDocument())) +// collection.insertMany( +// listOf( +// SyncDog("dog1"), +// SyncDog("dog1"), +// SyncDog("dog2") +// ) +// ) +// val x: SyncDog = collection.findOneAndDelete( +// BsonDocument(), +// BsonDocument("""{ "name": "dog1" }"""), +// ) // } +// // +//// @Test +//// fun findOneAndDelete() { +//// with(getCollectionInternal()) { +//// val sampleDoc = Document("hello", "world1").apply { this["num"] = 1 } +//// +//// // Collection should start out empty +//// // This also tests the null return format +//// assertNull(findOneAndDelete(Document()).get()) +//// +//// // Insert a sample Document +//// insertOne(sampleDoc).get() +//// assertEquals(1, count().get()) +//// +//// // Sample call to findOneAndDelete() where we delete the only doc in the collection +//// assertEquals(sampleDoc.withoutId(), +//// findOneAndDelete(Document()).get()!!.withoutId()) +//// +//// // There should be no documents in the collection now +//// assertEquals(0, count().get()) +//// +//// // Insert a sample Document +//// insertOne(sampleDoc).get() +//// assertEquals(1, count().get()) +//// +//// // Call findOneAndDelete() again but this time with a filter +//// assertEquals(sampleDoc.withoutId(), +//// findOneAndDelete(Document("hello", "world1")).get()!!.withoutId()) +//// +//// // There should be no documents in the collection now +//// assertEquals(0, count().get()) +//// +//// // Insert a sample Document +//// insertOne(sampleDoc).get() +//// assertEquals(1, count().get()) +//// +//// // Test null behaviour again with a filter that should not match any documents +//// assertNull(findOneAndDelete(Document("hello", "zzzzz")).get()) +//// assertEquals(1, count().get()) +//// +//// val doc2 = Document("hello", "world2").apply { this["num"] = 2 } +//// val doc3 = Document("hello", "world3").apply { this["num"] = 3 } +//// +//// // Insert new documents +//// insertMany(listOf(doc2, doc3)).get() +//// assertEquals(3, count().get()) +//// } +//// } +//// +//// @Test +//// fun findOneAndDelete_withProjectionAndSort() { +//// with(getCollectionInternal()) { +//// insertMany(listOf( +//// Document(mapOf(Pair("team", "Fearful Mallards"), Pair("score", 25000))), +//// Document(mapOf(Pair("team", "Tactful Mooses"), Pair("score", 23500))), +//// Document(mapOf(Pair("team", "Aquatic Ponies"), Pair("score", 19250))), +//// Document(mapOf(Pair("team", "Cuddly Zebras"), Pair("score", 15235))), +//// Document(mapOf(Pair("team", "Garrulous Bears"), Pair("score", 18000))) +//// )).get() +//// +//// assertEquals(5, count().get()) +//// assertNotNull(findOne(Document("team", "Cuddly Zebras"))) +//// +//// // Project: team, hide _id; Sort: score ascending +//// val project = Document(mapOf(Pair("_id", 0), Pair("team", 1))) +//// val sort = Document("score", 1) +//// +//// // This results in the deletion of Cuddly Zebras +//// val deletedDocument = findOneAndDelete( +//// Document("score", Document("\$lt", 22250)), +//// FindOneAndModifyOptions() +//// .projection(project) +//// .sort(sort) +//// ).get() +//// +//// assertEquals(4, count().get()) +//// assertEquals(Document("team", "Cuddly Zebras"), deletedDocument.withoutId()) +//// assertNull(findOne(Document("team", "Cuddly Zebras")).get()) +//// } +//// } // -// @Test -// fun findOneAndDelete_withProjectionAndSort() { -// with(getCollectionInternal()) { -// insertMany(listOf( -// Document(mapOf(Pair("team", "Fearful Mallards"), Pair("score", 25000))), -// Document(mapOf(Pair("team", "Tactful Mooses"), Pair("score", 23500))), -// Document(mapOf(Pair("team", "Aquatic Ponies"), Pair("score", 19250))), -// Document(mapOf(Pair("team", "Cuddly Zebras"), Pair("score", 15235))), -// Document(mapOf(Pair("team", "Garrulous Bears"), Pair("score", 18000))) -// )).get() -// -// assertEquals(5, count().get()) -// assertNotNull(findOne(Document("team", "Cuddly Zebras"))) -// -// // Project: team, hide _id; Sort: score ascending -// val project = Document(mapOf(Pair("_id", 0), Pair("team", 1))) -// val sort = Document("score", 1) -// -// // This results in the deletion of Cuddly Zebras -// val deletedDocument = findOneAndDelete( -// Document("score", Document("\$lt", 22250)), -// FindOneAndModifyOptions() -// .projection(project) -// .sort(sort) -// ).get() -// -// assertEquals(4, count().get()) -// assertEquals(Document("team", "Cuddly Zebras"), deletedDocument.withoutId()) -// assertNull(findOne(Document("team", "Cuddly Zebras")).get()) -// } -// } - } From 673b7b2ab38be525f507dce7cce790166439a666 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Wed, 6 Dec 2023 12:08:20 +0100 Subject: [PATCH 04/36] Clean up API experiments --- .../mongodb/internal/MongoCollectionImpl.kt | 123 +- .../kotlin/mongodb/mongo/MongoCollection.kt | 355 ++-- .../mongodb/common/mongo/MongoClientTest.kt | 1486 ++++++++--------- 3 files changed, 960 insertions(+), 1004 deletions(-) diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt index e8e541fcc9..a2b97c654a 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt @@ -17,10 +17,11 @@ package io.realm.kotlin.mongodb.internal import io.realm.kotlin.annotations.ExperimentalRealmSerializerApi +import io.realm.kotlin.mongodb.exceptions.ServiceException import io.realm.kotlin.mongodb.ext.call import io.realm.kotlin.mongodb.mongo.MongoCollection -import io.realm.kotlin.mongodb.mongo.insertMany import org.mongodb.kbson.BsonArray +import org.mongodb.kbson.BsonBoolean import org.mongodb.kbson.BsonDocument import org.mongodb.kbson.BsonInt64 import org.mongodb.kbson.BsonString @@ -32,7 +33,7 @@ import org.mongodb.kbson.serialization.encodeToBsonValue @OptIn(ExperimentalKBsonSerializerApi::class, ExperimentalRealmSerializerApi::class) @PublishedApi -internal class MongoCollectionImpl @OptIn(ExperimentalKBsonSerializerApi::class) constructor( +internal class MongoCollectionImpl constructor( @PublishedApi internal val database: MongoDatabaseImpl, override val name: String, val eJson: EJson, @@ -43,12 +44,16 @@ internal class MongoCollectionImpl @OptIn(ExperimentalKBsonSerializerApi:: val functions = user.functions - val defaults: Map = mapOf( + private val defaults: Map = mapOf( "database" to BsonString(database.name), "collection" to BsonString(name), ) - @ExperimentalRealmSerializerApi + @OptIn(ExperimentalKBsonSerializerApi::class) + override fun collection(eJson: EJson?): MongoCollection { + return MongoCollectionImpl(this.database, this.name, eJson ?: this.eJson) + } + private suspend inline fun call(name: String, crossinline document: MutableMap.()-> Unit): R { return user.functions.call(name) { serviceName(client.serviceName) @@ -95,16 +100,118 @@ internal class MongoCollectionImpl @OptIn(ExperimentalKBsonSerializerApi:: internal suspend fun insertMany(documents: List): List = call("insertMany") { put("documents", BsonArray(documents)) - }.asDocument().get("insertedIds")!!.asArray().toList() + }.asDocument()["insertedIds"]!!.asArray().toList() + + @PublishedApi + internal suspend fun deleteOne(filter: BsonDocument): Boolean { + val deletedCountBson = call("deleteOne") { + put("query", filter) + }.asDocument()["deletedCount"]!! + val deletedCount = decodeFromBsonValue(deletedCountBson) + return when (deletedCount) { + 0L -> false + 1L -> true + else -> throw ServiceException("Unexpected response from deleteOne: deletedCount=${deletedCount}") + } + } + + @PublishedApi + internal suspend fun deleteMany(filter: BsonDocument): Long { + val deletedCountBson = call("deleteMany") { + put("query", filter) + }.asDocument()["deletedCount"]!! + return decodeFromBsonValue(deletedCountBson) + } + + @PublishedApi + internal suspend fun updateOne( filter: BsonDocument, update: BsonDocument, upsert: Boolean = false ): BsonValue { + val deletedCountBson = call("updateOne") { + put("query", filter) + put("update", update) + put("upsert", BsonBoolean(upsert)) + } + TODO() + return decodeFromBsonValue(deletedCountBson) + } + + @PublishedApi + internal suspend fun updateMany( filter: BsonDocument, update: BsonDocument, upsert: Boolean = false ): BsonValue { + val deletedCountBson = call("updateMany") { + put("query", filter) + put("update", update) + put("upsert", BsonBoolean(upsert)) + } + TODO() + return decodeFromBsonValue(deletedCountBson) + } + + @PublishedApi + internal suspend fun findOneAndUpdate( + filter: BsonDocument, + update: BsonDocument, + projection: BsonDocument? = null, + sort: BsonDocument? = null, + upsert: Boolean = false, + returnNewDoc: Boolean = false, + ): BsonValue = call("findOneAndUpdate") { + put("filter", filter) + put("update", update) + projection?.let { put("projection", projection)} + sort?.let { put("sort", sort)} + put("upsert", BsonBoolean(upsert)) + put("returnNewDoc", BsonBoolean(returnNewDoc)) + } + + @PublishedApi + internal suspend fun findOneAndReplace( + filter: BsonDocument, + update: BsonDocument, + projection: BsonDocument? = null, + sort: BsonDocument? = null, + upsert: Boolean = false, + returnNewDoc: Boolean = false, + ): BsonValue = call("findOneAndReplace") { + put("filter", filter) + put("update", update) + projection?.let { put("projection", projection)} + sort?.let { put("sort", sort)} + put("upsert", BsonBoolean(upsert)) + put("returnNewDoc", BsonBoolean(returnNewDoc)) + } + + @PublishedApi + internal suspend fun findOneAndDelete( + filter: BsonDocument, + projection: BsonDocument? = null, + sort: BsonDocument? = null, + ): BsonValue = call("findOneAndDelete") { + put("filter", filter) + projection?.let { put("projection", projection)} + sort?.let { put("sort", sort)} + } } +@OptIn(ExperimentalKBsonSerializerApi::class) +@PublishedApi +internal inline fun MongoCollectionImpl<*, *>.encodeToBsonValue(value: R): BsonValue { + return eJson.encodeToBsonValue(value) +} @OptIn(ExperimentalKBsonSerializerApi::class) @PublishedApi internal inline fun MongoCollectionImpl<*, *>.decodeFromBsonValue(bsonValue: BsonValue): R { - return eJson.decodeFromBsonValue(bsonValue) + return if (bsonValue is R) { + bsonValue + } else { + eJson.decodeFromBsonValue(bsonValue) + } } + @OptIn(ExperimentalKBsonSerializerApi::class) @PublishedApi -internal inline fun MongoCollectionImpl<*, *>.encodeToBsonValue(value: R): BsonValue { - return eJson.encodeToBsonValue(value) +internal inline fun MongoCollectionImpl<*, *>.decodeFromBsonValueList(bsonValues: List): List { + return if (R::class == BsonValue::class) { + bsonValues as List + } else { + bsonValues.map { eJson.decodeFromBsonValue(it) } + } } diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt index 6109d5d5be..aac5d72af7 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt @@ -21,13 +21,13 @@ package io.realm.kotlin.mongodb.mongo import io.realm.kotlin.internal.util.Validation import io.realm.kotlin.mongodb.internal.MongoCollectionImpl +import io.realm.kotlin.mongodb.internal.encodeToBsonValue import io.realm.kotlin.mongodb.internal.decodeFromBsonValue +import io.realm.kotlin.mongodb.internal.decodeFromBsonValueList import org.mongodb.kbson.BsonDocument import org.mongodb.kbson.BsonValue import org.mongodb.kbson.ExperimentalKBsonSerializerApi import org.mongodb.kbson.serialization.EJson -import org.mongodb.kbson.serialization.decodeFromBsonValue -import org.mongodb.kbson.serialization.encodeToBsonValue import kotlin.jvm.JvmName public interface MongoCollection { @@ -35,18 +35,17 @@ public interface MongoCollection { public val name: String @OptIn(ExperimentalKBsonSerializerApi::class) - public fun typedCollection(eJson: EJson? = null): MongoCollection { - val source = this as MongoCollectionImpl<*, *> - return MongoCollectionImpl(source.database, source.name, eJson ?: source.eJson) - } + public fun collection(eJson: EJson? = null): MongoCollection } -public suspend inline fun MongoCollection<*, *>.count(filter: BsonDocument? = null, limit: Long? = null): Long { - return (this as MongoCollectionImpl).count(filter, limit) +public suspend fun MongoCollection<*, *>.count(filter: BsonDocument? = null, limit: Long? = null): Long { + Validation.isType>(this, "MongoCollection should be an instance of MongoCollectionImpl") + return count(filter, limit) } -public suspend inline fun MongoCollection.findOne(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null): T { - return (this as MongoCollectionImpl).decodeFromBsonValue(findOne(filter, projection, sort)) +public suspend inline fun < reified T, R: Any> MongoCollection.findOne(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null): T { + Validation.isType>(this, "MongoCollection should be an instance of MongoCollectionImpl") + return decodeFromBsonValue(findOne(filter, projection, sort)) } @JvmName("findOneTyped") @@ -54,22 +53,20 @@ public suspend inline fun MongoCollection<*, *>.findOne(filter: Bson return (this as MongoCollection).findOne(filter, projection, sort) } -@OptIn(ExperimentalKBsonSerializerApi::class) -public suspend inline fun MongoCollection.find(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null, limit: Long? = null): List { - Validation.isType>(this, "") +public suspend inline fun MongoCollection.find(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null, limit: Long? = null): List { + Validation.isType>(this, "MongoCollection should be an instance of MongoCollectionImpl") val objects = find(filter, projection, sort, limit).asArray().toList() - return if (T::class == BsonValue::class) { objects as List } else {objects.map {EJson.decodeFromBsonValue(it) } } + return decodeFromBsonValueList(objects) } @JvmName("findTyped") public suspend inline fun MongoCollection<*, *>.find(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null, limit: Long? = null ): List { return (this as MongoCollection).find(filter, projection, sort, limit) } -@OptIn(ExperimentalKBsonSerializerApi::class) public suspend inline fun MongoCollection.aggregate(pipeline: List): List { - Validation.isType>(this, "") - val objects = aggregate(pipeline) - return if (T::class == BsonValue::class) { objects as List } else { objects.map { EJson.decodeFromBsonValue(it) } } + Validation.isType>(this, "MongoCollection should be an instance of MongoCollectionImpl") + val objects: List = aggregate(pipeline) + return decodeFromBsonValueList(objects) } @JvmName("aggregateTyped") @@ -77,12 +74,11 @@ public suspend inline fun MongoCollection<*, *>.aggregate(pipeline: return (this as MongoCollection).aggregate(pipeline) } -@OptIn(ExperimentalKBsonSerializerApi::class) public suspend inline fun MongoCollection.insertOne(document: T): R { - Validation.isType>(this, "") - val encodedDocument: BsonDocument = eJson.encodeToBsonValue(document).asDocument() + Validation.isType>(this, "MongoCollection should be an instance of MongoCollectionImpl") + val encodedDocument: BsonDocument = encodeToBsonValue(document).asDocument() val insertedId = insertOne(encodedDocument) - return if (insertedId is R) { insertedId } else { EJson.decodeFromBsonValue(insertedId) } + return if (insertedId is R) { insertedId } else { decodeFromBsonValue(insertedId) } } @JvmName("insertOneTyped") @@ -90,18 +86,16 @@ public suspend inline fun MongoCollection<*, return (this as MongoCollection).insertOne(document) } - -@OptIn(ExperimentalKBsonSerializerApi::class) public suspend inline fun MongoCollection.insertMany( documents: Collection, ): List { - Validation.isType>(this, "") - val encodedDocuments: List = documents.map { eJson.encodeToBsonValue(it).asDocument() } + Validation.isType>(this, "MongoCollection should be an instance of MongoCollectionImpl") + val encodedDocuments: List = documents.map { encodeToBsonValue(it).asDocument() } val insertedIds: List = insertMany(encodedDocuments) return if (R::class == BsonValue::class) { insertedIds as List } else { - insertedIds.map { (this as MongoCollectionImpl).decodeFromBsonValue(it) } + insertedIds.map { decodeFromBsonValue(it) } } } @@ -110,205 +104,116 @@ public suspend inline fun MongoCollection<*, * return (this as MongoCollection).insertMany(documents) } -//@OptIn(ExperimentalKBsonSerializerApi::class) -//public suspend inline fun MongoCollection<*, *>.deleteOne(filter: BsonDocument): Boolean { -//// val encodedDocument: BsonValue = EJson.encodeToBsonValue(EJson.serializersModule.serializerOrRealmBuiltInSerializer(), documents) -// val insertedId: BsonValue = (this as BsonMongoCollection).call("deleteOne") { -// put("query", filter) -// }.asDocument().get("deletedCount")!! -// val decodeFromBsonValue = EJson.decodeFromBsonValue(insertedId) -// return when(decodeFromBsonValue) { -// 0L -> false -// 1L -> true -// else -> TODO("Unexpected $decodeFromBsonValue") -// } -//} -// -// -//// deleteMany(filter): Count -//// query -//@ExperimentalRealmSerializerApi -//@OptIn(ExperimentalKBsonSerializerApi::class) -//public suspend inline fun MongoCollection<*, *>.deleteMany(filter: BsonDocument): Long { -// val insertedId: BsonValue = (this as BsonMongoCollection).call("deleteMany") { -// put("query", filter) -// }.asDocument().get("deletedCount")!! -// return EJson.decodeFromBsonValue(insertedId) -//} -// -//// updateOne(filter, updateDoc, updateOptions(upsert: Boolean)): UpdateResult(matchCount, modifiedCount, upsertedId) -//// updateMany(filter, updateDoc, updateOptions(upsert: Boolean)): UpdateResult(matchCount, modifiedCount, upsertedId) -//// query -//// update : BsonDocument -//// upsert: Boolean -// -//// FIXME Would we also allow filter and update to be serializables? -//// FIXME Could just return Boolean, since matchedCount=1,modifiedCount=1 even if multiple documents should be matching :thinking: -//// FIXME Should we split into upsertOne, since response only contains 'upsertedId' if call has 'upsert:true` -//@ExperimentalRealmSerializerApi -//@OptIn(ExperimentalKBsonSerializerApi::class) -//public suspend inline fun MongoCollection<*, *>.updateOne( -// filter: BsonDocument, -// update: BsonDocument, -// upsert: Boolean = false -//): R { -// val insertedId: BsonValue = (this as BsonMongoCollection).call("updateOne") { -// put("query", filter) -// put("update", update) -// put("upsert", BsonBoolean(upsert)) -// }.asDocument()//.get("insertedId")!!.asArray().toList() -// // {"matchedCount":{"$numberInt":"0"},"modifiedCount":{"$numberInt":"0"}} -// -// return if (R::class == BsonValue::class) { insertedId as R } else { EJson.decodeFromBsonValue(insertedId) } -//} -//@ExperimentalRealmSerializerApi -//@OptIn(ExperimentalKBsonSerializerApi::class) -//public suspend inline fun MongoCollection<*, *>.updateMany( -// filter: BsonDocument, -// update: BsonDocument, -// upsert: Boolean = false -//): R { -// val insertedId: BsonValue = (this as BsonMongoCollection).call("updateMany") { -// put("query", filter) -// put("update", update) -// put("upsert", BsonBoolean(upsert)) -// }.asDocument().get("upsertedId")!! -// // {"matchedCount":{"$numberInt":"0"},"modifiedCount":{"$numberInt":"0"}} -// -// return if (R::class == BsonValue::class) { insertedId as R } else { EJson.decodeFromBsonValue(insertedId) } -//} -// -//@ExperimentalRealmSerializerApi -//@OptIn(ExperimentalKBsonSerializerApi::class) -//public suspend inline fun MongoCollection<*, *>.findOneAndUpdate( -// filter: BsonDocument, -// update: BsonDocument, -// projection: BsonDocument? = null, -// sort: BsonDocument? = null, -// upsert: Boolean = false, -// returnNewDoc: Boolean = false, -//): R { -// val updatedDocument: BsonValue = (this as BsonMongoCollection).call("findOneAndUpdate") { -// put("filter", filter) -// put("update", update) -// projection?.let { put("projection", projection)} -// sort?.let { put("sort", sort)} -// put("upsert", BsonBoolean(upsert)) -// put("returnNewDoc", BsonBoolean(returnNewDoc)) -// } -// return if (R::class == BsonValue::class) { -// updatedDocument as R -// } else { -// EJson.decodeFromBsonValue(updatedDocument) -// } -//} -// -//@ExperimentalRealmSerializerApi -//@OptIn(ExperimentalKBsonSerializerApi::class) -//public suspend inline fun MongoCollection<*, *>.findOneAndReplace( -// filter: BsonDocument, -// update: BsonDocument, -// projection: BsonDocument? = null, -// sort: BsonDocument? = null, -// upsert: Boolean = false, -// returnNewDoc: Boolean = false, -//): R { -// // If returnNewDoc==true then the returned document is after the update otherwise it is from -// // before the update -// val updatedDocument: BsonValue = (this as BsonMongoCollection).call("findOneAndReplace") { -// put("filter", filter) -// put("update", update) -// projection?.let { put("projection", projection)} -// sort?.let { put("sort", sort)} -// put("upsert", BsonBoolean(upsert)) -// put("returnNewDoc", BsonBoolean(returnNewDoc)) -// } -// return if (R::class == BsonValue::class) { -// updatedDocument as R -// } else { -// EJson.decodeFromBsonValue(updatedDocument) -// } -//} -// -//@ExperimentalRealmSerializerApi -//@OptIn(ExperimentalKBsonSerializerApi::class) -//public suspend inline fun MongoCollection<*, *>.findOneAndDelete( -// filter: BsonDocument, -// projection: BsonDocument? = null, -// sort: BsonDocument? = null, -//): R { -// val deletedDocument: BsonValue = (this as BsonMongoCollection).call("findOneAndDelete") { -// put("filter", filter) -// projection?.let { put("projection", projection)} -// sort?.let { put("sort", sort)} -// } -// return if (R::class == BsonValue::class) { -// deletedDocument as R -// } else { -// EJson.decodeFromBsonValue(deletedDocument) -// } -//} +public suspend fun MongoCollection<*, *>.deleteOne(filter: BsonDocument): Boolean { + Validation.isType>(this, "MongoCollection should be an instance of MongoCollectionImpl") + return deleteOne(filter) +} + +public suspend fun MongoCollection<*, *>.deleteMany(filter: BsonDocument): Long { + Validation.isType>(this, "MongoCollection should be an instance of MongoCollectionImpl") + return deleteMany(filter) +} + +// FIXME Could just return Boolean, since matchedCount=1,modifiedCount=1 even if multiple documents should be matching :thinking: +// FIXME Should we split into upsertOne, since response only contains 'upsertedId' if call has 'upsert:true` +public suspend inline fun MongoCollection.updateOne( + filter: BsonDocument, + update: BsonDocument, + upsert: Boolean = false +): List { + Validation.isType>(this, "MongoCollection should be an instance of MongoCollectionImpl") + return decodeFromBsonValue(updateOne(filter, update, upsert)) +} + +@JvmName("updateOneTyped") +public suspend inline fun MongoCollection<*, *>.updateOne( + filter: BsonDocument, + update: BsonDocument, + upsert: Boolean = false +): List { + return (this as MongoCollection).updateOne(filter, update, upsert) +} + +public suspend inline fun MongoCollection.updateMany( + filter: BsonDocument, + update: BsonDocument, + upsert: Boolean = false +): List { + Validation.isType>(this, "MongoCollection should be an instance of MongoCollectionImpl") + return decodeFromBsonValue(updateMany(filter, update, upsert)) +} + +@JvmName("updateManyTyped") +public suspend inline fun MongoCollection<*, *>.updateMany( + filter: BsonDocument, + update: BsonDocument, + upsert: Boolean = false + ): List { + return (this as MongoCollection).updateMany(filter, update, upsert) +} + +public suspend inline fun MongoCollection.findOneAndUpdate( + filter: BsonDocument, + update: BsonDocument, + projection: BsonDocument? = null, + sort: BsonDocument? = null, + upsert: Boolean = false, + returnNewDoc: Boolean = false, +): T { + Validation.isType>(this, "MongoCollection should be an instance of MongoCollectionImpl") + return decodeFromBsonValue(findOneAndUpdate(filter, update, projection, sort, upsert, returnNewDoc)) +} -// -// -//public class ClientOption { -// -//} -// -//@PublishedApi -//internal interface FilterOptionInternal { -// var filter: BsonDocument? -//} -//@PublishedApi -//internal interface LimitOptionInternal { -// var limit: Long? -//} -// -//public interface LimitOption { } -//public fun LimitOption.limit(limit: Long) { -// (this as LimitOptionInternal).limit = limit -//} -//public interface FilterOption { } -//public fun FilterOption.filter(json: String) { -// (this as FilterOptionInternal).filter = BsonDocument(json) -//} -//@ExperimentalKBsonSerializerApi -//public inline fun FilterOption.filter(argument: T) { -// filter(argument, EJson.serializersModule.serializerOrRealmBuiltInSerializer()) -//} -//@OptIn(ExperimentalKBsonSerializerApi::class) -//public inline fun FilterOption.filter(argument: T, serializer: KSerializer) { -// (this as FilterOptionInternal).filter = EJson.encodeToBsonValue(serializer, argument).asDocument() -//} -//public interface ProjectionOption -//public interface SortOption -// -//public interface CountOptions : LimitOption, FilterOption -//public interface FindOneOptions : LimitOption, FilterOption, SortOption, ProjectionOption -// -//@PublishedApi -//internal class FindOptionsInternal: FindOneOptions, FilterOptionInternal, LimitOptionInternal { -// override var filter: BsonDocument? = null -// override var limit: Long? = null -//} +@JvmName("findAndUpdateTyped") +public suspend inline fun MongoCollection<*, *>.findOneAndUpdate( + filter: BsonDocument, + update: BsonDocument, + projection: BsonDocument? = null, + sort: BsonDocument? = null, + upsert: Boolean = false, + returnNewDoc: Boolean = false, +): T { + return (this as MongoCollection).findOneAndUpdate(filter, update, projection, sort, upsert, returnNewDoc) +} + +public suspend inline fun MongoCollection.findOneAndReplace( + filter: BsonDocument, + update: BsonDocument, + projection: BsonDocument? = null, + sort: BsonDocument? = null, + upsert: Boolean = false, + returnNewDoc: Boolean = false, +): T { + Validation.isType>(this, "MongoCollection should be an instance of MongoCollectionImpl") + return decodeFromBsonValue(findOneAndReplace(filter, update, projection, sort, upsert, returnNewDoc)) +} -//public suspend inline fun findOne(filter: Bson = {}, options: FindOptions(limit, projection, sort)): T -// query -// findoptions -// limit -// projection -// sort +@JvmName("findAndReplaceTyped") +public suspend inline fun MongoCollection<*, *>.findOneAndReplace( + filter: BsonDocument, + update: BsonDocument, + projection: BsonDocument? = null, + sort: BsonDocument? = null, + upsert: Boolean = false, + returnNewDoc: Boolean = false, +): T { + return (this as MongoCollection).findOneAndReplace(filter, update, projection, sort, upsert, returnNewDoc) +} -//@OptIn(ExperimentalKBsonSerializerApi::class) -//@ExperimentalRealmSerializerApi -//public suspend inline fun MongoCollection.findOne(configuration: FindOneOptions.() -> Unit): T { -// val options = FindOptionsInternal() -// configuration(options) -// val response = (this as BsonMongoCollection).call("findOne") { -// options.filter?.let { put("query", it) } -//// options.projection?.let { put("projection", it) } -//// options.sort?.let { put("sort", it) } -// } -// return EJson.decodeFromBsonValue(response) -//} +public suspend inline fun MongoCollection.findOneAndDelete( + filter: BsonDocument, + projection: BsonDocument? = null, + sort: BsonDocument? = null, +): T { + Validation.isType>(this, "MongoCollection should be an instance of MongoCollectionImpl") + return decodeFromBsonValue(findOneAndDelete(filter, projection, sort)) +} +@JvmName("findAndDeleteTyped") +public suspend inline fun MongoCollection<*, *>.findOneAndDelete( + filter: BsonDocument, + projection: BsonDocument? = null, + sort: BsonDocument? = null, +): T { + return (this as MongoCollection).findOneAndDelete(filter, projection, sort) +} diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTest.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTest.kt index 154fd791ab..ece64fcf7a 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTest.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTest.kt @@ -29,17 +29,17 @@ import io.realm.kotlin.mongodb.mongo.MongoCollection import io.realm.kotlin.mongodb.mongo.MongoDatabase import io.realm.kotlin.mongodb.mongo.aggregate import io.realm.kotlin.mongodb.mongo.count -//import io.realm.kotlin.mongodb.mongo.deleteMany -//import io.realm.kotlin.mongodb.mongo.deleteOne +import io.realm.kotlin.mongodb.mongo.deleteMany +import io.realm.kotlin.mongodb.mongo.deleteOne import io.realm.kotlin.mongodb.mongo.find import io.realm.kotlin.mongodb.mongo.findOne -//import io.realm.kotlin.mongodb.mongo.findOneAndDelete -//import io.realm.kotlin.mongodb.mongo.findOneAndReplace -//import io.realm.kotlin.mongodb.mongo.findOneAndUpdate +import io.realm.kotlin.mongodb.mongo.findOneAndDelete +import io.realm.kotlin.mongodb.mongo.findOneAndReplace +import io.realm.kotlin.mongodb.mongo.findOneAndUpdate import io.realm.kotlin.mongodb.mongo.insertMany import io.realm.kotlin.mongodb.mongo.insertOne -//import io.realm.kotlin.mongodb.mongo.updateMany -//import io.realm.kotlin.mongodb.mongo.updateOne +import io.realm.kotlin.mongodb.mongo.updateMany +import io.realm.kotlin.mongodb.mongo.updateOne import io.realm.kotlin.test.mongodb.TestApp import io.realm.kotlin.test.mongodb.asTestApp import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage @@ -478,6 +478,7 @@ class MongoClientTest { // assertEquals(2, count().get()) // } + RealmLog.level = LogLevel.ALL // Option 1 - Typed return value - BsonValue val insertedIdDocument: BsonValue = collection.insertOne(BsonDocument("name", "sadffds")) val insertedIdDocument2: BsonValue = collection.insertOne(SyncDog("sadf")) @@ -546,7 +547,7 @@ class MongoClientTest { println(x) println(y) - val typedCollection = collection.typedCollection() + val typedCollection = collection.collection() val z: List = typedCollection.insertMany(listOf(SyncDog("sadf"))) val tyz = typedCollection.insertMany(listOf(SyncDog("sadf"))) @@ -667,735 +668,734 @@ class MongoClientTest { // } // + @Test + fun deleteOne() = runBlocking { + // Argument wrapper DSL + RealmLog.level = LogLevel.ALL + assertFalse { collection.deleteOne(BsonDocument()) } + + // A bit ann + val elements = SyncDog("x") + assertEquals(2, collection.insertMany(listOf(elements, elements)).size) + assertTrue { collection.deleteOne(BsonDocument("""{ "name": "x" }""")) } + } + // @Test -// fun deleteOne() = runBlocking { -// // Argument wrapper DSL -// RealmLog.level = LogLevel.ALL -// assertFalse { collection.deleteOne(BsonDocument()) } -// -// // A bit ann -// val elements = SyncDog("x") -// assertEquals(2, collection.insertMany(listOf(elements, elements)).size) -// assertTrue { collection.deleteOne(BsonDocument("""{ "name": "x" }""")) } +// fun deleteOne_singleDocument() { +// with(getCollectionInternal()) { +// assertEquals(0, deleteOne(Document()).get()!!.deletedCount) +// assertEquals(0, deleteOne(Document("hello", "world")).get()!!.deletedCount) +// +// val doc1 = Document("hello", "world") +// +// insertOne(doc1).get() +// assertEquals(1, deleteOne(doc1).get()!!.deletedCount) +// assertEquals(0, count().get()) +// } // } // -// // @Test -//// fun deleteOne_singleDocument() { -//// with(getCollectionInternal()) { -//// assertEquals(0, deleteOne(Document()).get()!!.deletedCount) -//// assertEquals(0, deleteOne(Document("hello", "world")).get()!!.deletedCount) -//// -//// val doc1 = Document("hello", "world") -//// -//// insertOne(doc1).get() -//// assertEquals(1, deleteOne(doc1).get()!!.deletedCount) -//// assertEquals(0, count().get()) -//// } -//// } -//// -//// @Test -//// fun deleteOne_fails() { -//// with(getCollectionInternal()) { -//// assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { -//// deleteOne(Document("\$who", 1)).get() -//// }.also { e -> -//// assertTrue(e.errorMessage!!.contains("operator", true)) -//// } -//// } -//// } -//// -//// @Test -//// fun deleteOne_multipleDocuments() { -//// with(getCollectionInternal()) { -//// assertEquals(0, count().get()) -//// -//// val rawDoc = Document("hello", "world") -//// val doc1 = Document(rawDoc) -//// val doc1b = Document(rawDoc) -//// val doc2 = Document("foo", "bar") -//// val doc3 = Document("42", "666") -//// insertMany(listOf(doc1, doc1b, doc2, doc3)).get() -//// assertEquals(1, deleteOne(rawDoc).get()!!.deletedCount) -//// assertEquals(1, deleteOne(Document()).get()!!.deletedCount) -//// assertEquals(2, count().get()) -//// } -//// } -//// // @Test -// fun deleteMany() = runBlocking { -// // Argument wrapper DSL -// RealmLog.level = LogLevel.ALL -// assertEquals(0, collection.deleteMany(BsonDocument())) -// -// // A bit ann -// val elements = SyncDog("x") -// assertEquals(2, collection.insertMany(listOf(elements, elements)).size) -// assertEquals(2, collection.deleteMany(BsonDocument("""{ "name": "x" }"""))) -// -// assertEquals( -// 3, -// collection.insertMany(listOf(elements, elements, elements)).size -// ) -// assertEquals(3, collection.deleteMany(BsonDocument())) +// fun deleteOne_fails() { +// with(getCollectionInternal()) { +// assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { +// deleteOne(Document("\$who", 1)).get() +// }.also { e -> +// assertTrue(e.errorMessage!!.contains("operator", true)) +// } +// } // } // -// // @Test -//// fun deleteMany_singleDocument() { -//// with(getCollectionInternal()) { -//// assertEquals(0, count().get()) -//// -//// val rawDoc = Document("hello", "world") -//// val doc1 = Document(rawDoc) -//// -//// insertOne(doc1).get() -//// assertEquals(1, count().get()) -//// assertEquals(1, deleteMany(doc1).get()!!.deletedCount) -//// assertEquals(0, count().get()) -//// } -//// } -//// -//// @Test -//// fun deleteMany_multipleDocuments() { -//// with(getCollectionInternal()) { -//// assertEquals(0, count().get()) -//// -//// val rawDoc = Document("hello", "world") -//// val doc1 = Document(rawDoc) -//// val doc1b = Document(rawDoc) -//// val doc2 = Document("foo", "bar") -//// val doc3 = Document("42", "666") -//// insertMany(listOf(doc1, doc1b, doc2, doc3)).get() -//// assertEquals(2, deleteMany(rawDoc).get()!!.deletedCount) // two docs will be deleted -//// assertEquals(2, count().get()) // two docs still present -//// assertEquals(2, deleteMany(Document()).get()!!.deletedCount) // delete all -//// assertEquals(0, count().get()) -//// -//// insertMany(listOf(doc1, doc1b, doc2, doc3)).get() -//// assertEquals(4, deleteMany(Document()).get()!!.deletedCount) // delete all -//// assertEquals(0, count().get()) -//// } -//// } -//// -//// @Test -//// fun deleteMany_fails() { -//// with(getCollectionInternal()) { -//// assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { -//// deleteMany(Document("\$who", 1)).get() -//// }.also { e -> -//// assertTrue(e.errorMessage!!.contains("operator", true)) -//// } -//// } -//// } -//// // @Test -// fun updateOne() = runBlocking { -// // Argument wrapper DSL -// RealmLog.level = LogLevel.ALL -// -// val elements = SyncDog("x") -// assertEquals(2, collection.insertMany(listOf(elements, elements)).size) -// assertEquals(2, collection.count(filter = BsonDocument("""{"name": "x"}"""))) -// -// // A bit ann -// collection.updateOne( -// BsonDocument("""{ "name": "x"}"""), -// BsonDocument("""{ "name": "y"}"""), -// true -// ) -//// assertEquals(2, collection.deleteMany(BsonDocument("""{ "name": "x" }""" )) ) -// -//// assertEquals(3, collection.insertMany(listOf(elements, elements, elements)).size) -//// assertEquals(3, collection.deleteMany(BsonDocument()) ) -// -// collection.updateOne( -// BsonDocument("""{ "name": "z"}"""), -// BsonDocument(""" { "name": "y"}"""), -// upsert = true -// ) +// fun deleteOne_multipleDocuments() { +// with(getCollectionInternal()) { +// assertEquals(0, count().get()) +// +// val rawDoc = Document("hello", "world") +// val doc1 = Document(rawDoc) +// val doc1b = Document(rawDoc) +// val doc2 = Document("foo", "bar") +// val doc3 = Document("42", "666") +// insertMany(listOf(doc1, doc1b, doc2, doc3)).get() +// assertEquals(1, deleteOne(rawDoc).get()!!.deletedCount) +// assertEquals(1, deleteOne(Document()).get()!!.deletedCount) +// assertEquals(2, count().get()) +// } // } // -// // @Test -//// fun updateOne_emptyCollection() { -//// with(getCollectionInternal()) { -//// val doc1 = Document("hello", "world") -//// -//// // Update on an empty collection -//// updateOne(Document(), doc1) -//// .get()!! -//// .let { -//// assertEquals(0, it.matchedCount) -//// assertEquals(0, it.modifiedCount) -//// assertNull(it.upsertedId) -//// } -//// -//// // Update on an empty collection adding some values -//// val doc2 = Document("\$set", Document("woof", "meow")) -//// updateOne(Document(), doc2) -//// .get()!! -//// .let { -//// assertEquals(0, it.matchedCount) -//// assertEquals(0, it.modifiedCount) -//// assertNull(it.upsertedId) -//// assertEquals(0, count().get()) -//// } -//// } -//// } -//// -//// @Test -//// fun updateOne_emptyCollectionWithUpsert() { -//// with(getCollectionInternal()) { -//// val doc1 = Document("hello", "world") -//// -//// // Update on empty collection with upsert -//// val options = UpdateOptions().upsert(true) -//// updateOne(Document(), doc1, options) -//// .get()!! -//// .let { -//// assertEquals(0, it.matchedCount) -//// assertEquals(0, it.modifiedCount) -//// assertFalse(it.upsertedId!!.isNull) -//// } -//// assertEquals(1, count().get()) -//// -//// assertEquals(doc1, find(Document()).first().get()!!.withoutId()) -//// } -//// } -//// -//// @Test -//// fun updateOne_fails() { -//// with(getCollectionInternal()) { -//// assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { -//// updateOne(Document("\$who", 1), Document()).get() -//// }.also { e -> -//// assertTrue(e.errorMessage!!.contains("operator", true)) -//// } -//// } -//// } -//// + @Test + fun deleteMany() = runBlocking { + // Argument wrapper DSL + RealmLog.level = LogLevel.ALL + assertEquals(0, collection.deleteMany(BsonDocument())) + + // A bit ann + val elements = SyncDog("x") + assertEquals(2, collection.insertMany(listOf(elements, elements)).size) + assertEquals(2, collection.deleteMany(BsonDocument("""{ "name": "x" }"""))) + + assertEquals( + 3, + collection.insertMany(listOf(elements, elements, elements)).size + ) + assertEquals(3, collection.deleteMany(BsonDocument())) + } + // @Test -// fun updateMany() = runBlocking { -// RealmLog.level = LogLevel.ALL -// assertEquals( -// 4, -// collection.insertMany( -// listOf( -// SyncDog("x"), -// SyncDog("x"), -// SyncDog("y"), -// SyncDog("z") -// ) -// ).size -// ) -// assertEquals(2, collection.count(filter = BsonDocument("""{"name": "x"}"""))) -// collection.updateMany( -// BsonDocument("""{"name": "x2"}"""), -// BsonDocument("""{"name": "x1"}"""), -// true -// ) +// fun deleteMany_singleDocument() { +// with(getCollectionInternal()) { +// assertEquals(0, count().get()) +// +// val rawDoc = Document("hello", "world") +// val doc1 = Document(rawDoc) +// +// insertOne(doc1).get() +// assertEquals(1, count().get()) +// assertEquals(1, deleteMany(doc1).get()!!.deletedCount) +// assertEquals(0, count().get()) +// } // } // -// // @Test -//// fun updateMany_emptyCollection() { -//// with(getCollectionInternal()) { -//// val doc1 = Document("hello", "world") -//// -//// // Update on empty collection -//// updateMany(Document(), doc1) -//// .get()!! -//// .let { -//// assertEquals(0, it.matchedCount) -//// assertEquals(0, it.modifiedCount) -//// assertNull(it.upsertedId) -//// } -//// assertEquals(0, count().get()) -//// } -//// } -//// -//// @Test -//// fun updateMany_emptyCollectionWithUpsert() { -//// with(getCollectionInternal()) { -//// val doc1 = Document("hello", "world") -//// -//// // Update on empty collection with upsert -//// updateMany(Document(), doc1, UpdateOptions().upsert(true)) -//// .get()!! -//// .let { -//// assertEquals(0, it.matchedCount) -//// assertEquals(0, it.modifiedCount) -//// assertNotNull(it.upsertedId) -//// } -//// assertEquals(1, count().get()) -//// -//// // Add new value using update -//// val update = Document("woof", "meow") -//// updateMany(Document(), Document("\$set", update)) -//// .get()!! -//// .let { -//// assertEquals(1, it.matchedCount) -//// assertEquals(1, it.modifiedCount) -//// assertNull(it.upsertedId) -//// } -//// assertEquals(1, count().get()) -//// val expected = Document(doc1).apply { this["woof"] = "meow" } -//// assertEquals(expected, find().first().get()!!.withoutId()) -//// -//// // Insert empty document, add ["woof", "meow"] to it and check it worked -//// insertOne(Document()).get() -//// updateMany(Document(), Document("\$set", update)) -//// .get()!! -//// .let { -//// assertEquals(2, it.matchedCount) -//// assertEquals(2, it.modifiedCount) -//// } -//// assertEquals(2, count().get()) -//// find().iterator() -//// .get()!! -//// .let { -//// assertEquals(expected, it.next().withoutId()) -//// assertEquals(update, it.next().withoutId()) -//// assertFalse(it.hasNext()) -//// } -//// } -//// } -//// -//// @Test -//// fun updateMany_fails() { -//// with(getCollectionInternal()) { -//// assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { -//// updateMany(Document("\$who", 1), Document()).get() -//// }.also { e -> -//// assertTrue(e.errorMessage!!.contains("operator", true)) -//// } -//// } -//// } -//// // @Test -// fun findOneAndUpdate() = runBlocking { -// RealmLog.level = LogLevel.ALL -// assertNull(collection.findOneAndUpdate(BsonDocument(), BsonDocument())) -// collection.insertMany( -// listOf( -// SyncDog("dog1"), -// SyncDog("dog1"), -// SyncDog("dog2") -// ) -// ) -// collection.findOneAndUpdate( -// BsonDocument(), -// BsonDocument("""{ "name": "dog1" }"""), -// upsert = true -// ) +// fun deleteMany_multipleDocuments() { +// with(getCollectionInternal()) { +// assertEquals(0, count().get()) +// +// val rawDoc = Document("hello", "world") +// val doc1 = Document(rawDoc) +// val doc1b = Document(rawDoc) +// val doc2 = Document("foo", "bar") +// val doc3 = Document("42", "666") +// insertMany(listOf(doc1, doc1b, doc2, doc3)).get() +// assertEquals(2, deleteMany(rawDoc).get()!!.deletedCount) // two docs will be deleted +// assertEquals(2, count().get()) // two docs still present +// assertEquals(2, deleteMany(Document()).get()!!.deletedCount) // delete all +// assertEquals(0, count().get()) +// +// insertMany(listOf(doc1, doc1b, doc2, doc3)).get() +// assertEquals(4, deleteMany(Document()).get()!!.deletedCount) // delete all +// assertEquals(0, count().get()) +// } // } -//// @Test -//// fun findOneAndUpdate_emptyCollection() { -//// with(getCollectionInternal()) { -//// // Test null return format -//// assertNull(findOneAndUpdate(Document(), Document()).get()) -//// } -//// } -//// -//// @Test -//// fun findOneAndUpdate_noUpdates() { -//// with(getCollectionInternal()) { -//// assertNull(findOneAndUpdate(Document(), Document()).get()) -//// assertEquals(0, count().get()) -//// } -//// } -//// -//// @Test -//// fun findOneAndUpdate_noUpsert() { -//// with(getCollectionInternal()) { -//// val sampleDoc = Document("hello", "world1") -//// sampleDoc["num"] = 2 -//// -//// // Insert a sample Document -//// insertOne(sampleDoc).get() -//// assertEquals(1, count().get()) -//// -//// // Sample call to findOneAndUpdate() where we get the previous document back -//// val sampleUpdate = Document("\$set", Document("hello", "hellothere")).apply { -//// this["\$inc"] = Document("num", 1) -//// } -//// findOneAndUpdate(Document("hello", "world1"), sampleUpdate) -//// .get()!! -//// .withoutId() -//// .let { -//// assertEquals(sampleDoc.withoutId(), it) -//// } -//// assertEquals(1, count().get()) -//// -//// // Make sure the update took place -//// val expectedDoc = Document("hello", "hellothere") -//// expectedDoc["num"] = 3 -//// assertEquals(expectedDoc.withoutId(), find().first().get()!!.withoutId()) -//// assertEquals(1, count().get()) -//// -//// // Call findOneAndUpdate() again but get the new document -//// sampleUpdate.remove("\$set") -//// expectedDoc["num"] = 4 -//// val options = FindOneAndModifyOptions() -//// .returnNewDocument(true) -//// findOneAndUpdate(Document("hello", "hellothere"), sampleUpdate, options) -//// .get()!! -//// .withoutId() -//// .let { -//// assertEquals(expectedDoc.withoutId(), it) -//// } -//// assertEquals(1, count().get()) -//// -//// // Test null behaviour again with a filter that should not match any documents -//// assertNull(findOneAndUpdate(Document("hello", "zzzzz"), Document()).get()) -//// assertEquals(1, count().get()) -//// } -//// } -//// -//// @Test -//// fun findOneAndUpdate_upsert() { -//// with(getCollectionInternal()) { -//// val doc1 = Document("hello", "world1").apply { this["num"] = 1 } -//// val doc2 = Document("hello", "world2").apply { this["num"] = 2 } -//// val doc3 = Document("hello", "world3").apply { this["num"] = 3 } -//// -//// val filter = Document("hello", "hellothere") -//// -//// // Test the upsert option where it should not actually be invoked -//// var options = FindOneAndModifyOptions() -//// .returnNewDocument(true) -//// .upsert(true) -//// val update1 = Document("\$set", doc1) -//// assertEquals(doc1, -//// findOneAndUpdate(filter, update1, options) -//// .get()!! -//// .withoutId()) -//// assertEquals(1, count().get()) -//// assertEquals(doc1.withoutId(), -//// find().first() -//// .get()!! -//// .withoutId()) -//// -//// // Test the upsert option where the server should perform upsert and return new document -//// val update2 = Document("\$set", doc2) -//// assertEquals(doc2, -//// findOneAndUpdate(filter, update2, options) -//// .get()!! -//// .withoutId()) -//// assertEquals(2, count().get()) -//// -//// // Test the upsert option where the server should perform upsert and return old document -//// // The old document should be empty -//// options = FindOneAndModifyOptions() -//// .upsert(true) -//// val update = Document("\$set", doc3) -//// assertNull(findOneAndUpdate(filter, update, options).get()) -//// assertEquals(3, count().get()) -//// } -//// } -//// -//// @Test -//// fun findOneAndUpdate_withProjectionAndSort() { -//// with(getCollectionInternal()) { -//// insertMany(listOf( -//// Document(mapOf(Pair("team", "Fearful Mallards"), Pair("score", 25000))), -//// Document(mapOf(Pair("team", "Tactful Mooses"), Pair("score", 23500))), -//// Document(mapOf(Pair("team", "Aquatic Ponies"), Pair("score", 19250))), -//// Document(mapOf(Pair("team", "Cuddly Zebras"), Pair("score", 15235))), -//// Document(mapOf(Pair("team", "Garrulous Bears"), Pair("score", 18000))) -//// )).get() -//// -//// assertEquals(5, count().get()) -//// assertNotNull(findOne(Document("team", "Cuddly Zebras"))) -//// -//// // Project: team, hide _id; Sort: score ascending -//// val project = Document(mapOf(Pair("_id", 0), Pair("team", 1), Pair("score", 1))) -//// val sort = Document("score", 1) -//// -//// // This results in the update of Cuddly Zebras -//// val updatedDocument = findOneAndUpdate( -//// Document("score", Document("\$lt", 22250)), -//// Document("\$inc", Document("score", 1)), -//// FindOneAndModifyOptions() -//// .projection(project) -//// .sort(sort) -//// ).get() -//// -//// assertEquals(5, count().get()) -//// assertEquals( -//// Document(mapOf(Pair("team", "Cuddly Zebras"), Pair("score", 15235))), -//// updatedDocument -//// ) -//// assertEquals( -//// Document(mapOf(Pair("team", "Cuddly Zebras"), Pair("score", 15235 + 1))), -//// findOne(Document("team", "Cuddly Zebras")).get().withoutId() -//// ) -//// } -//// } -//// -//// @Test -//// fun findOneAndUpdate_fails() { -//// with(getCollectionInternal()) { -//// assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { -//// findOneAndUpdate(Document(), Document("\$who", 1)).get() -//// }.also { e -> -//// assertTrue(e.errorMessage!!.contains("modifier", true)) -//// } -//// -//// assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { -//// findOneAndUpdate(Document(), Document("\$who", 1), FindOneAndModifyOptions().upsert(true)).get() -//// }.also { e -> -//// assertTrue(e.errorMessage!!.contains("modifier", true)) -//// } -//// } -//// } // -// // FIXME Invalid fields?~? // @Test -// fun findOneAndReplace() = runBlocking { -// RealmLog.level = LogLevel.ALL -// assertNull(collection.findOneAndReplace(BsonDocument(), BsonDocument())) -// collection.insertMany( -// listOf( -// SyncDog("dog1"), -// SyncDog("dog1"), -// SyncDog("dog2") -// ) -// ) -// val x = collection.findOneAndReplace( -// BsonDocument(), -// BsonDocument("""{ "name": "dog1" }"""), -// upsert = true -// ) -// println(x) +// fun deleteMany_fails() { +// with(getCollectionInternal()) { +// assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { +// deleteMany(Document("\$who", 1)).get() +// }.also { e -> +// assertTrue(e.errorMessage!!.contains("operator", true)) +// } +// } +// } +// + @Test + fun updateOne() = runBlocking { + // Argument wrapper DSL + RealmLog.level = LogLevel.ALL + + val elements = SyncDog("x") + assertEquals(2, collection.insertMany(listOf(elements, elements)).size) + assertEquals(2, collection.count(filter = BsonDocument("""{"name": "x"}"""))) + + // A bit ann + collection.updateOne( + BsonDocument("""{ "name": "x"}"""), + BsonDocument("""{ "name": "y"}"""), + true + ) +// assertEquals(2, collection.deleteMany(BsonDocument("""{ "name": "x" }""" )) ) + +// assertEquals(3, collection.insertMany(listOf(elements, elements, elements)).size) +// assertEquals(3, collection.deleteMany(BsonDocument()) ) + + collection.updateOne( + BsonDocument("""{ "name": "z"}"""), + BsonDocument(""" { "name": "y"}"""), + upsert = true + ) + } + + // @Test +// fun updateOne_emptyCollection() { +// with(getCollectionInternal()) { +// val doc1 = Document("hello", "world") +// +// // Update on an empty collection +// updateOne(Document(), doc1) +// .get()!! +// .let { +// assertEquals(0, it.matchedCount) +// assertEquals(0, it.modifiedCount) +// assertNull(it.upsertedId) +// } +// +// // Update on an empty collection adding some values +// val doc2 = Document("\$set", Document("woof", "meow")) +// updateOne(Document(), doc2) +// .get()!! +// .let { +// assertEquals(0, it.matchedCount) +// assertEquals(0, it.modifiedCount) +// assertNull(it.upsertedId) +// assertEquals(0, count().get()) +// } +// } +// } +// +// @Test +// fun updateOne_emptyCollectionWithUpsert() { +// with(getCollectionInternal()) { +// val doc1 = Document("hello", "world") +// +// // Update on empty collection with upsert +// val options = UpdateOptions().upsert(true) +// updateOne(Document(), doc1, options) +// .get()!! +// .let { +// assertEquals(0, it.matchedCount) +// assertEquals(0, it.modifiedCount) +// assertFalse(it.upsertedId!!.isNull) +// } +// assertEquals(1, count().get()) +// +// assertEquals(doc1, find(Document()).first().get()!!.withoutId()) +// } +// } +// +// @Test +// fun updateOne_fails() { +// with(getCollectionInternal()) { +// assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { +// updateOne(Document("\$who", 1), Document()).get() +// }.also { e -> +// assertTrue(e.errorMessage!!.contains("operator", true)) +// } +// } +// } +// + @Test + fun updateMany() = runBlocking { + RealmLog.level = LogLevel.ALL + assertEquals( + 4, + collection.insertMany( + listOf( + SyncDog("x"), + SyncDog("x"), + SyncDog("y"), + SyncDog("z") + ) + ).size + ) + assertEquals(2, collection.count(filter = BsonDocument("""{"name": "x"}"""))) + collection.updateMany( + BsonDocument("""{"name": "x2"}"""), + BsonDocument("""{"name": "x1"}"""), + true + ) + } + + // @Test +// fun updateMany_emptyCollection() { +// with(getCollectionInternal()) { +// val doc1 = Document("hello", "world") +// +// // Update on empty collection +// updateMany(Document(), doc1) +// .get()!! +// .let { +// assertEquals(0, it.matchedCount) +// assertEquals(0, it.modifiedCount) +// assertNull(it.upsertedId) +// } +// assertEquals(0, count().get()) +// } // } -//// -//// @Test -//// fun findOneAndReplace_noUpdates() { -//// with(getCollectionInternal()) { -//// // Test null behaviour again with a filter that should not match any documents -//// assertNull(findOneAndReplace(Document("hello", "zzzzz"), Document()).get()) -//// assertEquals(0, count().get()) -//// assertNull(findOneAndReplace(Document(), Document()).get()) -//// assertEquals(0, count().get()) -//// } -//// } -//// -//// @Test -//// fun findOneAndReplace_noUpsert() { -//// with(getCollectionInternal()) { -//// val sampleDoc = Document("hello", "world1").apply { this["num"] = 2 } -//// -//// // Insert a sample Document -//// insertOne(sampleDoc).get() -//// assertEquals(1, count().get()) -//// -//// // Sample call to findOneAndReplace() where we get the previous document back -//// var sampleUpdate = Document("hello", "world2").apply { this["num"] = 2 } -//// assertEquals(sampleDoc.withoutId(), -//// findOneAndReplace(Document("hello", "world1"), sampleUpdate).get()!!.withoutId()) -//// assertEquals(1, count().get()) -//// -//// // Make sure the update took place -//// val expectedDoc = Document("hello", "world2").apply { this["num"] = 2 } -//// assertEquals(expectedDoc.withoutId(), find().first().get()!!.withoutId()) -//// assertEquals(1, count().get()) -//// -//// // Call findOneAndReplace() again but get the new document -//// sampleUpdate = Document("hello", "world3").apply { this["num"] = 3 } -//// val options = FindOneAndModifyOptions().returnNewDocument(true) -//// assertEquals(sampleUpdate.withoutId(), -//// findOneAndReplace(Document(), sampleUpdate, options).get()!!.withoutId()) -//// assertEquals(1, count().get()) -//// -//// // Test null behaviour again with a filter that should not match any documents -//// assertNull(findOneAndReplace(Document("hello", "zzzzz"), Document()).get()) -//// assertEquals(1, count().get()) -//// } -//// } -//// -//// @Test -//// fun findOneAndReplace_upsert() { -//// with(getCollectionInternal()) { -//// val doc4 = Document("hello", "world4").apply { this["num"] = 4 } -//// val doc5 = Document("hello", "world5").apply { this["num"] = 5 } -//// val doc6 = Document("hello", "world6").apply { this["num"] = 6 } -//// -//// // Test the upsert option where it should not actually be invoked -//// val sampleUpdate = Document("hello", "world4").apply { this["num"] = 4 } -//// var options = FindOneAndModifyOptions() -//// .returnNewDocument(true) -//// .upsert(true) -//// assertEquals(doc4.withoutId(), -//// findOneAndReplace(Document("hello", "world3"), doc4, options) -//// .get()!! -//// .withoutId()) -//// assertEquals(1, count().get()) -//// assertEquals(doc4.withoutId(), find().first().get()!!.withoutId()) -//// -//// // Test the upsert option where the server should perform upsert and return new document -//// options = FindOneAndModifyOptions().returnNewDocument(true).upsert(true) -//// assertEquals(doc5.withoutId(), findOneAndReplace(Document("hello", "hellothere"), doc5, options).get()!!.withoutId()) -//// assertEquals(2, count().get()) -//// -//// // Test the upsert option where the server should perform upsert and return old document -//// // The old document should be empty -//// options = FindOneAndModifyOptions().upsert(true) -//// assertNull(findOneAndReplace(Document("hello", "hellothere"), doc6, options).get()) -//// assertEquals(3, count().get()) -//// } -//// } -//// -//// @Test -//// fun findOneAndReplace_withProjectionAndSort() { -//// with(getCollectionInternal()) { -//// insertMany(listOf( -//// Document(mapOf(Pair("team", "Fearful Mallards"), Pair("score", 25000))), -//// Document(mapOf(Pair("team", "Tactful Mooses"), Pair("score", 23500))), -//// Document(mapOf(Pair("team", "Aquatic Ponies"), Pair("score", 19250))), -//// Document(mapOf(Pair("team", "Cuddly Zebras"), Pair("score", 15235))), -//// Document(mapOf(Pair("team", "Garrulous Bears"), Pair("score", 18000))) -//// )).get() -//// -//// assertEquals(5, count().get()) -//// assertNotNull(findOne(Document("team", "Cuddly Zebras"))) -//// -//// // Project: team, hide _id; Sort: score ascending -//// val project = Document(mapOf(Pair("_id", 0), Pair("team", 1))) -//// val sort = Document("score", 1) -//// -//// // This results in the replacement of Cuddly Zebras -//// val replacedDocument = findOneAndReplace( -//// Document("score", Document("\$lt", 22250)), -//// Document(mapOf(Pair("team", "Therapeutic Hamsters"), Pair("score", 22250))), -//// FindOneAndModifyOptions() -//// .projection(project) -//// .sort(sort) -//// ).get() -//// -//// assertEquals(5, count().get()) -//// assertEquals(Document("team", "Cuddly Zebras"), replacedDocument) -//// assertNull(findOne(Document("team", "Cuddly Zebras")).get()) -//// assertNotNull(findOne(Document("team", "Therapeutic Hamsters")).get()) -//// -//// // Check returnNewDocument -//// val newDocument = findOneAndReplace( -//// Document("score", 22250), -//// Document(mapOf(Pair("team", "New Therapeutic Hamsters"), Pair("score", 30000))), -//// FindOneAndModifyOptions().returnNewDocument(true) -//// ).get() -//// -//// assertEquals(Document(mapOf(Pair("team", "New Therapeutic Hamsters"), Pair("score", 30000))), newDocument.withoutId()) -//// } -//// } -//// -//// @Test -//// fun findOneAndReplace_fails() { -//// with(getCollectionInternal()) { -//// assertFailsWithErrorCode(ErrorCode.INVALID_PARAMETER) { -//// findOneAndReplace(Document(), Document("\$who", 1)).get() -//// } -//// -//// assertFailsWithErrorCode(ErrorCode.INVALID_PARAMETER) { -//// findOneAndReplace(Document(), Document("\$who", 1), FindOneAndModifyOptions().upsert(true)).get() -//// } -//// } -//// } // // @Test -// fun findOneAndDelete() = runBlocking { -// RealmLog.level = LogLevel.ALL -// assertNull(collection.findOneAndDelete(BsonDocument(), BsonDocument())) -// collection.insertMany( -// listOf( -// SyncDog("dog1"), -// SyncDog("dog1"), -// SyncDog("dog2") +// fun updateMany_emptyCollectionWithUpsert() { +// with(getCollectionInternal()) { +// val doc1 = Document("hello", "world") +// +// // Update on empty collection with upsert +// updateMany(Document(), doc1, UpdateOptions().upsert(true)) +// .get()!! +// .let { +// assertEquals(0, it.matchedCount) +// assertEquals(0, it.modifiedCount) +// assertNotNull(it.upsertedId) +// } +// assertEquals(1, count().get()) +// +// // Add new value using update +// val update = Document("woof", "meow") +// updateMany(Document(), Document("\$set", update)) +// .get()!! +// .let { +// assertEquals(1, it.matchedCount) +// assertEquals(1, it.modifiedCount) +// assertNull(it.upsertedId) +// } +// assertEquals(1, count().get()) +// val expected = Document(doc1).apply { this["woof"] = "meow" } +// assertEquals(expected, find().first().get()!!.withoutId()) +// +// // Insert empty document, add ["woof", "meow"] to it and check it worked +// insertOne(Document()).get() +// updateMany(Document(), Document("\$set", update)) +// .get()!! +// .let { +// assertEquals(2, it.matchedCount) +// assertEquals(2, it.modifiedCount) +// } +// assertEquals(2, count().get()) +// find().iterator() +// .get()!! +// .let { +// assertEquals(expected, it.next().withoutId()) +// assertEquals(update, it.next().withoutId()) +// assertFalse(it.hasNext()) +// } +// } +// } +// +// @Test +// fun updateMany_fails() { +// with(getCollectionInternal()) { +// assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { +// updateMany(Document("\$who", 1), Document()).get() +// }.also { e -> +// assertTrue(e.errorMessage!!.contains("operator", true)) +// } +// } +// } +// + @Test + fun findOneAndUpdate() = runBlocking { + RealmLog.level = LogLevel.ALL + assertNull(collection.findOneAndUpdate(BsonDocument(), BsonDocument())) + collection.insertMany( + listOf( + SyncDog("dog1"), + SyncDog("dog1"), + SyncDog("dog2") + ) + ) + collection.findOneAndUpdate( + BsonDocument(), + BsonDocument("""{ "name": "dog1" }"""), + upsert = true + ) + } +// @Test +// fun findOneAndUpdate_emptyCollection() { +// with(getCollectionInternal()) { +// // Test null return format +// assertNull(findOneAndUpdate(Document(), Document()).get()) +// } +// } +// +// @Test +// fun findOneAndUpdate_noUpdates() { +// with(getCollectionInternal()) { +// assertNull(findOneAndUpdate(Document(), Document()).get()) +// assertEquals(0, count().get()) +// } +// } +// +// @Test +// fun findOneAndUpdate_noUpsert() { +// with(getCollectionInternal()) { +// val sampleDoc = Document("hello", "world1") +// sampleDoc["num"] = 2 +// +// // Insert a sample Document +// insertOne(sampleDoc).get() +// assertEquals(1, count().get()) +// +// // Sample call to findOneAndUpdate() where we get the previous document back +// val sampleUpdate = Document("\$set", Document("hello", "hellothere")).apply { +// this["\$inc"] = Document("num", 1) +// } +// findOneAndUpdate(Document("hello", "world1"), sampleUpdate) +// .get()!! +// .withoutId() +// .let { +// assertEquals(sampleDoc.withoutId(), it) +// } +// assertEquals(1, count().get()) +// +// // Make sure the update took place +// val expectedDoc = Document("hello", "hellothere") +// expectedDoc["num"] = 3 +// assertEquals(expectedDoc.withoutId(), find().first().get()!!.withoutId()) +// assertEquals(1, count().get()) +// +// // Call findOneAndUpdate() again but get the new document +// sampleUpdate.remove("\$set") +// expectedDoc["num"] = 4 +// val options = FindOneAndModifyOptions() +// .returnNewDocument(true) +// findOneAndUpdate(Document("hello", "hellothere"), sampleUpdate, options) +// .get()!! +// .withoutId() +// .let { +// assertEquals(expectedDoc.withoutId(), it) +// } +// assertEquals(1, count().get()) +// +// // Test null behaviour again with a filter that should not match any documents +// assertNull(findOneAndUpdate(Document("hello", "zzzzz"), Document()).get()) +// assertEquals(1, count().get()) +// } +// } +// +// @Test +// fun findOneAndUpdate_upsert() { +// with(getCollectionInternal()) { +// val doc1 = Document("hello", "world1").apply { this["num"] = 1 } +// val doc2 = Document("hello", "world2").apply { this["num"] = 2 } +// val doc3 = Document("hello", "world3").apply { this["num"] = 3 } +// +// val filter = Document("hello", "hellothere") +// +// // Test the upsert option where it should not actually be invoked +// var options = FindOneAndModifyOptions() +// .returnNewDocument(true) +// .upsert(true) +// val update1 = Document("\$set", doc1) +// assertEquals(doc1, +// findOneAndUpdate(filter, update1, options) +// .get()!! +// .withoutId()) +// assertEquals(1, count().get()) +// assertEquals(doc1.withoutId(), +// find().first() +// .get()!! +// .withoutId()) +// +// // Test the upsert option where the server should perform upsert and return new document +// val update2 = Document("\$set", doc2) +// assertEquals(doc2, +// findOneAndUpdate(filter, update2, options) +// .get()!! +// .withoutId()) +// assertEquals(2, count().get()) +// +// // Test the upsert option where the server should perform upsert and return old document +// // The old document should be empty +// options = FindOneAndModifyOptions() +// .upsert(true) +// val update = Document("\$set", doc3) +// assertNull(findOneAndUpdate(filter, update, options).get()) +// assertEquals(3, count().get()) +// } +// } +// +// @Test +// fun findOneAndUpdate_withProjectionAndSort() { +// with(getCollectionInternal()) { +// insertMany(listOf( +// Document(mapOf(Pair("team", "Fearful Mallards"), Pair("score", 25000))), +// Document(mapOf(Pair("team", "Tactful Mooses"), Pair("score", 23500))), +// Document(mapOf(Pair("team", "Aquatic Ponies"), Pair("score", 19250))), +// Document(mapOf(Pair("team", "Cuddly Zebras"), Pair("score", 15235))), +// Document(mapOf(Pair("team", "Garrulous Bears"), Pair("score", 18000))) +// )).get() +// +// assertEquals(5, count().get()) +// assertNotNull(findOne(Document("team", "Cuddly Zebras"))) +// +// // Project: team, hide _id; Sort: score ascending +// val project = Document(mapOf(Pair("_id", 0), Pair("team", 1), Pair("score", 1))) +// val sort = Document("score", 1) +// +// // This results in the update of Cuddly Zebras +// val updatedDocument = findOneAndUpdate( +// Document("score", Document("\$lt", 22250)), +// Document("\$inc", Document("score", 1)), +// FindOneAndModifyOptions() +// .projection(project) +// .sort(sort) +// ).get() +// +// assertEquals(5, count().get()) +// assertEquals( +// Document(mapOf(Pair("team", "Cuddly Zebras"), Pair("score", 15235))), +// updatedDocument // ) -// ) -// val x: SyncDog = collection.findOneAndDelete( -// BsonDocument(), -// BsonDocument("""{ "name": "dog1" }"""), -// ) +// assertEquals( +// Document(mapOf(Pair("team", "Cuddly Zebras"), Pair("score", 15235 + 1))), +// findOne(Document("team", "Cuddly Zebras")).get().withoutId() +// ) +// } // } -// // -//// @Test -//// fun findOneAndDelete() { -//// with(getCollectionInternal()) { -//// val sampleDoc = Document("hello", "world1").apply { this["num"] = 1 } -//// -//// // Collection should start out empty -//// // This also tests the null return format -//// assertNull(findOneAndDelete(Document()).get()) -//// -//// // Insert a sample Document -//// insertOne(sampleDoc).get() -//// assertEquals(1, count().get()) -//// -//// // Sample call to findOneAndDelete() where we delete the only doc in the collection -//// assertEquals(sampleDoc.withoutId(), -//// findOneAndDelete(Document()).get()!!.withoutId()) -//// -//// // There should be no documents in the collection now -//// assertEquals(0, count().get()) -//// -//// // Insert a sample Document -//// insertOne(sampleDoc).get() -//// assertEquals(1, count().get()) -//// -//// // Call findOneAndDelete() again but this time with a filter -//// assertEquals(sampleDoc.withoutId(), -//// findOneAndDelete(Document("hello", "world1")).get()!!.withoutId()) -//// -//// // There should be no documents in the collection now -//// assertEquals(0, count().get()) -//// -//// // Insert a sample Document -//// insertOne(sampleDoc).get() -//// assertEquals(1, count().get()) -//// -//// // Test null behaviour again with a filter that should not match any documents -//// assertNull(findOneAndDelete(Document("hello", "zzzzz")).get()) -//// assertEquals(1, count().get()) -//// -//// val doc2 = Document("hello", "world2").apply { this["num"] = 2 } -//// val doc3 = Document("hello", "world3").apply { this["num"] = 3 } -//// -//// // Insert new documents -//// insertMany(listOf(doc2, doc3)).get() -//// assertEquals(3, count().get()) -//// } -//// } -//// -//// @Test -//// fun findOneAndDelete_withProjectionAndSort() { -//// with(getCollectionInternal()) { -//// insertMany(listOf( -//// Document(mapOf(Pair("team", "Fearful Mallards"), Pair("score", 25000))), -//// Document(mapOf(Pair("team", "Tactful Mooses"), Pair("score", 23500))), -//// Document(mapOf(Pair("team", "Aquatic Ponies"), Pair("score", 19250))), -//// Document(mapOf(Pair("team", "Cuddly Zebras"), Pair("score", 15235))), -//// Document(mapOf(Pair("team", "Garrulous Bears"), Pair("score", 18000))) -//// )).get() -//// -//// assertEquals(5, count().get()) -//// assertNotNull(findOne(Document("team", "Cuddly Zebras"))) -//// -//// // Project: team, hide _id; Sort: score ascending -//// val project = Document(mapOf(Pair("_id", 0), Pair("team", 1))) -//// val sort = Document("score", 1) -//// -//// // This results in the deletion of Cuddly Zebras -//// val deletedDocument = findOneAndDelete( -//// Document("score", Document("\$lt", 22250)), -//// FindOneAndModifyOptions() -//// .projection(project) -//// .sort(sort) -//// ).get() -//// -//// assertEquals(4, count().get()) -//// assertEquals(Document("team", "Cuddly Zebras"), deletedDocument.withoutId()) -//// assertNull(findOne(Document("team", "Cuddly Zebras")).get()) -//// } -//// } // +// @Test +// fun findOneAndUpdate_fails() { +// with(getCollectionInternal()) { +// assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { +// findOneAndUpdate(Document(), Document("\$who", 1)).get() +// }.also { e -> +// assertTrue(e.errorMessage!!.contains("modifier", true)) +// } +// +// assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { +// findOneAndUpdate(Document(), Document("\$who", 1), FindOneAndModifyOptions().upsert(true)).get() +// }.also { e -> +// assertTrue(e.errorMessage!!.contains("modifier", true)) +// } +// } +// } + + // FIXME Invalid fields?~? + @Test + fun findOneAndReplace() = runBlocking { + RealmLog.level = LogLevel.ALL + assertNull(collection.findOneAndReplace(BsonDocument(), BsonDocument())) + collection.insertMany( + listOf( + SyncDog("dog1"), + SyncDog("dog1"), + SyncDog("dog2") + ) + ) + val x = collection.findOneAndReplace( + BsonDocument(), + BsonDocument("""{ "name": "dog1" }"""), + upsert = true + ) + println(x) + } +// +// @Test +// fun findOneAndReplace_noUpdates() { +// with(getCollectionInternal()) { +// // Test null behaviour again with a filter that should not match any documents +// assertNull(findOneAndReplace(Document("hello", "zzzzz"), Document()).get()) +// assertEquals(0, count().get()) +// assertNull(findOneAndReplace(Document(), Document()).get()) +// assertEquals(0, count().get()) +// } +// } +// +// @Test +// fun findOneAndReplace_noUpsert() { +// with(getCollectionInternal()) { +// val sampleDoc = Document("hello", "world1").apply { this["num"] = 2 } +// +// // Insert a sample Document +// insertOne(sampleDoc).get() +// assertEquals(1, count().get()) +// +// // Sample call to findOneAndReplace() where we get the previous document back +// var sampleUpdate = Document("hello", "world2").apply { this["num"] = 2 } +// assertEquals(sampleDoc.withoutId(), +// findOneAndReplace(Document("hello", "world1"), sampleUpdate).get()!!.withoutId()) +// assertEquals(1, count().get()) +// +// // Make sure the update took place +// val expectedDoc = Document("hello", "world2").apply { this["num"] = 2 } +// assertEquals(expectedDoc.withoutId(), find().first().get()!!.withoutId()) +// assertEquals(1, count().get()) +// +// // Call findOneAndReplace() again but get the new document +// sampleUpdate = Document("hello", "world3").apply { this["num"] = 3 } +// val options = FindOneAndModifyOptions().returnNewDocument(true) +// assertEquals(sampleUpdate.withoutId(), +// findOneAndReplace(Document(), sampleUpdate, options).get()!!.withoutId()) +// assertEquals(1, count().get()) +// +// // Test null behaviour again with a filter that should not match any documents +// assertNull(findOneAndReplace(Document("hello", "zzzzz"), Document()).get()) +// assertEquals(1, count().get()) +// } +// } +// +// @Test +// fun findOneAndReplace_upsert() { +// with(getCollectionInternal()) { +// val doc4 = Document("hello", "world4").apply { this["num"] = 4 } +// val doc5 = Document("hello", "world5").apply { this["num"] = 5 } +// val doc6 = Document("hello", "world6").apply { this["num"] = 6 } +// +// // Test the upsert option where it should not actually be invoked +// val sampleUpdate = Document("hello", "world4").apply { this["num"] = 4 } +// var options = FindOneAndModifyOptions() +// .returnNewDocument(true) +// .upsert(true) +// assertEquals(doc4.withoutId(), +// findOneAndReplace(Document("hello", "world3"), doc4, options) +// .get()!! +// .withoutId()) +// assertEquals(1, count().get()) +// assertEquals(doc4.withoutId(), find().first().get()!!.withoutId()) +// +// // Test the upsert option where the server should perform upsert and return new document +// options = FindOneAndModifyOptions().returnNewDocument(true).upsert(true) +// assertEquals(doc5.withoutId(), findOneAndReplace(Document("hello", "hellothere"), doc5, options).get()!!.withoutId()) +// assertEquals(2, count().get()) +// +// // Test the upsert option where the server should perform upsert and return old document +// // The old document should be empty +// options = FindOneAndModifyOptions().upsert(true) +// assertNull(findOneAndReplace(Document("hello", "hellothere"), doc6, options).get()) +// assertEquals(3, count().get()) +// } +// } +// +// @Test +// fun findOneAndReplace_withProjectionAndSort() { +// with(getCollectionInternal()) { +// insertMany(listOf( +// Document(mapOf(Pair("team", "Fearful Mallards"), Pair("score", 25000))), +// Document(mapOf(Pair("team", "Tactful Mooses"), Pair("score", 23500))), +// Document(mapOf(Pair("team", "Aquatic Ponies"), Pair("score", 19250))), +// Document(mapOf(Pair("team", "Cuddly Zebras"), Pair("score", 15235))), +// Document(mapOf(Pair("team", "Garrulous Bears"), Pair("score", 18000))) +// )).get() +// +// assertEquals(5, count().get()) +// assertNotNull(findOne(Document("team", "Cuddly Zebras"))) +// +// // Project: team, hide _id; Sort: score ascending +// val project = Document(mapOf(Pair("_id", 0), Pair("team", 1))) +// val sort = Document("score", 1) +// +// // This results in the replacement of Cuddly Zebras +// val replacedDocument = findOneAndReplace( +// Document("score", Document("\$lt", 22250)), +// Document(mapOf(Pair("team", "Therapeutic Hamsters"), Pair("score", 22250))), +// FindOneAndModifyOptions() +// .projection(project) +// .sort(sort) +// ).get() +// +// assertEquals(5, count().get()) +// assertEquals(Document("team", "Cuddly Zebras"), replacedDocument) +// assertNull(findOne(Document("team", "Cuddly Zebras")).get()) +// assertNotNull(findOne(Document("team", "Therapeutic Hamsters")).get()) +// +// // Check returnNewDocument +// val newDocument = findOneAndReplace( +// Document("score", 22250), +// Document(mapOf(Pair("team", "New Therapeutic Hamsters"), Pair("score", 30000))), +// FindOneAndModifyOptions().returnNewDocument(true) +// ).get() +// +// assertEquals(Document(mapOf(Pair("team", "New Therapeutic Hamsters"), Pair("score", 30000))), newDocument.withoutId()) +// } +// } +// +// @Test +// fun findOneAndReplace_fails() { +// with(getCollectionInternal()) { +// assertFailsWithErrorCode(ErrorCode.INVALID_PARAMETER) { +// findOneAndReplace(Document(), Document("\$who", 1)).get() +// } +// +// assertFailsWithErrorCode(ErrorCode.INVALID_PARAMETER) { +// findOneAndReplace(Document(), Document("\$who", 1), FindOneAndModifyOptions().upsert(true)).get() +// } +// } +// } + + @Test + fun findOneAndDelete() = runBlocking { + RealmLog.level = LogLevel.ALL + assertNull(collection.findOneAndDelete(BsonDocument(), BsonDocument())) + collection.insertMany( + listOf( + SyncDog("dog1"), + SyncDog("dog1"), + SyncDog("dog2") + ) + ) + val x: SyncDog = collection.findOneAndDelete( + BsonDocument(), + BsonDocument("""{ "name": "dog1" }"""), + ) + } + // +// @Test +// fun findOneAndDelete() { +// with(getCollectionInternal()) { +// val sampleDoc = Document("hello", "world1").apply { this["num"] = 1 } +// +// // Collection should start out empty +// // This also tests the null return format +// assertNull(findOneAndDelete(Document()).get()) +// +// // Insert a sample Document +// insertOne(sampleDoc).get() +// assertEquals(1, count().get()) +// +// // Sample call to findOneAndDelete() where we delete the only doc in the collection +// assertEquals(sampleDoc.withoutId(), +// findOneAndDelete(Document()).get()!!.withoutId()) +// +// // There should be no documents in the collection now +// assertEquals(0, count().get()) +// +// // Insert a sample Document +// insertOne(sampleDoc).get() +// assertEquals(1, count().get()) +// +// // Call findOneAndDelete() again but this time with a filter +// assertEquals(sampleDoc.withoutId(), +// findOneAndDelete(Document("hello", "world1")).get()!!.withoutId()) +// +// // There should be no documents in the collection now +// assertEquals(0, count().get()) +// +// // Insert a sample Document +// insertOne(sampleDoc).get() +// assertEquals(1, count().get()) +// +// // Test null behaviour again with a filter that should not match any documents +// assertNull(findOneAndDelete(Document("hello", "zzzzz")).get()) +// assertEquals(1, count().get()) +// +// val doc2 = Document("hello", "world2").apply { this["num"] = 2 } +// val doc3 = Document("hello", "world3").apply { this["num"] = 3 } +// +// // Insert new documents +// insertMany(listOf(doc2, doc3)).get() +// assertEquals(3, count().get()) +// } +// } +// +// @Test +// fun findOneAndDelete_withProjectionAndSort() { +// with(getCollectionInternal()) { +// insertMany(listOf( +// Document(mapOf(Pair("team", "Fearful Mallards"), Pair("score", 25000))), +// Document(mapOf(Pair("team", "Tactful Mooses"), Pair("score", 23500))), +// Document(mapOf(Pair("team", "Aquatic Ponies"), Pair("score", 19250))), +// Document(mapOf(Pair("team", "Cuddly Zebras"), Pair("score", 15235))), +// Document(mapOf(Pair("team", "Garrulous Bears"), Pair("score", 18000))) +// )).get() +// +// assertEquals(5, count().get()) +// assertNotNull(findOne(Document("team", "Cuddly Zebras"))) +// +// // Project: team, hide _id; Sort: score ascending +// val project = Document(mapOf(Pair("_id", 0), Pair("team", 1))) +// val sort = Document("score", 1) +// +// // This results in the deletion of Cuddly Zebras +// val deletedDocument = findOneAndDelete( +// Document("score", Document("\$lt", 22250)), +// FindOneAndModifyOptions() +// .projection(project) +// .sort(sort) +// ).get() +// +// assertEquals(4, count().get()) +// assertEquals(Document("team", "Cuddly Zebras"), deletedDocument.withoutId()) +// assertNull(findOne(Document("team", "Cuddly Zebras")).get()) +// } +// } } @@ -1415,10 +1415,6 @@ class SyncDog : RealmObject { @Serializable data class SyncDogIntId(val name: String, val _id: Int) -//@OptIn(ExperimentalKBsonSerializerApi::class) -//inline fun EJson.BsonDocument(key: String, value: T): BsonDocument { -// return BsonDocument(key to this.encodeToBsonValue(value)) -//} @OptIn(ExperimentalKBsonSerializerApi::class) inline operator fun BsonDocument.Companion.invoke( key: String, @@ -1426,55 +1422,3 @@ inline operator fun BsonDocument.Companion.invoke( ): BsonDocument { return BsonDocument(key to EJson.Default.encodeToBsonValue(value)) } - -//fun BsonDocument2(vararg args: Pair): BsonDocument = -// BsonDocument(args.toMap().mapValues { anyToBsonValue(it.value) }) -//private fun anyToBsonValue(value: Any?): BsonValue { -//return when (value) { -// // BsonNull -> TODO() -// null -> BsonNull -// -// // is BsonString -> TODO() -// is String -> BsonString(value) -// -// // is BsonBoolean -> TODO() -// is Boolean -> BsonBoolean(value) -// -// // is BsonNumber -> TODO() -// is Int -> BsonInt32(value) -// is Long -> BsonInt64(value) -// is Double -> BsonDouble(value) -// -// // is BsonObjectId -> TODO() -// is ObjectId -> value -// // is BsonDecimal128 -> TODO() -// is Decimal128 -> value -// -// // is BsonBinary -> TODO() -// is ByteArray -> BsonBinary(value) -// -// // is BsonTimestamp -> TODO() -// is RealmInstant -> { -// if (value.epochSeconds.let { it >= Int.MIN_VALUE && it <= Int.MAX_VALUE }) { -// BsonTimestamp(value.epochSeconds.toInt(), value.nanosecondsOfSecond) -// } else throw IllegalArgumentException("Cannot represent RealmInstant in BsonTimestamp: $$value") -// } -// -// // is BsonArray -> TODO() -// is List<*> -> BsonArray(value.map { anyToBsonValue(it) } ) -// -// // is BsonDocument -> TODO() -// is Map<*, *> -> BsonDocument(value.mapValues { anyToBsonValue(it) } as Map) -// // is BsonDBPointer -> TODO() -// // is BsonDateTime -> TODO() -// // is BsonJavaScript -> TODO() -// // is BsonJavaScriptWithScope -> TODO() -// // BsonMaxKey -> TODO() -// // BsonMinKey -> TODO() -// // is BsonRegularExpression -> TODO() -// // is BsonSymbol -> TODO() -// // BsonUndefined -> TODO() -// else -> TODO("$value ${value::class}") -// } -// -//} From 4a6086744d4e05bcca086da89f2d65645fac5e6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Fri, 8 Dec 2023 14:17:38 +0100 Subject: [PATCH 05/36] Liniting --- .../kotlin/internal/interop/RealmInterop.kt | 1 + .../realm/kotlin/internal/util/Validation.kt | 2 +- .../kotlin/io/realm/kotlin/mongodb/User.kt | 9 +- .../realm/kotlin/mongodb/ext/FunctionsExt.kt | 37 +-- .../io/realm/kotlin/mongodb/ext/UserExt.kt | 41 ++- .../mongodb/internal/MongoClientImpl.kt | 1 - .../mongodb/internal/MongoCollectionImpl.kt | 57 ++-- .../mongodb/internal/MongoDatabaseImpl.kt | 3 +- .../realm/kotlin/mongodb/internal/UserImpl.kt | 2 - .../realm/kotlin/mongodb/mongo/MongoClient.kt | 13 +- .../kotlin/mongodb/mongo/MongoCollection.kt | 114 ++++---- .../kotlin/mongodb/mongo/MongoDatabase.kt | 30 +- .../mongodb/common/mongo/MongoClientTest.kt | 275 +++++++++--------- .../kotlin/test/mongodb/common/utils/Utils.kt | 4 - 14 files changed, 327 insertions(+), 262 deletions(-) diff --git a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt index f4ff9a3312..e894c66967 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 @@ -1614,6 +1614,7 @@ actual object RealmInterop { ) } + @Suppress("LongParameterList") actual fun realm_app_call_function( app: RealmAppPointer, user: RealmUserPointer, 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 7172efdc20..7b37f1c900 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 @@ -38,7 +38,7 @@ public object Validation { * to that type, otherwise an IllegalArgumentException is thrown with the provided error message. */ @OptIn(ExperimentalContracts::class) - public inline fun isType(arg: Any?, errorMessage: String) { + public inline fun isType(arg: Any?, errorMessage: String = "Object '$arg' is not of type ${T::class.qualifiedName}") { contract { returns() implies (arg is T) } diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/User.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/User.kt index 64accd35ff..23f4f7d145 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/User.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/User.kt @@ -204,7 +204,14 @@ public interface User { */ public suspend fun linkCredentials(credentials: Credentials): User - // TODO Doc + /** + * Get a [MongoClient] for accessing documents from App Service's _Data Source_. + * + * @param serviceName the name of the data service. + * @param eJson the EJson serializer that the [MongoClient] should use to convert objects and + * primary keys with. Will default to the apps [EJson] instance configured with + * [AppConfiguration.Builder.ejson]. + */ @OptIn(ExperimentalKBsonSerializerApi::class) public fun mongoClient(serviceName: String, eJson: EJson? = null): MongoClient diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/FunctionsExt.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/FunctionsExt.kt index 348685c5cf..711f23e528 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/FunctionsExt.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/FunctionsExt.kt @@ -99,7 +99,7 @@ public suspend fun Functions.call2( ): Pair, String> = with(this as FunctionsImpl) { val builder = CallBuilder(app.configuration.ejson) - builder + builder .apply(callBuilderBlock) .run { val serializedEjsonArgs: String = Bson.toJson(arguments) @@ -118,24 +118,24 @@ public suspend inline fun Functions.call( ): T = call2(name, callBuilderBlock).run { first.ejson.decodeFromString(first.returnValueSerializer ?: first.ejson.serializersModule.serializerOrRealmBuiltInSerializer(), second) } -//with(this as FunctionsImpl) { +// with(this as FunctionsImpl) { // -// CallBuilder(app.configuration.ejson) -// .apply(callBuilderBlock) -// .run { -// val serializedEjsonArgs: String = Bson.toJson(arguments) +// CallBuilder(app.configuration.ejson) +// .apply(callBuilderBlock) +// .run { +// val serializedEjsonArgs: String = Bson.toJson(arguments) // -// val encodedResult: String = callInternal(name, serviceName = serviceName, serializedEjsonArgs = serializedEjsonArgs) +// val encodedResult: String = callInternal(name, serviceName = serviceName, serializedEjsonArgs = serializedEjsonArgs) // -// val returnValueSerializer: KSerializer = -// returnValueSerializer -// ?: ejson.serializersModule.serializerOrRealmBuiltInSerializer() +// val returnValueSerializer: KSerializer = +// returnValueSerializer +// ?: ejson.serializersModule.serializerOrRealmBuiltInSerializer() // -// val r: T = ejson.decodeFromString(returnValueSerializer, encodedResult) -// r -// } +// val r: T = ejson.decodeFromString(returnValueSerializer, encodedResult) +// r +// } -//} +// } /** * Builder used to construct a call defining serializers for the different arguments and return value. @@ -148,9 +148,12 @@ internal constructor( @PublishedApi internal val ejson: EJson, ) { -// internal val entries: MutableMap = mutableMapOf() -@PublishedApi -internal var serviceName: String? = null + /** + * The name of the App Services's data source for MongoClient requests. + */ + @PublishedApi + internal var serviceName: String? = null + /** * Contains all given arguments transformed as [BsonValue]. The encoding is done on each [add] call * as in that context we have type information from the reified type. diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/UserExt.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/UserExt.kt index f118ac0ce9..ea6af48f7c 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/UserExt.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/UserExt.kt @@ -78,16 +78,16 @@ public inline fun User.profile(serializer: KSerializer = (this as * @param T the type to decoded the user profile. * @return The profile for this user. */ -//@ExperimentalRealmSerializerApi -//@OptIn(ExperimentalKBsonSerializerApi::class) -//public inline fun User.profile(): T = -// profile( -// (this as UserImpl).app -// .configuration -// .ejson -// .serializersModule -// .serializerOrRealmBuiltInSerializer() -// ) +// @ExperimentalRealmSerializerApi +// @OptIn(ExperimentalKBsonSerializerApi::class) +// public inline fun User.profile(): T = +// profile( +// (this as UserImpl).app +// .configuration +// .ejson +// .serializersModule +// .serializerOrRealmBuiltInSerializer() +// ) // /** * Returns the custom user data associated with the user in the Realm App as [T]. @@ -101,16 +101,16 @@ public inline fun User.profile(serializer: KSerializer = (this as * @param T the type to decoded the user custom data. * @return The custom user data associated with the user. */ -//@ExperimentalRealmSerializerApi -//@OptIn(ExperimentalKBsonSerializerApi::class) -//public inline fun User.customData(): T? = -// customData( -// (this as UserImpl).app -// .configuration -// .ejson -// .serializersModule -// .serializerOrRealmBuiltInSerializer() -// ) +// @ExperimentalRealmSerializerApi +// @OptIn(ExperimentalKBsonSerializerApi::class) +// public inline fun User.customData(): T? = +// customData( +// (this as UserImpl).app +// .configuration +// .ejson +// .serializersModule +// .serializerOrRealmBuiltInSerializer() +// ) /** * Returns the custom user data associated with the user in the Realm App as [T]. @@ -136,4 +136,3 @@ public inline fun User.customData(serializer: KSerializer = (this ) } } - diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoClientImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoClientImpl.kt index 77ff03f9d6..b746529ca2 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoClientImpl.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoClientImpl.kt @@ -31,5 +31,4 @@ internal class MongoClientImpl( override fun database(databaseName: String, eJson: EJson?): MongoDatabase = MongoDatabaseImpl(this, databaseName, eJson ?: this.eJson) - } diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt index a2b97c654a..381f553f2d 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt @@ -37,24 +37,23 @@ internal class MongoCollectionImpl constructor( @PublishedApi internal val database: MongoDatabaseImpl, override val name: String, val eJson: EJson, -): MongoCollection { +) : MongoCollection { val client = this.database.client val user = client.user val functions = user.functions - private val defaults: Map = mapOf( - "database" to BsonString(database.name), - "collection" to BsonString(name), - ) + "database" to BsonString(database.name), + "collection" to BsonString(name), + ) @OptIn(ExperimentalKBsonSerializerApi::class) override fun collection(eJson: EJson?): MongoCollection { return MongoCollectionImpl(this.database, this.name, eJson ?: this.eJson) } - private suspend inline fun call(name: String, crossinline document: MutableMap.()-> Unit): R { + private suspend inline fun call(name: String, crossinline document: MutableMap.() -> Unit): R { return user.functions.call(name) { serviceName(client.serviceName) val doc = defaults.toMutableMap() @@ -111,7 +110,7 @@ internal class MongoCollectionImpl constructor( return when (deletedCount) { 0L -> false 1L -> true - else -> throw ServiceException("Unexpected response from deleteOne: deletedCount=${deletedCount}") + else -> throw ServiceException("Unexpected response from deleteOne: deletedCount=$deletedCount") } } @@ -124,27 +123,36 @@ internal class MongoCollectionImpl constructor( } @PublishedApi - internal suspend fun updateOne( filter: BsonDocument, update: BsonDocument, upsert: Boolean = false ): BsonValue { - val deletedCountBson = call("updateOne") { + internal suspend fun updateOne(filter: BsonDocument, update: BsonDocument, upsert: Boolean = false): Pair { + val response: BsonValue = call("updateOne") { put("query", filter) put("update", update) put("upsert", BsonBoolean(upsert)) } - TODO() - return decodeFromBsonValue(deletedCountBson) + return response.asDocument().run { + val modifiedCount: Long? = get("modifiedCount")?.let { decodeFromBsonValue(it) } + val modified = when (modifiedCount) { + 0L -> false + 1L -> true + else -> throw ServiceException("Unexpected response from updateOne: modifiedCount=$modifiedCount") + } + modified to (get("upsertedId")) + } } @PublishedApi - internal suspend fun updateMany( filter: BsonDocument, update: BsonDocument, upsert: Boolean = false ): BsonValue { - val deletedCountBson = call("updateMany") { + internal suspend fun updateMany(filter: BsonDocument, update: BsonDocument, upsert: Boolean = false): Pair { + val response = call("updateMany") { put("query", filter) put("update", update) put("upsert", BsonBoolean(upsert)) } - TODO() - return decodeFromBsonValue(deletedCountBson) + return response.asDocument().run { + decodeFromBsonValue(get("modifiedCount")!!) to (get("upsertedId")) + } } + @Suppress("LongParameterList") @PublishedApi internal suspend fun findOneAndUpdate( filter: BsonDocument, @@ -156,12 +164,13 @@ internal class MongoCollectionImpl constructor( ): BsonValue = call("findOneAndUpdate") { put("filter", filter) put("update", update) - projection?.let { put("projection", projection)} - sort?.let { put("sort", sort)} + projection?.let { put("projection", projection) } + sort?.let { put("sort", sort) } put("upsert", BsonBoolean(upsert)) put("returnNewDoc", BsonBoolean(returnNewDoc)) } + @Suppress("LongParameterList") @PublishedApi internal suspend fun findOneAndReplace( filter: BsonDocument, @@ -173,12 +182,12 @@ internal class MongoCollectionImpl constructor( ): BsonValue = call("findOneAndReplace") { put("filter", filter) put("update", update) - projection?.let { put("projection", projection)} - sort?.let { put("sort", sort)} + projection?.let { put("projection", projection) } + sort?.let { put("sort", sort) } put("upsert", BsonBoolean(upsert)) put("returnNewDoc", BsonBoolean(returnNewDoc)) } - + @PublishedApi internal suspend fun findOneAndDelete( filter: BsonDocument, @@ -186,8 +195,8 @@ internal class MongoCollectionImpl constructor( sort: BsonDocument? = null, ): BsonValue = call("findOneAndDelete") { put("filter", filter) - projection?.let { put("projection", projection)} - sort?.let { put("sort", sort)} + projection?.let { put("projection", projection) } + sort?.let { put("sort", sort) } } } @@ -199,8 +208,8 @@ internal inline fun MongoCollectionImpl<*, *>.encodeToBsonValu @OptIn(ExperimentalKBsonSerializerApi::class) @PublishedApi internal inline fun MongoCollectionImpl<*, *>.decodeFromBsonValue(bsonValue: BsonValue): R { - return if (bsonValue is R) { - bsonValue + return if (R::class == BsonValue::class) { + bsonValue as R } else { eJson.decodeFromBsonValue(bsonValue) } diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDatabaseImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDatabaseImpl.kt index 9c71dfeb82..4053f17066 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDatabaseImpl.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDatabaseImpl.kt @@ -18,6 +18,7 @@ package io.realm.kotlin.mongodb.internal import io.realm.kotlin.mongodb.mongo.MongoCollection import io.realm.kotlin.mongodb.mongo.MongoDatabase +import org.mongodb.kbson.BsonDocument import org.mongodb.kbson.BsonValue import org.mongodb.kbson.ExperimentalKBsonSerializerApi import org.mongodb.kbson.serialization.EJson @@ -31,7 +32,7 @@ internal class MongoDatabaseImpl constructor( val eJson: EJson, ) : MongoDatabase { - override fun collection(collectionName: String): MongoCollection { + override fun collection(collectionName: String): MongoCollection { return MongoCollectionImpl(this, collectionName, this.eJson) } diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/UserImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/UserImpl.kt index d429dd78d8..35cf549e0d 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/UserImpl.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/UserImpl.kt @@ -29,8 +29,6 @@ import io.realm.kotlin.mongodb.auth.ApiKeyAuth import io.realm.kotlin.mongodb.exceptions.CredentialsCannotBeLinkedException import io.realm.kotlin.mongodb.exceptions.ServiceException import io.realm.kotlin.mongodb.mongo.MongoClient -import io.realm.kotlin.mongodb.mongo.MongoCollection -import io.realm.kotlin.mongodb.mongo.MongoDatabase import kotlinx.coroutines.channels.Channel import org.mongodb.kbson.ExperimentalKBsonSerializerApi import org.mongodb.kbson.serialization.EJson diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt index b890b49551..f1bec538a9 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt @@ -20,12 +20,23 @@ import org.mongodb.kbson.ExperimentalKBsonSerializerApi import org.mongodb.kbson.serialization.EJson /** - * The remote MongoClient used for working with data in MongoDB remotely via Realm. + * A **Mongo client** is used to access an App Service's Data Source directly without Sync support. */ public interface MongoClient { + /** + * The name of the data source that the [MongoClient] is connecting to. + */ + // FIXME Data Source name?? public val serviceName: String + /** + * Get a [MongoDatabase] object to access data from the remote collections of the data source. + * + * @param databaseName name of the database from the data source. + * @param eJson the EJson serializer that the [MongoDatabase] should use to convert objects and + * primary keys with. Will default to the client's [EJson] instance. + */ @OptIn(ExperimentalKBsonSerializerApi::class) public fun database(databaseName: String, eJson: EJson? = null): MongoDatabase } diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt index aac5d72af7..7f4883b3e2 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt @@ -16,57 +16,73 @@ // TODO - QUESTIONS // - should we allow serialization of update, sort and projection arguments? +// - Experimental annotation as a safegaurd around all the serialization stuff? We dont support an API without Bson-serialization, so will depend on ExperimentalKBsonSerializerApi internally anyway, so can't avoid the KSerialization-dependency +// - Missing ignoreUnknown properties +// - #naming App Services seems to use "Data source", Data package io.realm.kotlin.mongodb.mongo -import io.realm.kotlin.internal.util.Validation +import io.realm.kotlin.internal.util.Validation.isType import io.realm.kotlin.mongodb.internal.MongoCollectionImpl -import io.realm.kotlin.mongodb.internal.encodeToBsonValue import io.realm.kotlin.mongodb.internal.decodeFromBsonValue import io.realm.kotlin.mongodb.internal.decodeFromBsonValueList +import io.realm.kotlin.mongodb.internal.encodeToBsonValue import org.mongodb.kbson.BsonDocument import org.mongodb.kbson.BsonValue import org.mongodb.kbson.ExperimentalKBsonSerializerApi import org.mongodb.kbson.serialization.EJson import kotlin.jvm.JvmName +/** + * A __mongo collection__ provides access to retrieve and update data from the database's + * collection with specific typed serialization. + * + * @param T the default type that remote entities of the collection will be serialized from and + * to. + * @param K the default type that primary keys will be serialized into. + */ public interface MongoCollection { + /** + * Name of the remote collection. + */ public val name: String + /** + * Get an instance of the same collection with a different set of default types serialization. + */ @OptIn(ExperimentalKBsonSerializerApi::class) public fun collection(eJson: EJson? = null): MongoCollection } public suspend fun MongoCollection<*, *>.count(filter: BsonDocument? = null, limit: Long? = null): Long { - Validation.isType>(this, "MongoCollection should be an instance of MongoCollectionImpl") + isType>(this) return count(filter, limit) } -public suspend inline fun < reified T, R: Any> MongoCollection.findOne(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null): T { - Validation.isType>(this, "MongoCollection should be an instance of MongoCollectionImpl") +public suspend inline fun < reified T, R : Any> MongoCollection.findOne(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null): T? { + isType>(this) return decodeFromBsonValue(findOne(filter, projection, sort)) } @JvmName("findOneTyped") -public suspend inline fun MongoCollection<*, *>.findOne(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null): T { +public suspend inline fun MongoCollection<*, *>.findOne(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null): T? { return (this as MongoCollection).findOne(filter, projection, sort) } -public suspend inline fun MongoCollection.find(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null, limit: Long? = null): List { - Validation.isType>(this, "MongoCollection should be an instance of MongoCollectionImpl") - val objects = find(filter, projection, sort, limit).asArray().toList() - return decodeFromBsonValueList(objects) +public suspend inline fun MongoCollection.find(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null, limit: Long? = null): List { + isType>(this) + return decodeFromBsonValueList(find(filter, projection, sort, limit).asArray().toList()) } + @JvmName("findTyped") -public suspend inline fun MongoCollection<*, *>.find(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null, limit: Long? = null ): List { +public suspend inline fun MongoCollection<*, *>.find(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null, limit: Long? = null): List { return (this as MongoCollection).find(filter, projection, sort, limit) } -public suspend inline fun MongoCollection.aggregate(pipeline: List): List { - Validation.isType>(this, "MongoCollection should be an instance of MongoCollectionImpl") - val objects: List = aggregate(pipeline) - return decodeFromBsonValueList(objects) +public suspend inline fun MongoCollection.aggregate(pipeline: List): List { + isType>(this) + return decodeFromBsonValueList(aggregate(pipeline)) } @JvmName("aggregateTyped") @@ -75,10 +91,8 @@ public suspend inline fun MongoCollection<*, *>.aggregate(pipeline: } public suspend inline fun MongoCollection.insertOne(document: T): R { - Validation.isType>(this, "MongoCollection should be an instance of MongoCollectionImpl") - val encodedDocument: BsonDocument = encodeToBsonValue(document).asDocument() - val insertedId = insertOne(encodedDocument) - return if (insertedId is R) { insertedId } else { decodeFromBsonValue(insertedId) } + isType>(this) + return decodeFromBsonValue(insertOne(encodeToBsonValue(document).asDocument())) } @JvmName("insertOneTyped") @@ -89,48 +103,44 @@ public suspend inline fun MongoCollection<*, public suspend inline fun MongoCollection.insertMany( documents: Collection, ): List { - Validation.isType>(this, "MongoCollection should be an instance of MongoCollectionImpl") - val encodedDocuments: List = documents.map { encodeToBsonValue(it).asDocument() } - val insertedIds: List = insertMany(encodedDocuments) - return if (R::class == BsonValue::class) { - insertedIds as List - } else { - insertedIds.map { decodeFromBsonValue(it) } - } + isType>(this) + return decodeFromBsonValueList(insertMany(documents.map { encodeToBsonValue(it).asDocument() })) } @JvmName("insertManyTyped") -public suspend inline fun MongoCollection<*, *>.insertMany(documents: Collection): List { +public suspend inline fun MongoCollection<*, *>.insertMany(documents: Collection): List { return (this as MongoCollection).insertMany(documents) } public suspend fun MongoCollection<*, *>.deleteOne(filter: BsonDocument): Boolean { - Validation.isType>(this, "MongoCollection should be an instance of MongoCollectionImpl") + isType>(this) return deleteOne(filter) } public suspend fun MongoCollection<*, *>.deleteMany(filter: BsonDocument): Long { - Validation.isType>(this, "MongoCollection should be an instance of MongoCollectionImpl") + isType>(this) return deleteMany(filter) } // FIXME Could just return Boolean, since matchedCount=1,modifiedCount=1 even if multiple documents should be matching :thinking: // FIXME Should we split into upsertOne, since response only contains 'upsertedId' if call has 'upsert:true` -public suspend inline fun MongoCollection.updateOne( +public suspend inline fun MongoCollection.updateOne( filter: BsonDocument, update: BsonDocument, upsert: Boolean = false -): List { - Validation.isType>(this, "MongoCollection should be an instance of MongoCollectionImpl") - return decodeFromBsonValue(updateOne(filter, update, upsert)) +): Pair { + isType>(this) + return updateOne(filter, update, upsert).let { (updated, asdf) -> + updated to asdf?.let { decodeFromBsonValue(it) } + } } @JvmName("updateOneTyped") -public suspend inline fun MongoCollection<*, *>.updateOne( +public suspend inline fun MongoCollection<*, *>.updateOne( filter: BsonDocument, update: BsonDocument, upsert: Boolean = false -): List { +): Pair { return (this as MongoCollection).updateOne(filter, update, upsert) } @@ -138,9 +148,11 @@ public suspend inline fun MongoCollection.updat filter: BsonDocument, update: BsonDocument, upsert: Boolean = false -): List { - Validation.isType>(this, "MongoCollection should be an instance of MongoCollectionImpl") - return decodeFromBsonValue(updateMany(filter, update, upsert)) +): Pair { + isType>(this) + return updateMany(filter, update, upsert).let { (updatedCount, upsertedId) -> + updatedCount to upsertedId?.let { decodeFromBsonValue(it) } + } } @JvmName("updateManyTyped") @@ -148,10 +160,11 @@ public suspend inline fun MongoCollection<*, *>.updateMany( filter: BsonDocument, update: BsonDocument, upsert: Boolean = false - ): List { +): Pair { return (this as MongoCollection).updateMany(filter, update, upsert) } +@Suppress("LongParameterList") public suspend inline fun MongoCollection.findOneAndUpdate( filter: BsonDocument, update: BsonDocument, @@ -159,11 +172,12 @@ public suspend inline fun MongoCollection.findOneAndU sort: BsonDocument? = null, upsert: Boolean = false, returnNewDoc: Boolean = false, -): T { - Validation.isType>(this, "MongoCollection should be an instance of MongoCollectionImpl") +): T? { + isType>(this) return decodeFromBsonValue(findOneAndUpdate(filter, update, projection, sort, upsert, returnNewDoc)) } +@Suppress("LongParameterList") @JvmName("findAndUpdateTyped") public suspend inline fun MongoCollection<*, *>.findOneAndUpdate( filter: BsonDocument, @@ -172,10 +186,11 @@ public suspend inline fun MongoCollection<*, *>.findOneAndUpdate( sort: BsonDocument? = null, upsert: Boolean = false, returnNewDoc: Boolean = false, -): T { +): T? { return (this as MongoCollection).findOneAndUpdate(filter, update, projection, sort, upsert, returnNewDoc) } +@Suppress("LongParameterList") public suspend inline fun MongoCollection.findOneAndReplace( filter: BsonDocument, update: BsonDocument, @@ -183,11 +198,12 @@ public suspend inline fun MongoCollection.findOneAndR sort: BsonDocument? = null, upsert: Boolean = false, returnNewDoc: Boolean = false, -): T { - Validation.isType>(this, "MongoCollection should be an instance of MongoCollectionImpl") +): T? { + isType>(this) return decodeFromBsonValue(findOneAndReplace(filter, update, projection, sort, upsert, returnNewDoc)) } +@Suppress("LongParameterList") @JvmName("findAndReplaceTyped") public suspend inline fun MongoCollection<*, *>.findOneAndReplace( filter: BsonDocument, @@ -196,7 +212,7 @@ public suspend inline fun MongoCollection<*, *>.findOneAndReplace( sort: BsonDocument? = null, upsert: Boolean = false, returnNewDoc: Boolean = false, -): T { +): T? { return (this as MongoCollection).findOneAndReplace(filter, update, projection, sort, upsert, returnNewDoc) } @@ -204,8 +220,8 @@ public suspend inline fun MongoCollection.findOneAndD filter: BsonDocument, projection: BsonDocument? = null, sort: BsonDocument? = null, -): T { - Validation.isType>(this, "MongoCollection should be an instance of MongoCollectionImpl") +): T? { + isType>(this) return decodeFromBsonValue(findOneAndDelete(filter, projection, sort)) } @@ -214,6 +230,6 @@ public suspend inline fun MongoCollection<*, *>.findOneAndDelete( filter: BsonDocument, projection: BsonDocument? = null, sort: BsonDocument? = null, -): T { +): T? { return (this as MongoCollection).findOneAndDelete(filter, projection, sort) } diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoDatabase.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoDatabase.kt index 6a1b34fb1d..f31875013a 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoDatabase.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoDatabase.kt @@ -16,11 +16,10 @@ package io.realm.kotlin.mongodb.mongo -import io.realm.kotlin.annotations.ExperimentalRealmSerializerApi +import org.mongodb.kbson.BsonDocument import org.mongodb.kbson.BsonValue import org.mongodb.kbson.ExperimentalKBsonSerializerApi import org.mongodb.kbson.serialization.EJson -import kotlin.jvm.JvmName /** * A handle to a remote **Atlas App Service database** that provides access to its [MongoCollection]s. @@ -32,10 +31,31 @@ public interface MongoDatabase { */ public val name: String - public fun collection(collectionName: String): MongoCollection + /** + * Get a [MongoCollection] that exposed methods to retrieve and update data from the database's + * collection. + * + * @param collectionName the name of the collection name that the [MongoCollection] will + * connect to. + * @return a [MongoCollection] that will accept and return entities from the remote collection + * as [BsonValue] values. + */ + public fun collection(collectionName: String): MongoCollection + /** + * Get a [MongoCollection] that exposed methods to retrieve and update data from the database's + * collection with specific typed serialization. + * + * @param collectionName the name of the collection name that the [MongoCollection] will + * connect to. + * @param eJson the EJson serializer that the [MongoCollection] should use to convert objects and + * primary keys with. Will default to the databases [EJson] instance. + * @param T the default type that remote entities of the collection will be serialized from and + * to. + * @param K the default type that primary keys will be serialized into. + * @return a [MongoCollection] that will accept and return entities from the remote collection + * as [BsonValue] values. + */ @OptIn(ExperimentalKBsonSerializerApi::class) public fun collection(collectionName: String, eJson: EJson? = null): MongoCollection - } - diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTest.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTest.kt index ece64fcf7a..e14e8d1cd5 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTest.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTest.kt @@ -14,11 +14,8 @@ * limitations under the License. */ -@file:OptIn(ExperimentalRealmSerializerApi::class, ExperimentalRealmSerializerApi::class) - package io.realm.kotlin.test.mongodb.common.mongo -import io.realm.kotlin.annotations.ExperimentalRealmSerializerApi import io.realm.kotlin.internal.platform.runBlocking import io.realm.kotlin.log.LogLevel import io.realm.kotlin.log.RealmLog @@ -45,7 +42,6 @@ import io.realm.kotlin.test.mongodb.asTestApp import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage import io.realm.kotlin.types.RealmObject import kotlinx.serialization.Serializable -import kotlinx.serialization.json.Json import org.mongodb.kbson.BsonDocument import org.mongodb.kbson.BsonString import org.mongodb.kbson.BsonValue @@ -65,7 +61,7 @@ import kotlin.test.assertTrue private const val SERVICE_NAME = "BackingDB" -@OptIn(ExperimentalRealmSerializerApi::class, ExperimentalKBsonSerializerApi::class) +@OptIn(ExperimentalKBsonSerializerApi::class) class MongoClientTest { lateinit var app: TestApp @@ -109,21 +105,37 @@ class MongoClientTest { assertEquals(SERVICE_NAME, client.serviceName) } + @Test + fun collection() = runBlocking { + val bsonCollection: MongoCollection = database.collection("SyncDogIntId") + + // Default argument issues default return type + assertIs(bsonCollection.insertOne(BsonDocument("dog-1", Random.nextInt()))) + assertIs(bsonCollection.findOne()) + + // Cannot resolve return type because not using explicit typed insertOne + // val value2 = bsonCollection.insertOne(SyncDogIntId("dog-2", Random.nextInt())) + + // Explicit combinations returns what they should + val value2: BsonValue = bsonCollection.insertOne(SyncDogIntId("dog-2", Random.nextInt())) + assertIs(value2) + val value3: Int = bsonCollection.insertOne(SyncDogIntId("dog-2", Random.nextInt())) + assertIs(value3) + assertIs(bsonCollection.findOne()) + + // Converting to typed collection changes defaults + val typedCollection = bsonCollection.collection() + assertIs(typedCollection.insertOne(SyncDogIntId("dog-1", Random.nextInt()))) + assertIs(typedCollection.findOne()) + + // TODO eJson + } + @Test fun count() = runBlocking { - RealmLog.level = LogLevel.ALL - val actual = collection.count() - assertEquals(0, actual) + assertEquals(0, collection.count()) - app.asTestApp.run { - (1..10).forEach { - insertDocument( - "SyncDog", - Json.encodeToString(SyncDog.serializer(), SyncDog("dog-${it % 5}")) - ) - } - assertEquals(10, countDocuments("SyncDog")) - } + collection.insertMany((1..10).map { SyncDog("dog-${it % 5}") }) assertEquals(10, collection.count()) assertEquals(5, collection.count(limit = 5)) @@ -144,101 +156,47 @@ class MongoClientTest { @Test fun findOne() = runBlocking { - val actual = collection.findOne() - assertNull(actual) - val actual1 = collection.findOne() - assertNull(actual1) + RealmLog.level = LogLevel.ALL + assertNull(collection.findOne()) + assertNull(collection.findOne()) - app.asTestApp.run { - (1..10).forEach { - insertDocument( - "SyncDog", - Json.encodeToString(SyncDog.serializer(), SyncDog("dog-${it % 5}")) - ) - } - assertEquals(10, countDocuments("SyncDog")) - } + collection.insertMany((1..10).map { SyncDog("dog-${it % 5}") }) // No match - assertNull(collection.findOne(filter = BsonDocument("name", "cat"))) + assertNull(collection.findOne(filter = BsonDocument("name", "cat"))) // Only on document even though multiple matches assertIs(collection.findOne(filter = BsonDocument("name", "dog-0"))) + collection.findOne(filter = BsonDocument("name", "dog-0")).run { + assertEquals("dog-0", get("name")!!.asString().value) + } + // Projection select field // Limit // Sort val y: BsonDocument = collection.findOne(filter = BsonDocument("name", "dog-0")) - val y2: BsonValue = collection.findOne(filter = BsonDocument("name", "dog-0")) - println(y) - println(y2) + + // argument type differentiation + // - serialization issues + // - missing fields + // - too many fields + // return type differentiation + // - serialization issues + // - missing fields + // - too many fields + // parameters + // - ill formatted parameters + + // state differentiation + // - empty collection + // - } @OptIn(ExperimentalKBsonSerializerApi::class) // @Test -// fun findOne2() = runBlocking { -// val dog = SyncDog("dog-1") -// val dog1 = collection.findOne() -// assertNull(dog1) -// -// app.asTestApp.run { -// (1..10).forEach { -// insertDocument( -// "SyncDog", -// Json.encodeToString(SyncDog.serializer(), SyncDog("dog-${it % 5}")) -// ) -// } -// assertEquals(10, countDocuments("SyncDog")) -// } -// -// // No match -// assertNull(collection.findOne(filter = BsonDocument("name" , "cat"))) -// -// // Only on document even though multiple matches -// assertIs(collection.findOne(filter = BsonDocument("name", "dog-0"))) -// -// val dog2: SyncDog = collection.findOne { -// filter("""{ "name": "dog-1" }""") -//// arg(SyncDog()) -//// filter(BsonDocument("""{ "name": "dog-1" }""")) -//// filter(BsonDocument("asdf")) -//// projection(SyncDog::members) -// -//// sort() -// } -// assertEquals(dog, dog2) -// -// val dog3: SyncDog = collection.findOne { -// filter(BsonDocument("name" to BsonString("dog-1"))) -//// arg(SyncDog()) -//// filter(BsonDocument("""{ "name": "dog-1" }""")) -//// filter(BsonDocument("asdf")) -//// projection(SyncDog::members) -// -//// sort() -// } -// assertEquals(dog, dog3) -// -// @Serializable -// data class Filter(val name: String) -// -// val dog4: SyncDog = collection.findOne { -// filter(Filter("dog-1")) -//// arg(SyncDog()) -//// filter(BsonDocument("""{ "name": "dog-1" }""")) -//// filter(BsonDocument("asdf")) -//// projection(SyncDog::members) -// -//// sort() -// } -// assertEquals(dog, dog4) -// -//// val dog5 = collection.findOne { -//// query = """{ "name": "dog-1" }""" -//// query = BsonDocument("""{ "name": "dog-1" }""") -//// } -// } +// fun findOne() = runBlocking { // with(getCollectionInternal()) { // // Test findOne() on empty collection with no filter and no options // assertNull(findOne().get()) @@ -311,16 +269,12 @@ class MongoClientTest { // } // } // -// @Test -// fun findOne_fails() { -// with(getCollectionInternal()) { -// assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { -// findOne(Document("\$who", 1)).get() -// }.also { e -> -// assertTrue(e.errorMessage!!.contains("operator", true)) -// } -// } -// } + @Test + fun findOne_fails() = runBlocking { + assertFailsWithMessage("operator") { + collection.findOne(BsonDocument("\$who", 1)) + } + } @Test fun find() = runBlocking { @@ -330,6 +284,8 @@ class MongoClientTest { val x: List = collection.insertMany(listOf(SyncDog("dog1"), SyncDog("dog2"))) assertEquals(2, collection.find().size) + // + collection.find(filter = null, projection = null, sort = null, limit = null) } // @@ -407,7 +363,6 @@ class MongoClientTest { val x: List = collection.insertMany(listOf(SyncDog(name = "dog1"), SyncDog(name = "dog2"))) collection.aggregate(listOf()) - collection.aggregate(listOf(BsonDocument("\$sort", BsonDocument("name", -1)), BsonDocument("\$limit", 1))) } // @Test @@ -464,7 +419,6 @@ class MongoClientTest { // } // } - @Test fun insertOne() = runBlocking { // with(getCollectionInternal()) { @@ -485,14 +439,14 @@ class MongoClientTest { // Option 2 - Explicit generic arguments to enabling fluent API val id = collection.insertOne(SyncDog("sadf")) - println(id) // Option 3 - Automatically serialized object val x2: ObjectId = collection.insertOne(SyncDog("sadf")) - println(x2) val x3: ObjectId = collection.insertOne(BsonDocument("""{ "name" : "asdf" }""")) - println(x2) + + // TODO + // Existing primary key object } // @@ -544,14 +498,12 @@ class MongoClientTest { assertFailsWithMessage("dup key") { val z: List = syncDogIntIdCollection.insertMany(listOf(elements)) } - println(x) - println(y) val typedCollection = collection.collection() val z: List = typedCollection.insertMany(listOf(SyncDog("sadf"))) val tyz = typedCollection.insertMany(listOf(SyncDog("sadf"))) - val bsonSyncDogs: MongoCollection = database.collection("SyncDog") + val bsonSyncDogs: MongoCollection = database.collection("SyncDog") val insertMany /*: List */ = bsonSyncDogs.insertMany(listOf(BsonDocument("name", "x"))) val syncDogs: MongoCollection = database.collection("SyncDog") @@ -791,28 +743,53 @@ class MongoClientTest { @Test fun updateOne() = runBlocking { // Argument wrapper DSL - RealmLog.level = LogLevel.ALL + assertEquals(0, collection.count()) + RealmLog.level = LogLevel.ALL val elements = SyncDog("x") - assertEquals(2, collection.insertMany(listOf(elements, elements)).size) - assertEquals(2, collection.count(filter = BsonDocument("""{"name": "x"}"""))) + assertEquals( + 4, + collection.insertMany( + listOf(elements, elements, elements, elements) + ).size + ) + assertEquals(4, collection.count()) - // A bit ann - collection.updateOne( + // update no match + val updateWithoutMatch = collection.updateOne( + BsonDocument("""{ "name": "NOMATCH"}"""), + BsonDocument("""{ "name": "UPDATED"}"""), + ) + assertEquals(false to null, updateWithoutMatch) + + // update with match match + val updateWithMatch = collection.updateOne( BsonDocument("""{ "name": "x"}"""), BsonDocument("""{ "name": "y"}"""), - true ) -// assertEquals(2, collection.deleteMany(BsonDocument("""{ "name": "x" }""" )) ) - -// assertEquals(3, collection.insertMany(listOf(elements, elements, elements)).size) -// assertEquals(3, collection.deleteMany(BsonDocument()) ) + assertEquals(true to null, updateWithMatch) + assertEquals(4, collection.count()) + assertEquals(3, collection.count(filter = BsonDocument("""{"name": "x"}"""))) + assertEquals(1, collection.count(filter = BsonDocument("""{"name": "y"}"""))) + + // upsert no match + val upsertWithoutMatch = collection.updateOne( + BsonDocument("""{ "name": "z"}"""), BsonDocument(""" { "name": "y"}"""), upsert = true + ) + upsertWithoutMatch.let { (updated, upsertedId) -> + assertFalse(updated) + assertIs(upsertedId) + } + assertEquals(5, collection.count()) + assertEquals(2, collection.count(filter = BsonDocument("""{"name": "y"}"""))) - collection.updateOne( - BsonDocument("""{ "name": "z"}"""), - BsonDocument(""" { "name": "y"}"""), - upsert = true + // upsert with match + val upsertWithMatch = collection.updateOne( + BsonDocument("""{ "name": "y"}"""), BsonDocument(""" { "name": "z"}"""), upsert = true ) + assertEquals(true to null, upsertWithMatch) + assertEquals(5, collection.count()) + assertEquals(1, collection.count(filter = BsonDocument("""{"name": "y"}"""))) } // @Test @@ -875,6 +852,7 @@ class MongoClientTest { // @Test fun updateMany() = runBlocking { + assertEquals(0, collection.count()) RealmLog.level = LogLevel.ALL assertEquals( 4, @@ -887,12 +865,41 @@ class MongoClientTest { ) ).size ) - assertEquals(2, collection.count(filter = BsonDocument("""{"name": "x"}"""))) - collection.updateMany( - BsonDocument("""{"name": "x2"}"""), - BsonDocument("""{"name": "x1"}"""), - true + assertEquals(4, collection.count()) + val updateWithoutMatch = collection.updateMany( + BsonDocument("""{"name": "NOMATCH"}"""), + BsonDocument("""{"name": "UPDATED"}"""), ) + assertEquals(0L to null, updateWithoutMatch) + assertEquals(0, collection.count(filter = BsonDocument("""{"name": "UPDATED"}"""))) + assertEquals(4, collection.count()) + + // update with match match + val updateWithMatch = collection.updateMany( + BsonDocument("""{ "name": "x"}"""), + BsonDocument("""{ "name": "UPDATED"}"""), + ) + assertEquals(2L to null, updateWithMatch) + assertEquals(2, collection.count(filter = BsonDocument("""{"name": "UPDATED"}"""))) + assertEquals(4, collection.count()) + + // upsert no match + val upsertWithoutMatch = collection.updateMany( + BsonDocument("""{ "name": "NOMATCH"}"""), BsonDocument(""" { "name": "UPSERTED"}"""), upsert = true + ) + upsertWithoutMatch.let { + assertEquals(0, it.first) + assertIs(it.second) + } + assertEquals(5, collection.count()) + assertEquals(1, collection.count(filter = BsonDocument("""{"name": "UPSERTED"}"""))) + // upsert with match + val upsertWithMatch = collection.updateMany( + BsonDocument("""{ "name": "y"}"""), BsonDocument(""" { "name": "z"}"""), upsert = true + ) + assertEquals(1L to null, upsertWithMatch) + assertEquals(5, collection.count()) + assertEquals(0, collection.count(filter = BsonDocument("""{"name": "y"}"""))) } // @Test @@ -1165,7 +1172,6 @@ class MongoClientTest { BsonDocument("""{ "name": "dog1" }"""), upsert = true ) - println(x) } // // @Test @@ -1396,7 +1402,6 @@ class MongoClientTest { // assertNull(findOne(Document("team", "Cuddly Zebras")).get()) // } // } - } @Serializable diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/utils/Utils.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/utils/Utils.kt index d8a4e05b8a..313da75de7 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/utils/Utils.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/utils/Utils.kt @@ -16,11 +16,7 @@ package io.realm.kotlin.test.mongodb.common.utils import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.async -import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.withContext -import kotlin.coroutines.CoroutineContext import kotlin.reflect.KClass import kotlin.test.assertFailsWith From a6a1fb0e2f375821e6fda539f5103c8c7a60603f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Fri, 8 Dec 2023 15:42:56 +0100 Subject: [PATCH 06/36] Clean up internal function call --- .../realm/kotlin/mongodb/ext/FunctionsExt.kt | 66 ++++--------------- .../kotlin/mongodb/internal/FunctionsImpl.kt | 9 ++- .../mongodb/internal/MongoClientImpl.kt | 2 + .../mongodb/internal/MongoCollectionImpl.kt | 15 ++--- .../kotlin/mongodb/mongo/MongoCollection.kt | 16 ++--- 5 files changed, 36 insertions(+), 72 deletions(-) diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/FunctionsExt.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/FunctionsExt.kt index 711f23e528..d387dbb624 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/FunctionsExt.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/FunctionsExt.kt @@ -58,7 +58,7 @@ public suspend inline fun Functions.call( vararg args: Any? ): T = with(this as FunctionsImpl) { val serializedEjsonArgs = Bson.toJson(BsonEncoder.encodeToBsonValue(args.toList())) - val encodedResult = callInternal(name, serializedEjsonArgs = serializedEjsonArgs) + val encodedResult = callInternal(name, serializedEjsonArgs) BsonEncoder.decodeFromBsonValue( resultClass = T::class, @@ -90,52 +90,26 @@ public suspend inline fun Functions.call( * @param T the function return value type. * @return result of the function call. */ - @ExperimentalRealmSerializerApi @OptIn(ExperimentalKBsonSerializerApi::class) -public suspend fun Functions.call2( +public suspend inline fun Functions.call( name: String, callBuilderBlock: CallBuilder.() -> Unit -): Pair, String> = - with(this as FunctionsImpl) { - val builder = CallBuilder(app.configuration.ejson) - builder - .apply(callBuilderBlock) - .run { - val serializedEjsonArgs: String = Bson.toJson(arguments) +): T = with(this as FunctionsImpl) { + CallBuilder(app.configuration.ejson) + .apply(callBuilderBlock) + .run { + val serializedEjsonArgs = Bson.toJson(arguments) - val encodedResult: String = callInternal(name, serviceName = serviceName, serializedEjsonArgs = serializedEjsonArgs) + val encodedResult = callInternal(name, serializedEjsonArgs) - this to encodedResult - } - } + val returnValueSerializer = + returnValueSerializer + ?: ejson.serializersModule.serializerOrRealmBuiltInSerializer() -@ExperimentalRealmSerializerApi -@OptIn(ExperimentalKBsonSerializerApi::class) -public suspend inline fun Functions.call( - name: String, - noinline callBuilderBlock: CallBuilder.() -> Unit -): T = call2(name, callBuilderBlock).run { - first.ejson.decodeFromString(first.returnValueSerializer ?: first.ejson.serializersModule.serializerOrRealmBuiltInSerializer(), second) + ejson.decodeFromString(returnValueSerializer, encodedResult) + } } -// with(this as FunctionsImpl) { -// -// CallBuilder(app.configuration.ejson) -// .apply(callBuilderBlock) -// .run { -// val serializedEjsonArgs: String = Bson.toJson(arguments) -// -// val encodedResult: String = callInternal(name, serviceName = serviceName, serializedEjsonArgs = serializedEjsonArgs) -// -// val returnValueSerializer: KSerializer = -// returnValueSerializer -// ?: ejson.serializersModule.serializerOrRealmBuiltInSerializer() -// -// val r: T = ejson.decodeFromString(returnValueSerializer, encodedResult) -// r -// } - -// } /** * Builder used to construct a call defining serializers for the different arguments and return value. @@ -148,12 +122,6 @@ internal constructor( @PublishedApi internal val ejson: EJson, ) { - /** - * The name of the App Services's data source for MongoClient requests. - */ - @PublishedApi - internal var serviceName: String? = null - /** * Contains all given arguments transformed as [BsonValue]. The encoding is done on each [add] call * as in that context we have type information from the reified type. @@ -199,12 +167,4 @@ internal constructor( public inline fun add(argument: T, serializer: KSerializer) { arguments.add(ejson.encodeToBsonValue(serializer, argument)) } - -// public fun entry(key: String, value: String) { -// entries.put(key, value) -// } - @PublishedApi - internal fun serviceName(serviceName: String) { - this.serviceName = serviceName - } } diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/FunctionsImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/FunctionsImpl.kt index 2ae96646ad..98f3d21b49 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/FunctionsImpl.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/FunctionsImpl.kt @@ -19,16 +19,18 @@ import io.realm.kotlin.internal.interop.RealmInterop import io.realm.kotlin.internal.util.use import io.realm.kotlin.mongodb.Functions import kotlinx.coroutines.channels.Channel +import org.mongodb.kbson.BsonValue +import org.mongodb.kbson.serialization.Bson @PublishedApi internal class FunctionsImpl( override val app: AppImpl, - override val user: UserImpl + override val user: UserImpl, + val serviceName: String? = null, ) : Functions { @PublishedApi internal suspend fun callInternal( name: String, - serviceName: String? = null, serializedEjsonArgs: String ): String = Channel>(1).use { channel -> RealmInterop.realm_app_call_function( @@ -46,4 +48,7 @@ internal class FunctionsImpl( return channel.receive().getOrThrow() } + + internal suspend fun callInternal(name: String, bsonValue: BsonValue): BsonValue = + Bson(callInternal(name, Bson.toJson(bsonValue))) } diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoClientImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoClientImpl.kt index b746529ca2..bc3b4e3cd1 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoClientImpl.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoClientImpl.kt @@ -29,6 +29,8 @@ internal class MongoClientImpl( val eJson: EJson, ) : MongoClient { + val functions = FunctionsImpl(user.app, user, serviceName) + override fun database(databaseName: String, eJson: EJson?): MongoDatabase = MongoDatabaseImpl(this, databaseName, eJson ?: this.eJson) } diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt index 381f553f2d..f7c2e90d38 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt @@ -18,7 +18,6 @@ package io.realm.kotlin.mongodb.internal import io.realm.kotlin.annotations.ExperimentalRealmSerializerApi import io.realm.kotlin.mongodb.exceptions.ServiceException -import io.realm.kotlin.mongodb.ext.call import io.realm.kotlin.mongodb.mongo.MongoCollection import org.mongodb.kbson.BsonArray import org.mongodb.kbson.BsonBoolean @@ -41,7 +40,7 @@ internal class MongoCollectionImpl constructor( val client = this.database.client val user = client.user - val functions = user.functions + val functions = client.functions private val defaults: Map = mapOf( "database" to BsonString(database.name), @@ -53,13 +52,11 @@ internal class MongoCollectionImpl constructor( return MongoCollectionImpl(this.database, this.name, eJson ?: this.eJson) } - private suspend inline fun call(name: String, crossinline document: MutableMap.() -> Unit): R { - return user.functions.call(name) { - serviceName(client.serviceName) - val doc = defaults.toMutableMap() - document(doc) - add(doc) - } + private suspend inline fun call(name: String, crossinline arguments: MutableMap.() -> Unit): R { + val doc = defaults.toMutableMap() + arguments(doc) + val response = functions.callInternal(name, BsonArray(listOf(BsonDocument(doc)))) + return decodeFromBsonValue(response) } @PublishedApi diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt index 7f4883b3e2..209da2ed90 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt @@ -60,13 +60,13 @@ public suspend fun MongoCollection<*, *>.count(filter: BsonDocument? = null, lim return count(filter, limit) } -public suspend inline fun < reified T, R : Any> MongoCollection.findOne(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null): T? { +public suspend inline fun < reified T, R : Any> MongoCollection.findOne(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null): T { isType>(this) return decodeFromBsonValue(findOne(filter, projection, sort)) } @JvmName("findOneTyped") -public suspend inline fun MongoCollection<*, *>.findOne(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null): T? { +public suspend inline fun MongoCollection<*, *>.findOne(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null): T { return (this as MongoCollection).findOne(filter, projection, sort) } @@ -172,7 +172,7 @@ public suspend inline fun MongoCollection.findOneAndU sort: BsonDocument? = null, upsert: Boolean = false, returnNewDoc: Boolean = false, -): T? { +): T { isType>(this) return decodeFromBsonValue(findOneAndUpdate(filter, update, projection, sort, upsert, returnNewDoc)) } @@ -186,7 +186,7 @@ public suspend inline fun MongoCollection<*, *>.findOneAndUpdate( sort: BsonDocument? = null, upsert: Boolean = false, returnNewDoc: Boolean = false, -): T? { +): T { return (this as MongoCollection).findOneAndUpdate(filter, update, projection, sort, upsert, returnNewDoc) } @@ -198,7 +198,7 @@ public suspend inline fun MongoCollection.findOneAndR sort: BsonDocument? = null, upsert: Boolean = false, returnNewDoc: Boolean = false, -): T? { +): T { isType>(this) return decodeFromBsonValue(findOneAndReplace(filter, update, projection, sort, upsert, returnNewDoc)) } @@ -212,7 +212,7 @@ public suspend inline fun MongoCollection<*, *>.findOneAndReplace( sort: BsonDocument? = null, upsert: Boolean = false, returnNewDoc: Boolean = false, -): T? { +): T { return (this as MongoCollection).findOneAndReplace(filter, update, projection, sort, upsert, returnNewDoc) } @@ -220,7 +220,7 @@ public suspend inline fun MongoCollection.findOneAndD filter: BsonDocument, projection: BsonDocument? = null, sort: BsonDocument? = null, -): T? { +): T { isType>(this) return decodeFromBsonValue(findOneAndDelete(filter, projection, sort)) } @@ -230,6 +230,6 @@ public suspend inline fun MongoCollection<*, *>.findOneAndDelete( filter: BsonDocument, projection: BsonDocument? = null, sort: BsonDocument? = null, -): T? { +): T { return (this as MongoCollection).findOneAndDelete(filter, projection, sort) } From f4a35f940efec6a68f067183c84b815c99cafacd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Wed, 13 Dec 2023 11:37:42 +0100 Subject: [PATCH 07/36] Fix nullability of findOne-variants and add custom serialization tests --- .../kotlin/internal/platform/RealmObject.kt | 1 + .../kotlin/internal/platform/RealmObject.kt | 1 + .../kotlin/internal/platform/RealmObject.kt | 1 + .../mongodb/exceptions/ServiceExceptions.kt | 4 +- .../mongodb/internal/MongoCollectionImpl.kt | 86 +++++++----- .../mongodb/internal/MongoDatabaseImpl.kt | 4 +- .../realm/kotlin/mongodb/mongo/MongoClient.kt | 9 ++ .../kotlin/mongodb/mongo/MongoCollection.kt | 32 +++-- .../kotlin/test/mongodb/common/UserTests.kt | 55 ++++++++ .../mongodb/common/mongo/MongoClientTests.kt | 124 +++++++++++++++++ ...oClientTest.kt => MongoCollectionTests.kt} | 130 ++++++++++++++---- .../common/mongo/MongoDatabaseTests.kt | 88 ++++++++++++ 12 files changed, 452 insertions(+), 83 deletions(-) create mode 100644 packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTests.kt rename packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/{MongoClientTest.kt => MongoCollectionTests.kt} (93%) create mode 100644 packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoDatabaseTests.kt diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/platform/RealmObject.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/platform/RealmObject.kt index 969b2e7ba0..f9cbb727ec 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/platform/RealmObject.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/platform/RealmObject.kt @@ -30,4 +30,5 @@ internal expect fun realmObjectCompanionOrNull(clazz: KClass): Real /** * Returns the [RealmObjectCompanion] associated with a given [BaseRealmObject]'s [KClass]. */ +@PublishedApi internal expect fun realmObjectCompanionOrThrow(clazz: KClass): RealmObjectCompanion diff --git a/packages/library-base/src/jvm/kotlin/io/realm/kotlin/internal/platform/RealmObject.kt b/packages/library-base/src/jvm/kotlin/io/realm/kotlin/internal/platform/RealmObject.kt index 0bf754fa9d..1ac17f0b7f 100644 --- a/packages/library-base/src/jvm/kotlin/io/realm/kotlin/internal/platform/RealmObject.kt +++ b/packages/library-base/src/jvm/kotlin/io/realm/kotlin/internal/platform/RealmObject.kt @@ -28,6 +28,7 @@ internal actual fun realmObjectCompanionOrNull(clazz: KClass): Real clazz.companionObjectInstance as RealmObjectCompanion } else null +@PublishedApi internal actual fun realmObjectCompanionOrThrow(clazz: KClass): RealmObjectCompanion = realmObjectCompanionOrNull(clazz) ?: error("Couldn't find companion object of class '${clazz.simpleName}'.\nA common cause for this is when the `io.realm.kotlin` is not applied to the Gradle module that contains the '${clazz.simpleName}' class.") diff --git a/packages/library-base/src/nativeDarwin/kotlin/io/realm/kotlin/internal/platform/RealmObject.kt b/packages/library-base/src/nativeDarwin/kotlin/io/realm/kotlin/internal/platform/RealmObject.kt index 455cb38dac..a59ce11869 100644 --- a/packages/library-base/src/nativeDarwin/kotlin/io/realm/kotlin/internal/platform/RealmObject.kt +++ b/packages/library-base/src/nativeDarwin/kotlin/io/realm/kotlin/internal/platform/RealmObject.kt @@ -29,6 +29,7 @@ internal actual fun realmObjectCompanionOrNull(clazz: KClass): Real else -> null } +@PublishedApi internal actual fun realmObjectCompanionOrThrow(clazz: KClass): RealmObjectCompanion = realmObjectCompanionOrNull(clazz) ?: error("Couldn't find companion object of class '${clazz.simpleName}'.\nA common cause for this is when the `io.realm.kotlin` is not applied to the Gradle module that contains the '${clazz.simpleName}' class.") diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/exceptions/ServiceExceptions.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/exceptions/ServiceExceptions.kt index 1b8b10feaf..1befe047f5 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/exceptions/ServiceExceptions.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/exceptions/ServiceExceptions.kt @@ -20,8 +20,8 @@ import io.realm.kotlin.internal.interop.CodeDescription /** * This exception is considered the top-level or "catch-all" for problems related to HTTP requests - * made towards App Services. This covers both HTTP transport problems, problems passing JSON - * or the server considering the request invalid, for whatever reason. + * made towards App Services. This covers both HTTP transport problems, or the server considering + * the request invalid, for whatever reason. * * Generally, reacting to this exception will be hard, except to log the error for further * analysis. But in many cases a more specific subtype will be thrown, which will be easier to diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt index f7c2e90d38..c5433b5729 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt @@ -23,6 +23,7 @@ import org.mongodb.kbson.BsonArray import org.mongodb.kbson.BsonBoolean import org.mongodb.kbson.BsonDocument import org.mongodb.kbson.BsonInt64 +import org.mongodb.kbson.BsonNull import org.mongodb.kbson.BsonString import org.mongodb.kbson.BsonValue import org.mongodb.kbson.ExperimentalKBsonSerializerApi @@ -30,50 +31,67 @@ import org.mongodb.kbson.serialization.EJson import org.mongodb.kbson.serialization.decodeFromBsonValue import org.mongodb.kbson.serialization.encodeToBsonValue -@OptIn(ExperimentalKBsonSerializerApi::class, ExperimentalRealmSerializerApi::class) @PublishedApi -internal class MongoCollectionImpl constructor( - @PublishedApi internal val database: MongoDatabaseImpl, - override val name: String, - val eJson: EJson, -) : MongoCollection { - - val client = this.database.client - val user = client.user - val functions = client.functions - - private val defaults: Map = mapOf( +@OptIn(ExperimentalKBsonSerializerApi::class) +internal class MongoDatabaseCollection(@PublishedApi internal val database: MongoDatabaseImpl, val name: String, eJson: EJson) : MongoCollectionImpl(database.client.functions, eJson) { + override val defaults: Map = mapOf( "database" to BsonString(database.name), "collection" to BsonString(name), ) + @OptIn(ExperimentalKBsonSerializerApi::class) + override fun collection(eJson: EJson?): MongoCollection { + return MongoDatabaseCollection(this.database, this.name, eJson ?: this.eJson) + } +} +@PublishedApi +@OptIn(ExperimentalKBsonSerializerApi::class) +internal class MongoClientCollection(@PublishedApi internal val clientImpl: MongoClientImpl, val schemaName: String, eJson: EJson) : MongoCollectionImpl(clientImpl.functions, eJson) { + override val defaults: Map = mapOf( + "schema_name" to BsonString(schemaName), + ) @OptIn(ExperimentalKBsonSerializerApi::class) override fun collection(eJson: EJson?): MongoCollection { - return MongoCollectionImpl(this.database, this.name, eJson ?: this.eJson) + return MongoClientCollection(clientImpl, schemaName, eJson ?: this.eJson) } +} - private suspend inline fun call(name: String, crossinline arguments: MutableMap.() -> Unit): R { +@OptIn(ExperimentalKBsonSerializerApi::class, ExperimentalRealmSerializerApi::class) +@PublishedApi +internal abstract class MongoCollectionImpl constructor( + val functions: FunctionsImpl, + val eJson: EJson, +) : MongoCollection { + + // Default entries for the argument document submitted for the function call. + abstract val defaults: Map + + private suspend fun call(name: String, arguments: MutableMap.() -> Unit): BsonValue { val doc = defaults.toMutableMap() arguments(doc) - val response = functions.callInternal(name, BsonArray(listOf(BsonDocument(doc)))) - return decodeFromBsonValue(response) + val argument = BsonDocument(doc) + return functions.callInternal(name, BsonArray(listOf(argument))) } @PublishedApi internal suspend fun count(filter: BsonDocument? = null, limit: Long? = null): Long { - return call("count") { - filter?.let { put("query", it) } - limit?.let { put("limit", BsonInt64(it)) } - } + return decodeFromBsonValue( + call("count") { + filter?.let { put("query", it) } + limit?.let { put("limit", BsonInt64(it)) } + } + ) } @PublishedApi - internal suspend fun findOne(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null): BsonValue = - call("findOne") { + internal suspend fun findOne(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null): BsonValue { + val call: BsonValue = call("findOne") { filter?.let { put("query", it) } projection?.let { put("projection", it) } sort?.let { put("sort", it) } } + return call + } @PublishedApi internal suspend fun find(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null, limit: Long? = null): BsonValue = @@ -86,21 +104,21 @@ internal class MongoCollectionImpl constructor( @PublishedApi internal suspend fun aggregate(pipeline: List): List = - call("aggregate") { put("pipeline", BsonArray(pipeline)) }.asArray().toList() + call("aggregate") { put("pipeline", BsonArray(pipeline)) }.asArray().toList() @PublishedApi internal suspend fun insertOne(document: BsonDocument): BsonValue = - call("insertOne") { put("document", document) }.asDocument()["insertedId"]!! + call("insertOne") { put("document", document) }.asDocument()["insertedId"]!! @PublishedApi internal suspend fun insertMany(documents: List): List = - call("insertMany") { + call("insertMany") { put("documents", BsonArray(documents)) }.asDocument()["insertedIds"]!!.asArray().toList() @PublishedApi internal suspend fun deleteOne(filter: BsonDocument): Boolean { - val deletedCountBson = call("deleteOne") { + val deletedCountBson = call("deleteOne") { put("query", filter) }.asDocument()["deletedCount"]!! val deletedCount = decodeFromBsonValue(deletedCountBson) @@ -113,7 +131,7 @@ internal class MongoCollectionImpl constructor( @PublishedApi internal suspend fun deleteMany(filter: BsonDocument): Long { - val deletedCountBson = call("deleteMany") { + val deletedCountBson = call("deleteMany") { put("query", filter) }.asDocument()["deletedCount"]!! return decodeFromBsonValue(deletedCountBson) @@ -139,7 +157,7 @@ internal class MongoCollectionImpl constructor( @PublishedApi internal suspend fun updateMany(filter: BsonDocument, update: BsonDocument, upsert: Boolean = false): Pair { - val response = call("updateMany") { + val response = call("updateMany") { put("query", filter) put("update", update) put("upsert", BsonBoolean(upsert)) @@ -202,15 +220,15 @@ internal class MongoCollectionImpl constructor( internal inline fun MongoCollectionImpl<*, *>.encodeToBsonValue(value: R): BsonValue { return eJson.encodeToBsonValue(value) } + @OptIn(ExperimentalKBsonSerializerApi::class) @PublishedApi -internal inline fun MongoCollectionImpl<*, *>.decodeFromBsonValue(bsonValue: BsonValue): R { - return if (R::class == BsonValue::class) { - bsonValue as R - } else { - eJson.decodeFromBsonValue(bsonValue) +internal inline fun MongoCollectionImpl<*, *>.decodeFromBsonValue(bsonValue: BsonValue): R = + when { + bsonValue == BsonNull -> null as R + R::class == BsonValue::class -> bsonValue as R + else -> eJson.decodeFromBsonValue(bsonValue) } -} @OptIn(ExperimentalKBsonSerializerApi::class) @PublishedApi diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDatabaseImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDatabaseImpl.kt index 4053f17066..85f525cb9b 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDatabaseImpl.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDatabaseImpl.kt @@ -33,9 +33,9 @@ internal class MongoDatabaseImpl constructor( ) : MongoDatabase { override fun collection(collectionName: String): MongoCollection { - return MongoCollectionImpl(this, collectionName, this.eJson) + return MongoDatabaseCollection(this, collectionName, this.eJson) } override fun collection(collectionName: String, eJson: EJson?): MongoCollection = - MongoCollectionImpl(this, collectionName, eJson ?: this.eJson) + MongoDatabaseCollection(this, collectionName, eJson ?: this.eJson) } diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt index f1bec538a9..fd1c70e2e6 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt @@ -16,6 +16,9 @@ package io.realm.kotlin.mongodb.mongo +import io.realm.kotlin.mongodb.internal.MongoClientCollection +import io.realm.kotlin.mongodb.internal.MongoClientImpl +import io.realm.kotlin.types.BaseRealmObject import org.mongodb.kbson.ExperimentalKBsonSerializerApi import org.mongodb.kbson.serialization.EJson @@ -40,3 +43,9 @@ public interface MongoClient { @OptIn(ExperimentalKBsonSerializerApi::class) public fun database(databaseName: String, eJson: EJson? = null): MongoDatabase } + +@OptIn(ExperimentalKBsonSerializerApi::class) +public inline fun MongoClient.collection(eJson: EJson? = null): MongoCollection { + @Suppress("invisible_reference", "invisible_member") + return MongoClientCollection(this as MongoClientImpl, io.realm.kotlin.internal.platform.realmObjectCompanionOrThrow(T::class).io_realm_kotlin_className, eJson ?: this.eJson) +} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt index 209da2ed90..ab111beb6d 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt @@ -17,8 +17,8 @@ // TODO - QUESTIONS // - should we allow serialization of update, sort and projection arguments? // - Experimental annotation as a safegaurd around all the serialization stuff? We dont support an API without Bson-serialization, so will depend on ExperimentalKBsonSerializerApi internally anyway, so can't avoid the KSerialization-dependency -// - Missing ignoreUnknown properties // - #naming App Services seems to use "Data source", Data +// - What about missing fields in server response -> ServiceException? package io.realm.kotlin.mongodb.mongo @@ -43,10 +43,11 @@ import kotlin.jvm.JvmName */ public interface MongoCollection { - /** - * Name of the remote collection. - */ - public val name: String + // FIXME Remove? as we don't have a name when the collection is obtained from a schema name (directly from MongoClient +// /** +// * Name of the remote collection. +// */ +// public val name: String /** * Get an instance of the same collection with a different set of default types serialization. @@ -60,14 +61,15 @@ public suspend fun MongoCollection<*, *>.count(filter: BsonDocument? = null, lim return count(filter, limit) } -public suspend inline fun < reified T, R : Any> MongoCollection.findOne(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null): T { +public suspend inline fun < reified T, R : Any> MongoCollection.findOne(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null): T? { isType>(this) - return decodeFromBsonValue(findOne(filter, projection, sort)) + val bsonValue: BsonValue? = findOne(filter, projection, sort) + return decodeFromBsonValue(bsonValue!!) } @JvmName("findOneTyped") -public suspend inline fun MongoCollection<*, *>.findOne(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null): T { - return (this as MongoCollection).findOne(filter, projection, sort) +public suspend inline fun MongoCollection<*, *>.findOne(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null): T? { + return (this as MongoCollection).findOne(filter, projection, sort) } public suspend inline fun MongoCollection.find(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null, limit: Long? = null): List { @@ -172,7 +174,7 @@ public suspend inline fun MongoCollection.findOneAndU sort: BsonDocument? = null, upsert: Boolean = false, returnNewDoc: Boolean = false, -): T { +): T? { isType>(this) return decodeFromBsonValue(findOneAndUpdate(filter, update, projection, sort, upsert, returnNewDoc)) } @@ -186,7 +188,7 @@ public suspend inline fun MongoCollection<*, *>.findOneAndUpdate( sort: BsonDocument? = null, upsert: Boolean = false, returnNewDoc: Boolean = false, -): T { +): T? { return (this as MongoCollection).findOneAndUpdate(filter, update, projection, sort, upsert, returnNewDoc) } @@ -198,7 +200,7 @@ public suspend inline fun MongoCollection.findOneAndR sort: BsonDocument? = null, upsert: Boolean = false, returnNewDoc: Boolean = false, -): T { +): T? { isType>(this) return decodeFromBsonValue(findOneAndReplace(filter, update, projection, sort, upsert, returnNewDoc)) } @@ -212,7 +214,7 @@ public suspend inline fun MongoCollection<*, *>.findOneAndReplace( sort: BsonDocument? = null, upsert: Boolean = false, returnNewDoc: Boolean = false, -): T { +): T? { return (this as MongoCollection).findOneAndReplace(filter, update, projection, sort, upsert, returnNewDoc) } @@ -220,7 +222,7 @@ public suspend inline fun MongoCollection.findOneAndD filter: BsonDocument, projection: BsonDocument? = null, sort: BsonDocument? = null, -): T { +): T? { isType>(this) return decodeFromBsonValue(findOneAndDelete(filter, projection, sort)) } @@ -230,6 +232,6 @@ public suspend inline fun MongoCollection<*, *>.findOneAndDelete( filter: BsonDocument, projection: BsonDocument? = null, sort: BsonDocument? = null, -): T { +): T? { return (this as MongoCollection).findOneAndDelete(filter, projection, sort) } diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserTests.kt index 417c4c5516..512faac2f0 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserTests.kt @@ -26,18 +26,29 @@ import io.realm.kotlin.mongodb.AuthenticationProvider import io.realm.kotlin.mongodb.Credentials import io.realm.kotlin.mongodb.User import io.realm.kotlin.mongodb.exceptions.CredentialsCannotBeLinkedException +import io.realm.kotlin.mongodb.exceptions.ServiceException import io.realm.kotlin.mongodb.ext.customData import io.realm.kotlin.mongodb.ext.customDataAsBsonDocument +import io.realm.kotlin.mongodb.mongo.insertOne import io.realm.kotlin.mongodb.sync.SyncConfiguration import io.realm.kotlin.test.mongodb.TestApp import io.realm.kotlin.test.mongodb.asTestApp +import io.realm.kotlin.test.mongodb.common.mongo.CustomDataType +import io.realm.kotlin.test.mongodb.common.mongo.CustomIdType +import io.realm.kotlin.test.mongodb.common.mongo.SyncDog +import io.realm.kotlin.test.mongodb.common.mongo.TEST_SERVICE_NAME +import io.realm.kotlin.test.mongodb.common.mongo.customEjsonSerializer +import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage import io.realm.kotlin.test.util.TestHelper import io.realm.kotlin.test.util.TestHelper.randomEmail import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import kotlinx.serialization.SerializationException import org.mongodb.kbson.BsonDocument import org.mongodb.kbson.BsonString +import org.mongodb.kbson.BsonValue import org.mongodb.kbson.ExperimentalKBsonSerializerApi +import org.mongodb.kbson.ObjectId import org.mongodb.kbson.serialization.Bson import kotlin.test.AfterTest import kotlin.test.BeforeTest @@ -45,6 +56,7 @@ import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith import kotlin.test.assertFalse +import kotlin.test.assertIs import kotlin.test.assertNotEquals import kotlin.test.assertNotNull import kotlin.test.assertNotSame @@ -723,6 +735,49 @@ class UserTests { } } + @Test + fun mongoClient_defaultSerializer() = runBlocking { + val (email, password) = randomEmail() to "123456" + val user = runBlocking { + createUserAndLogin(email, password) + } + val client = user.mongoClient(TEST_SERVICE_NAME) + assertIs(client.database(app.clientAppId).collection("SyncDog").insertOne(SyncDog("dog-1"))) + } + + @Test + fun mongoClient_customSerializer() = runBlocking { + val (email, password) = randomEmail() to "123456" + val user = runBlocking { + createUserAndLogin(email, password) + } + val collectionWithDefaultSerializer = + user.mongoClient(TEST_SERVICE_NAME).database(app.clientAppId) + .collection("SyncDog") + assertFailsWithMessage("Serializer for class 'CustomDataType' is not found.") { + collectionWithDefaultSerializer.insertOne(CustomDataType("dog-1")) + } + + val collectionWithCustomSerializer = + user.mongoClient(TEST_SERVICE_NAME, customEjsonSerializer).database(app.clientAppId) + .collection("SyncDog") + assertIs(collectionWithCustomSerializer.insertOne(CustomDataType("dog-1"))) + } + + @Test + fun mongoClient_unknownClient() = runBlocking { + val (email, password) = randomEmail() to "123456" + val user = runBlocking { + createUserAndLogin(email, password) + } + val mongoClient = user.mongoClient("UNKNOWN_SERVICE") + val collection = + mongoClient.database(app.clientAppId).collection("SyncDog") + assertFailsWithMessage("service not found: 'UNKNOWN_SERVICE'") { + collection.insertOne(SyncDog("dog-1")) + } + } + private fun updatecustomDataAsBsonDocument(user: User, data: BsonDocument) { // Name of collection and property used for storing custom user data. Must match server config.json val COLLECTION_NAME = "UserData" diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTests.kt new file mode 100644 index 0000000000..d241d938f4 --- /dev/null +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTests.kt @@ -0,0 +1,124 @@ +/* + * Copyright 2023 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.realm.kotlin.test.mongodb.common.mongo + +import io.realm.kotlin.internal.platform.runBlocking +import io.realm.kotlin.log.LogLevel +import io.realm.kotlin.log.RealmLog +import io.realm.kotlin.mongodb.AppConfiguration +import io.realm.kotlin.mongodb.exceptions.ServiceException +import io.realm.kotlin.mongodb.mongo.MongoClient +import io.realm.kotlin.mongodb.mongo.collection +import io.realm.kotlin.mongodb.mongo.insertOne +import io.realm.kotlin.test.mongodb.TestApp +import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage +import kotlinx.serialization.SerializationException +import org.mongodb.kbson.BsonValue +import org.mongodb.kbson.ExperimentalKBsonSerializerApi +import org.mongodb.kbson.ObjectId +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertIs + +internal const val TEST_SERVICE_NAME = "BackingDB" + +@OptIn(ExperimentalKBsonSerializerApi::class) +class MongoClientTests { + + lateinit var app: TestApp + lateinit var client: MongoClient + + @BeforeTest + fun setUp() { + app = TestApp( + this::class.simpleName, + builder = { builder: AppConfiguration.Builder -> + builder.httpLogObfuscator(null) + } + ) + val user = app.createUserAndLogin() + client = user.mongoClient(TEST_SERVICE_NAME) + } + + @AfterTest + fun teadDown() { + RealmLog.level = LogLevel.WARN + if (this::app.isInitialized) { + app.close() + } + } + + @Test + fun properties() { + assertEquals(TEST_SERVICE_NAME, client.serviceName) + } + + @Test + fun database_defaultSerializer() = runBlocking { + assertIs(client.database(app.clientAppId).collection("SyncDog").insertOne(SyncDog("dog-1"))) + } + + @Test + fun database_customSerializer() = runBlocking { + val collectionWithDefaultSerializer = client.database(app.clientAppId) + .collection("SyncDog") + assertFailsWithMessage("Serializer for class 'CustomDataType' is not found.") { + collectionWithDefaultSerializer.insertOne(CustomDataType("dog-1")) + } + + val collectionWithCustomSerializer = client.database(app.clientAppId, customEjsonSerializer) + .collection("SyncDog") + assertIs(collectionWithCustomSerializer.insertOne(CustomDataType("dog-1"))) + } + + @Test + fun database_unknownDatabase() = runBlocking { + assertIs(client.database("Unknown").collection("SyncDog").insertOne(SyncDog("dog-1"))) + } + + @Test + fun collection_defaultSerializer() = runBlocking { + assertIs(client.collection().insertOne(SyncDog("dog-1"))) + } + + @Test + fun collection_customSerializer() = runBlocking { + val collectionWithDefaultSerializer = client.collection() + assertFailsWithMessage("Serializer for class 'CustomDataType' is not found.") { + collectionWithDefaultSerializer.insertOne(CustomDataType("dog-1")) + } + + val collectionWithCustomSerializer = client.collection( + customEjsonSerializer + ) + assertIs( + collectionWithCustomSerializer.insertOne( + CustomDataType("dog-1") + ) + ) + } + + @Test + fun collection_unknownSchemaType() = runBlocking { + val collectionWithDefaultSerializer = client.collection() + assertFailsWithMessage("no matching collection found that maps to a table with title \"NonSchemaType\".") { + collectionWithDefaultSerializer.insertOne(NonSchemaType()) + } + } +} diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTest.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt similarity index 93% rename from packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTest.kt rename to packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt index e14e8d1cd5..bb160d8217 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTest.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt @@ -25,6 +25,7 @@ import io.realm.kotlin.mongodb.mongo.MongoClient import io.realm.kotlin.mongodb.mongo.MongoCollection import io.realm.kotlin.mongodb.mongo.MongoDatabase import io.realm.kotlin.mongodb.mongo.aggregate +import io.realm.kotlin.mongodb.mongo.collection import io.realm.kotlin.mongodb.mongo.count import io.realm.kotlin.mongodb.mongo.deleteMany import io.realm.kotlin.mongodb.mongo.deleteOne @@ -41,7 +42,13 @@ import io.realm.kotlin.test.mongodb.TestApp import io.realm.kotlin.test.mongodb.asTestApp import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage import io.realm.kotlin.types.RealmObject +import io.realm.kotlin.types.annotations.PersistedName +import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.modules.SerializersModule import org.mongodb.kbson.BsonDocument import org.mongodb.kbson.BsonString import org.mongodb.kbson.BsonValue @@ -59,10 +66,20 @@ import kotlin.test.assertIs import kotlin.test.assertNull import kotlin.test.assertTrue -private const val SERVICE_NAME = "BackingDB" - +class MongoCollectionFromDatabaseTests : MongoCollectionTests() { + @OptIn(ExperimentalKBsonSerializerApi::class) + override fun collection(): MongoCollection { + return client.database(app.clientAppId).collection("SyncDog") + } +} +class MongoCollectionFromClientTests : MongoCollectionTests() { + @OptIn(ExperimentalKBsonSerializerApi::class) + override fun collection(): MongoCollection { + return client.collection() + } +} @OptIn(ExperimentalKBsonSerializerApi::class) -class MongoClientTest { +abstract class MongoCollectionTests { lateinit var app: TestApp lateinit var client: MongoClient @@ -85,9 +102,9 @@ class MongoClientTest { } } val user = app.createUserAndLogin() - client = user.mongoClient(SERVICE_NAME) + client = user.mongoClient(TEST_SERVICE_NAME) database = client.database(databaseName) - collection = database.collection("SyncDog") + collection = collection() } @AfterTest @@ -98,15 +115,16 @@ class MongoClientTest { } } + abstract fun collection(): MongoCollection + + // FIXME How to align this across MongoClient.collection and MongoDatabase.collection @Test fun properties() { - assertEquals("SyncDog", collection.name) - assertEquals(app.configuration.appId, database.name) - assertEquals(SERVICE_NAME, client.serviceName) +// assertEquals("SyncDog", collection.collectionname) } @Test - fun collection() = runBlocking { + fun collection_reshaping() = runBlocking { val bsonCollection: MongoCollection = database.collection("SyncDogIntId") // Default argument issues default return type @@ -155,9 +173,10 @@ class MongoClientTest { } @Test - fun findOne() = runBlocking { + open fun findOne() = runBlocking { RealmLog.level = LogLevel.ALL - assertNull(collection.findOne()) + val actual: SyncDog? = collection.findOne() + assertNull(actual) assertNull(collection.findOne()) collection.insertMany((1..10).map { SyncDog("dog-${it % 5}") }) @@ -169,14 +188,14 @@ class MongoClientTest { assertIs(collection.findOne(filter = BsonDocument("name", "dog-0"))) collection.findOne(filter = BsonDocument("name", "dog-0")).run { - assertEquals("dog-0", get("name")!!.asString().value) + assertEquals("dog-0", this!!.get("name")!!.asString().value) } // Projection select field // Limit // Sort - val y: BsonDocument = collection.findOne(filter = BsonDocument("name", "dog-0")) - val y2: BsonValue = collection.findOne(filter = BsonDocument("name", "dog-0")) + val y: BsonDocument = collection.findOne(filter = BsonDocument("name", "dog-0"))!! + val y2: BsonValue = collection.findOne(filter = BsonDocument("name", "dog-0"))!! // argument type differentiation // - serialization issues @@ -449,6 +468,13 @@ class MongoClientTest { // Existing primary key object } + @Test + fun insertOne_throwsOnTypeMismatch() = runBlocking { + assertFailsWithMessage("insert not permitted") { + collection.insertOne(BsonDocument("""{"x": "a" }""")) + } + } + // // @Test // fun insertOne_throwsWhenMixingIdTypes() { @@ -1317,7 +1343,7 @@ class MongoClientTest { SyncDog("dog2") ) ) - val x: SyncDog = collection.findOneAndDelete( + val x: SyncDog? = collection.findOneAndDelete( BsonDocument(), BsonDocument("""{ "name": "dog1" }"""), ) @@ -1404,26 +1430,70 @@ class MongoClientTest { // } } +@Suppress("name") +@OptIn(ExperimentalKBsonSerializerApi::class) +inline operator fun BsonDocument.Companion.invoke( + key: String, + value: T, +): BsonDocument { + return BsonDocument(key to EJson.Default.encodeToBsonValue(value)) +} + @Serializable -class SyncDog : RealmObject { - constructor() { - this.name = "Default" - } +class SyncDog(var name: String) : RealmObject { + constructor() : this("Default") +} - constructor(name: String) { - this.name = name - } +@Serializable +data class SyncDogIntId(val name: String, val _id: Int) - var name: String +@PersistedName("SyncDog") +class CustomDataType(var name: String) : RealmObject { + @Suppress("unused") + constructor() : this("Default") } @Serializable -data class SyncDogIntId(val name: String, val _id: Int) +class NonSchemaType : RealmObject { + var name: String = "Default" +} -@OptIn(ExperimentalKBsonSerializerApi::class) -inline operator fun BsonDocument.Companion.invoke( - key: String, - value: T, -): BsonDocument { - return BsonDocument(key to EJson.Default.encodeToBsonValue(value)) +class CustomIdType(val id: ObjectId) + +class CustomDataTypeSerializer : KSerializer { + + val serializer = BsonValue.serializer() + override val descriptor: SerialDescriptor = serializer.descriptor + override fun deserialize(decoder: Decoder): CustomDataType { + return decoder.decodeSerializableValue(serializer).let { + CustomDataType(it.asDocument()["name"]!!.asString().value) + } + } + + override fun serialize(encoder: Encoder, value: CustomDataType) { + encoder.encodeSerializableValue(serializer, BsonDocument("name", value.name)) + } +} + +class CustomIdSerializer : KSerializer { + val serializer = BsonValue.serializer() + override val descriptor: SerialDescriptor = serializer.descriptor + override fun deserialize(decoder: Decoder): CustomIdType { + return decoder.decodeSerializableValue(serializer).let { + CustomIdType(it.asObjectId()) + } + } + + override fun serialize(encoder: Encoder, value: CustomIdType) { + encoder.encodeSerializableValue(serializer, value.id) + } } + +@OptIn(ExperimentalKBsonSerializerApi::class) +val customEjsonSerializer = EJson( + ignoreUnknownKeys = false, + serializersModule = SerializersModule { + contextual(CustomDataType::class, CustomDataTypeSerializer()) + contextual(CustomIdType::class, CustomIdSerializer()) + } +) diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoDatabaseTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoDatabaseTests.kt new file mode 100644 index 0000000000..ca7aa5d567 --- /dev/null +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoDatabaseTests.kt @@ -0,0 +1,88 @@ +/* + * Copyright 2023 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.realm.kotlin.test.mongodb.common.mongo + +import io.realm.kotlin.internal.platform.runBlocking +import io.realm.kotlin.log.LogLevel +import io.realm.kotlin.log.RealmLog +import io.realm.kotlin.mongodb.AppConfiguration +import io.realm.kotlin.mongodb.mongo.MongoClient +import io.realm.kotlin.mongodb.mongo.MongoDatabase +import io.realm.kotlin.mongodb.mongo.insertOne +import io.realm.kotlin.test.mongodb.TestApp +import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage +import kotlinx.serialization.SerializationException +import org.mongodb.kbson.BsonValue +import org.mongodb.kbson.ExperimentalKBsonSerializerApi +import org.mongodb.kbson.ObjectId +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertIs + +@OptIn(ExperimentalKBsonSerializerApi::class) +class MongoDatabaseTests { + + lateinit var app: TestApp + lateinit var client: MongoClient + lateinit var databaseName: String + lateinit var database: MongoDatabase + + @BeforeTest + fun setUp() { + app = TestApp( + this::class.simpleName, + builder = { builder: AppConfiguration.Builder -> + builder.httpLogObfuscator(null) + } + ) + val user = app.createUserAndLogin() + client = user.mongoClient(TEST_SERVICE_NAME) + databaseName = app.clientAppId + database = client.database(databaseName) + } + + @AfterTest + fun teadDown() { + RealmLog.level = LogLevel.WARN + if (this::app.isInitialized) { + app.close() + } + } + + @Test + fun properties() { + assertEquals(databaseName, database.name) + } + + @Test + fun collection_defaultSerializer() = runBlocking { + assertIs(database.collection("SyncDog").insertOne(SyncDog("dog-1"))) + } + + @Test + fun collection_customSerializer() = runBlocking { + val collectionWithDefaultSerializer = database.collection("SyncDog") + assertFailsWithMessage("Serializer for class 'CustomDataType' is not found.") { + collectionWithDefaultSerializer.insertOne(CustomDataType("dog-1")) + } + + val collectionWithCustomSerializer = database.collection("SyncDog", customEjsonSerializer) + assertIs(collectionWithCustomSerializer.insertOne(CustomDataType("dog-1"))) + } +} From 1e221b94bbe75be51e1b398952b2a1bbf46a7a74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Fri, 15 Dec 2023 17:55:31 +0100 Subject: [PATCH 08/36] More testing --- .../kotlin/internal/platform/RealmObject.kt | 1 + .../kotlin/internal/platform/RealmObject.kt | 1 + .../kotlin/internal/platform/RealmObject.kt | 1 + .../mongodb/internal/MongoCollectionImpl.kt | 14 +- .../kotlin/mongodb/mongo/MongoCollection.kt | 11 +- .../test/mongodb/util/TestAppInitializer.kt | 29 + .../kotlin/test/mongodb/common/UserTests.kt | 10 +- .../mongodb/common/mongo/MongoClientTests.kt | 22 +- .../common/mongo/MongoCollectionTests.kt | 579 +++++++++--------- .../common/mongo/MongoDatabaseTests.kt | 27 +- 10 files changed, 370 insertions(+), 325 deletions(-) diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/platform/RealmObject.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/platform/RealmObject.kt index f9cbb727ec..5dd6ef7c76 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/platform/RealmObject.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/platform/RealmObject.kt @@ -25,6 +25,7 @@ import kotlin.reflect.KClass * associated [RealmObjectCompanion], in which case the `clazz` wasn't a user defined class * implementing [BaseRealmObject] augmented by our compiler plugin. */ +@PublishedApi internal expect fun realmObjectCompanionOrNull(clazz: KClass): RealmObjectCompanion? /** diff --git a/packages/library-base/src/jvm/kotlin/io/realm/kotlin/internal/platform/RealmObject.kt b/packages/library-base/src/jvm/kotlin/io/realm/kotlin/internal/platform/RealmObject.kt index 1ac17f0b7f..97f948eafb 100644 --- a/packages/library-base/src/jvm/kotlin/io/realm/kotlin/internal/platform/RealmObject.kt +++ b/packages/library-base/src/jvm/kotlin/io/realm/kotlin/internal/platform/RealmObject.kt @@ -23,6 +23,7 @@ import kotlin.reflect.full.companionObjectInstance // TODO OPTIMIZE Can we eliminate the reflective approach? Maybe by embedding the information // through the compiler plugin or something similar to the Native findAssociatedObject +@PublishedApi internal actual fun realmObjectCompanionOrNull(clazz: KClass): RealmObjectCompanion? = if (clazz.companionObjectInstance is RealmObjectCompanion) { clazz.companionObjectInstance as RealmObjectCompanion diff --git a/packages/library-base/src/nativeDarwin/kotlin/io/realm/kotlin/internal/platform/RealmObject.kt b/packages/library-base/src/nativeDarwin/kotlin/io/realm/kotlin/internal/platform/RealmObject.kt index a59ce11869..2d2d81f811 100644 --- a/packages/library-base/src/nativeDarwin/kotlin/io/realm/kotlin/internal/platform/RealmObject.kt +++ b/packages/library-base/src/nativeDarwin/kotlin/io/realm/kotlin/internal/platform/RealmObject.kt @@ -22,6 +22,7 @@ import kotlin.reflect.ExperimentalAssociatedObjects import kotlin.reflect.KClass import kotlin.reflect.findAssociatedObject +@PublishedApi internal actual fun realmObjectCompanionOrNull(clazz: KClass): RealmObjectCompanion? = @OptIn(ExperimentalAssociatedObjects::class) when (val associatedObject = clazz.findAssociatedObject()) { diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt index c5433b5729..725d105571 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt @@ -33,26 +33,26 @@ import org.mongodb.kbson.serialization.encodeToBsonValue @PublishedApi @OptIn(ExperimentalKBsonSerializerApi::class) -internal class MongoDatabaseCollection(@PublishedApi internal val database: MongoDatabaseImpl, val name: String, eJson: EJson) : MongoCollectionImpl(database.client.functions, eJson) { +internal class MongoDatabaseCollection(@PublishedApi internal val database: MongoDatabaseImpl, override val name: String, eJson: EJson) : MongoCollectionImpl(database.client.functions, eJson) { override val defaults: Map = mapOf( "database" to BsonString(database.name), "collection" to BsonString(name), ) @OptIn(ExperimentalKBsonSerializerApi::class) - override fun collection(eJson: EJson?): MongoCollection { + override fun reshape(eJson: EJson?): MongoCollection { return MongoDatabaseCollection(this.database, this.name, eJson ?: this.eJson) } } @PublishedApi @OptIn(ExperimentalKBsonSerializerApi::class) -internal class MongoClientCollection(@PublishedApi internal val clientImpl: MongoClientImpl, val schemaName: String, eJson: EJson) : MongoCollectionImpl(clientImpl.functions, eJson) { +internal class MongoClientCollection(@PublishedApi internal val clientImpl: MongoClientImpl, override val name: String, eJson: EJson) : MongoCollectionImpl(clientImpl.functions, eJson) { override val defaults: Map = mapOf( - "schema_name" to BsonString(schemaName), + "schema_name" to BsonString(name), ) @OptIn(ExperimentalKBsonSerializerApi::class) - override fun collection(eJson: EJson?): MongoCollection { - return MongoClientCollection(clientImpl, schemaName, eJson ?: this.eJson) + override fun reshape(eJson: EJson?): MongoCollection { + return MongoClientCollection(clientImpl, name, eJson ?: this.eJson) } } @@ -87,7 +87,7 @@ internal abstract class MongoCollectionImpl constructor( internal suspend fun findOne(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null): BsonValue { val call: BsonValue = call("findOne") { filter?.let { put("query", it) } - projection?.let { put("projection", it) } + projection?.let { put("project", it) } sort?.let { put("sort", it) } } return call diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt index ab111beb6d..ddea4a42d4 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt @@ -43,17 +43,16 @@ import kotlin.jvm.JvmName */ public interface MongoCollection { - // FIXME Remove? as we don't have a name when the collection is obtained from a schema name (directly from MongoClient -// /** -// * Name of the remote collection. -// */ -// public val name: String + /** + * Name of the remote collection. Will be `null` for collections + */ + public val name: String? /** * Get an instance of the same collection with a different set of default types serialization. */ @OptIn(ExperimentalKBsonSerializerApi::class) - public fun collection(eJson: EJson? = null): MongoCollection + public fun reshape(eJson: EJson? = null): MongoCollection } public suspend fun MongoCollection<*, *>.count(filter: BsonDocument? = null, limit: Long? = null): Long { diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/TestAppInitializer.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/TestAppInitializer.kt index 6076c78261..413bc33fc6 100644 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/TestAppInitializer.kt +++ b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/TestAppInitializer.kt @@ -229,6 +229,35 @@ object TestAppInitializer { """.trimIndent() ) + app.addSchema( + """ + { + "metadata": { + "data_source": "BackingDB", + "database": "$databaseName", + "collection": "CollectionDataType" + }, + "schema": { + "properties": { + "_id": { + "bsonType": "int" + }, + "realm_id": { + "bsonType": "string" + }, + "name": { + "bsonType": "string" + } + }, + "required": [ + "name" + ], + "title": "CollectionDataType" + } + } + """.trimIndent() + ) + app.addSchema( """ { diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserTests.kt index 512faac2f0..f2e54b88bb 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserTests.kt @@ -22,6 +22,8 @@ import io.realm.kotlin.Realm import io.realm.kotlin.annotations.ExperimentalRealmSerializerApi import io.realm.kotlin.internal.platform.fileExists import io.realm.kotlin.internal.platform.runBlocking +import io.realm.kotlin.log.LogLevel +import io.realm.kotlin.log.RealmLog import io.realm.kotlin.mongodb.AuthenticationProvider import io.realm.kotlin.mongodb.Credentials import io.realm.kotlin.mongodb.User @@ -33,9 +35,9 @@ import io.realm.kotlin.mongodb.mongo.insertOne import io.realm.kotlin.mongodb.sync.SyncConfiguration import io.realm.kotlin.test.mongodb.TestApp import io.realm.kotlin.test.mongodb.asTestApp +import io.realm.kotlin.test.mongodb.common.mongo.CollectionDataType import io.realm.kotlin.test.mongodb.common.mongo.CustomDataType import io.realm.kotlin.test.mongodb.common.mongo.CustomIdType -import io.realm.kotlin.test.mongodb.common.mongo.SyncDog import io.realm.kotlin.test.mongodb.common.mongo.TEST_SERVICE_NAME import io.realm.kotlin.test.mongodb.common.mongo.customEjsonSerializer import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage @@ -742,7 +744,7 @@ class UserTests { createUserAndLogin(email, password) } val client = user.mongoClient(TEST_SERVICE_NAME) - assertIs(client.database(app.clientAppId).collection("SyncDog").insertOne(SyncDog("dog-1"))) + assertIs(client.database(app.clientAppId).collection("CollectionDataType").insertOne(CollectionDataType("object-1"))) } @Test @@ -772,9 +774,9 @@ class UserTests { } val mongoClient = user.mongoClient("UNKNOWN_SERVICE") val collection = - mongoClient.database(app.clientAppId).collection("SyncDog") + mongoClient.database(app.clientAppId).collection("CollectionDataType") assertFailsWithMessage("service not found: 'UNKNOWN_SERVICE'") { - collection.insertOne(SyncDog("dog-1")) + collection.insertOne(CollectionDataType("object-1")) } } diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTests.kt index d241d938f4..8e94963dff 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTests.kt @@ -71,37 +71,39 @@ class MongoClientTests { @Test fun database_defaultSerializer() = runBlocking { - assertIs(client.database(app.clientAppId).collection("SyncDog").insertOne(SyncDog("dog-1"))) + assertIs(client.database(app.clientAppId).collection("CollectionDataType").insertOne(CollectionDataType("object-1"))) } @Test fun database_customSerializer() = runBlocking { val collectionWithDefaultSerializer = client.database(app.clientAppId) - .collection("SyncDog") + .collection("CollectionDataType") assertFailsWithMessage("Serializer for class 'CustomDataType' is not found.") { - collectionWithDefaultSerializer.insertOne(CustomDataType("dog-1")) + collectionWithDefaultSerializer.insertOne(CustomDataType("object-1")) } val collectionWithCustomSerializer = client.database(app.clientAppId, customEjsonSerializer) - .collection("SyncDog") - assertIs(collectionWithCustomSerializer.insertOne(CustomDataType("dog-1"))) + .collection("CollectionDataType") + assertIs(collectionWithCustomSerializer.insertOne(CustomDataType("object-1"))) } @Test fun database_unknownDatabase() = runBlocking { - assertIs(client.database("Unknown").collection("SyncDog").insertOne(SyncDog("dog-1"))) + RealmLog.level = LogLevel.ALL + assertIs(client.database("Unknown").collection("CollectionDataType").insertOne(CollectionDataType("object-1"))) + RealmLog.level = LogLevel.WARN } @Test fun collection_defaultSerializer() = runBlocking { - assertIs(client.collection().insertOne(SyncDog("dog-1"))) + assertIs(client.collection().insertOne(CollectionDataType("object-1"))) } @Test fun collection_customSerializer() = runBlocking { - val collectionWithDefaultSerializer = client.collection() + val collectionWithDefaultSerializer = client.collection() assertFailsWithMessage("Serializer for class 'CustomDataType' is not found.") { - collectionWithDefaultSerializer.insertOne(CustomDataType("dog-1")) + collectionWithDefaultSerializer.insertOne(CustomDataType("object-1")) } val collectionWithCustomSerializer = client.collection( @@ -109,7 +111,7 @@ class MongoClientTests { ) assertIs( collectionWithCustomSerializer.insertOne( - CustomDataType("dog-1") + CustomDataType("object-1") ) ) } diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt index bb160d8217..f895d9ba5c 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt @@ -14,6 +14,8 @@ * limitations under the License. */ +@file:OptIn(ExperimentalKBsonSerializerApi::class) + package io.realm.kotlin.test.mongodb.common.mongo import io.realm.kotlin.internal.platform.runBlocking @@ -41,15 +43,18 @@ import io.realm.kotlin.mongodb.mongo.updateOne import io.realm.kotlin.test.mongodb.TestApp import io.realm.kotlin.test.mongodb.asTestApp import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage +import io.realm.kotlin.types.BaseRealmObject import io.realm.kotlin.types.RealmObject import io.realm.kotlin.types.annotations.PersistedName import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable +import kotlinx.serialization.SerializationException import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.modules.SerializersModule import org.mongodb.kbson.BsonDocument +import org.mongodb.kbson.BsonInt32 import org.mongodb.kbson.BsonString import org.mongodb.kbson.BsonValue import org.mongodb.kbson.ExperimentalKBsonSerializerApi @@ -57,6 +62,7 @@ import org.mongodb.kbson.ObjectId import org.mongodb.kbson.serialization.EJson import org.mongodb.kbson.serialization.encodeToBsonValue import kotlin.random.Random +import kotlin.reflect.KClass import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test @@ -67,44 +73,69 @@ import kotlin.test.assertNull import kotlin.test.assertTrue class MongoCollectionFromDatabaseTests : MongoCollectionTests() { - @OptIn(ExperimentalKBsonSerializerApi::class) - override fun collection(): MongoCollection { - return client.database(app.clientAppId).collection("SyncDog") + + lateinit var database: MongoDatabase + + @BeforeTest + override fun setUp() { + super.setUp() + val databaseName = app.configuration.appId + database = client.database(databaseName) + collection = collection(CollectionDataType::class) + } + + @Test + override fun findOne_unknownCollection() = runBlocking { + // Unknown collections will create the collection + val unknownCollection = collection(Unknown::class) + assertNull(unknownCollection.findOne()) } } class MongoCollectionFromClientTests : MongoCollectionTests() { - @OptIn(ExperimentalKBsonSerializerApi::class) - override fun collection(): MongoCollection { - return client.collection() + + @BeforeTest + override fun setUp() { + super.setUp() + collection = collection(CollectionDataType::class) + } + + @Test + fun name_persistedName() { + assertEquals("CollectionDataType", client.collection().name) + } + + @Test + override fun findOne_unknownCollection() = runBlocking { + val unknownCollection = collection(Unknown::class) + assertFailsWithMessage("no matching collection found that maps to a table with title \"Unknown\"") { + RealmLog.level = LogLevel.ALL + unknownCollection.findOne() + } } } -@OptIn(ExperimentalKBsonSerializerApi::class) -abstract class MongoCollectionTests { + +abstract sealed class MongoCollectionTests { lateinit var app: TestApp lateinit var client: MongoClient - lateinit var database: MongoDatabase - lateinit var collection: MongoCollection + lateinit var collection: MongoCollection @BeforeTest - fun setUp() { + open fun setUp() { app = TestApp( this::class.simpleName, builder = { builder: AppConfiguration.Builder -> builder.httpLogObfuscator(null) } ) - val databaseName = app.configuration.appId app.asTestApp.run { runBlocking { - deleteDocuments(databaseName, "SyncDog", "{}") + deleteDocuments(app.configuration.appId, "CollectionDataType", "{}") } } val user = app.createUserAndLogin() client = user.mongoClient(TEST_SERVICE_NAME) - database = client.database(databaseName) - collection = collection() } @AfterTest @@ -115,179 +146,138 @@ abstract class MongoCollectionTests { } } - abstract fun collection(): MongoCollection - - // FIXME How to align this across MongoClient.collection and MongoDatabase.collection @Test - fun properties() { -// assertEquals("SyncDog", collection.collectionname) + fun name() { + assertEquals("CollectionDataType", collection.name) } @Test - fun collection_reshaping() = runBlocking { - val bsonCollection: MongoCollection = database.collection("SyncDogIntId") - - // Default argument issues default return type - assertIs(bsonCollection.insertOne(BsonDocument("dog-1", Random.nextInt()))) + open fun reshape() = runBlocking { + // Original typing + assertIs(collection.insertOne(CollectionDataType("object-1", Random.nextInt()))) + assertIs(collection.findOne()) + + // Reshaped + val bsonCollection: MongoCollection = collection.reshape() + assertIs(bsonCollection.insertOne(BsonDocument("name", "object-2"))) assertIs(bsonCollection.findOne()) + assertEquals(2, bsonCollection.count()) + } - // Cannot resolve return type because not using explicit typed insertOne - // val value2 = bsonCollection.insertOne(SyncDogIntId("dog-2", Random.nextInt())) + @Test + fun reshape_withCustomSerialization() = runBlocking { + val reshapedCollectionWithDefaultSerializer: MongoCollection = + collection.reshape() - // Explicit combinations returns what they should - val value2: BsonValue = bsonCollection.insertOne(SyncDogIntId("dog-2", Random.nextInt())) - assertIs(value2) - val value3: Int = bsonCollection.insertOne(SyncDogIntId("dog-2", Random.nextInt())) - assertIs(value3) - assertIs(bsonCollection.findOne()) + assertFailsWithMessage("Serializer for class 'CustomDataType' is not found.") { + reshapedCollectionWithDefaultSerializer.insertOne(CustomDataType("object-2")) + } - // Converting to typed collection changes defaults - val typedCollection = bsonCollection.collection() - assertIs(typedCollection.insertOne(SyncDogIntId("dog-1", Random.nextInt()))) - assertIs(typedCollection.findOne()) + val reshapedCollectionWithCustomSerializer: MongoCollection = + collection.reshape(customEjsonSerializer) - // TODO eJson + assertIs(reshapedCollectionWithCustomSerializer.insertOne(CustomDataType("object-2"))) } @Test fun count() = runBlocking { + RealmLog.level = LogLevel.ALL assertEquals(0, collection.count()) - collection.insertMany((1..10).map { SyncDog("dog-${it % 5}") }) + collection.insertMany((1..10).map { CollectionDataType("object-${it % 5}") }) assertEquals(10, collection.count()) assertEquals(5, collection.count(limit = 5)) - assertEquals(2, collection.count(filter = BsonDocument("name" to BsonString("dog-0")))) - assertEquals(2, collection.count(filter = BsonDocument("name", "dog-0"))) + assertEquals(2, collection.count(filter = BsonDocument("name" to BsonString("object-0")))) + assertEquals(2, collection.count(filter = BsonDocument("name", "object-0"))) assertEquals( 1, - collection.count(filter = BsonDocument("name" to BsonString("dog-0")), limit = 1) + collection.count(filter = BsonDocument("name" to BsonString("object-0")), limit = 1) ) } @Test fun count_invalidFilter() = runBlocking { assertFailsWithMessage("operator") { - collection.count(filter = BsonDocument("\$who", "dog-0")) + collection.count(filter = BsonDocument("\$who", "object-0")) } } @Test open fun findOne() = runBlocking { RealmLog.level = LogLevel.ALL - val actual: SyncDog? = collection.findOne() - assertNull(actual) + // Empty collections + assertNull(collection.findOne()) assertNull(collection.findOne()) - collection.insertMany((1..10).map { SyncDog("dog-${it % 5}") }) + collection.insertMany((1..10).map { CollectionDataType("object-${it % 5}") }) // No match assertNull(collection.findOne(filter = BsonDocument("name", "cat"))) - // Only on document even though multiple matches - assertIs(collection.findOne(filter = BsonDocument("name", "dog-0"))) + // Multiple matches, still only one document + // Default types + collection.findOne(filter = BsonDocument("name", "object-0")).run { + assertIs(this) + assertEquals("object-0", this.name) + } + + // projection + val collectionDataType = CollectionDataType("object-6") + assertEquals(collectionDataType._id, collection.insertOne(collectionDataType)) + + collection.findOne(filter = BsonDocument("name", "object-6")).run { + assertIs(this) + assertEquals("object-6", this.name) + assertEquals(collectionDataType._id, this._id) + } - collection.findOne(filter = BsonDocument("name", "dog-0")).run { - assertEquals("dog-0", this!!.get("name")!!.asString().value) + collection.findOne( + filter = BsonDocument("name", "object-6"), + projection = BsonDocument("""{ "name" : 0}""") + ).run { + assertIs(this) + // FIXME Should be "Default" but serialization fails if field is non-nullable even though descriptor correctly has isOptional=true + // assertEquals("Default", this.name) + assertNull(this.name) // Currently null because of nullability, but should have default value "Default" + // _id is included by default + assertEquals(collectionDataType._id, this._id) } - // Projection select field - // Limit - // Sort - val y: BsonDocument = collection.findOne(filter = BsonDocument("name", "dog-0"))!! - val y2: BsonValue = collection.findOne(filter = BsonDocument("name", "dog-0"))!! - - // argument type differentiation - // - serialization issues - // - missing fields - // - too many fields - // return type differentiation - // - serialization issues - // - missing fields - // - too many fields - // parameters - // - ill formatted parameters - - // state differentiation - // - empty collection - // - + // sort + collection.findOne(sort = BsonDocument(mapOf("name" to BsonInt32(-1)))).run { + assertIs(this) + assertEquals("object-6", this.name) + } + collection.findOne(sort = BsonDocument(mapOf("name" to BsonInt32(1)))).run { + assertIs(this) + assertEquals("object-0", this.name) + } } - @OptIn(ExperimentalKBsonSerializerApi::class) -// @Test -// fun findOne() = runBlocking { -// with(getCollectionInternal()) { -// // Test findOne() on empty collection with no filter and no options -// assertNull(findOne().get()) -// -// // Test findOne() with filter that does not match any documents and no options -// assertNull(findOne(Document("hello", "worldDNE")).get()) -// -// val doc1 = Document("hello", "world1") -// insertOne(doc1).get() -// assertEquals(1, count().get()) -// -// // Test findOne() with filter that does not match any documents and no options -// assertNull(findOne(Document("hello", "worldDNE")).get()) -// } -// } -// -// @Test -// fun findOne_singleDocument() { -// with(getCollectionInternal()) { -// val doc1 = Document("hello", "world1") -// -// // Insert one document -// insertOne(doc1).get() -// assertEquals(1, count().get()) -// -// // No filter and no options -// assertEquals(doc1, findOne().get()!!.withoutId()) -// -// // Projection (remove "_id") options -// val projection = Document("hello", 1).apply { this["_id"] = 0 } -// var options = FindOptions() -// .limit(2) -// .projection(projection) -// assertEquals(doc1, findOne(Document(), options).get()!!) -// -// // Projection (remove "_id") and sort (by desc "hello") options -// options = FindOptions() -// .limit(2) -// .projection(projection) -// .sort(Document("hello", -1)) -// assertEquals(doc1, findOne(Document(), options).get()!!) -// } -// } -// -// @Test -// fun findOne_multipleDocuments() { -// with(getCollectionInternal()) { -// val doc1 = Document("hello", "world1") -// val doc2 = Document("hello", "world2") -// val doc3 = Document("hello", "world3") -// -// // Insert 3 documents -// insertMany(listOf(doc1, doc2, doc3)).get() -// assertEquals(3, count().get()) -// -// // Projection (remove "_id") and sort (by asc "hello") options -// val projection = Document("hello", 1).apply { this["_id"] = 0 } -// var options = FindOptions() -// .limit(2) -// .projection(projection) -// .sort(Document("hello", 1)) -// assertEquals(doc1, findOne(Document(), options).get()!!) -// -// // Projection (remove "_id") and sort (by desc "hello") options -// options = FindOptions() -// .limit(2) -// .projection(projection) -// .sort(Document("hello", -1)) -// assertEquals(doc3, findOne(Document(), options).get()!!) -// } -// } -// + @Test + fun findOne_explicitTypes() = runBlocking { + // Empty collection + assertNull(collection.findOne()) + + collection.insertMany((1..10).map { CollectionDataType("object-${it % 5}") }) + + // Explicit types + collection.findOne(filter = BsonDocument("name", "object-0")).run { + assertIs(this) + assertEquals("object-0", this["name"]!!.asString().value) + } + } + + @Test + fun findOne_extraFieldsAreDiscarded() { } + + @Test + fun findOne_missingFieldsGetsDefaults() { } + + @Test + abstract fun findOne_unknownCollection() + @Test fun findOne_fails() = runBlocking { assertFailsWithMessage("operator") { @@ -299,10 +289,10 @@ abstract class MongoCollectionTests { fun find() = runBlocking { RealmLog.level = LogLevel.ALL assertTrue { collection.find().isEmpty() } - assertTrue { collection.find().isEmpty() } + assertTrue { collection.find().isEmpty() } - val x: List = collection.insertMany(listOf(SyncDog("dog1"), SyncDog("dog2"))) - assertEquals(2, collection.find().size) + val x: List = collection.insertMany(listOf(CollectionDataType("dog1"), CollectionDataType("dog2"))) + assertEquals(2, collection.find().size) // collection.find(filter = null, projection = null, sort = null, limit = null) } @@ -360,29 +350,24 @@ abstract class MongoCollectionTests { // } // } // -// @Test -// fun find_fails() { -// with(getCollectionInternal()) { -// assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { -// find(Document("\$who", 1)).first().get() -// }.also { e -> -// assertTrue(e.errorMessage!!.contains("operator", true)) -// } -// } -// } -// + @Test + fun find_fails() = runBlocking { + assertFailsWithMessage("unknown top level operator: \$who.") { + collection.find(BsonDocument("\$who", 1)).first() + } + } @Test fun aggregate() = runBlocking { RealmLog.level = LogLevel.ALL collection.aggregate(listOf()) - collection.aggregate(listOf()) + collection.aggregate(listOf()) collection.aggregate(listOf()) - val x: List = collection.insertMany(listOf(SyncDog(name = "dog1"), SyncDog(name = "dog2"))) - collection.aggregate(listOf()) + val x: List = collection.insertMany(listOf(CollectionDataType(name = "dog1"), CollectionDataType(name = "dog2"))) + collection.aggregate(listOf()) - collection.aggregate(listOf(BsonDocument("\$sort", BsonDocument("name", -1)), BsonDocument("\$limit", 1))) + collection.aggregate(listOf(BsonDocument("\$sort", BsonDocument("name", -1)), BsonDocument("\$limit", 1))) } // @Test // fun aggregate() { @@ -454,24 +439,30 @@ abstract class MongoCollectionTests { RealmLog.level = LogLevel.ALL // Option 1 - Typed return value - BsonValue val insertedIdDocument: BsonValue = collection.insertOne(BsonDocument("name", "sadffds")) - val insertedIdDocument2: BsonValue = collection.insertOne(SyncDog("sadf")) + val insertedIdDocument2: Int = collection.insertOne(CollectionDataType("sadf")) // Option 2 - Explicit generic arguments to enabling fluent API - val id = collection.insertOne(SyncDog("sadf")) + val id = collection.insertOne(CollectionDataType("sadf")) // Option 3 - Automatically serialized object - val x2: ObjectId = collection.insertOne(SyncDog("sadf")) + val x2: Int = collection.insertOne(CollectionDataType("sadf")) val x3: ObjectId = collection.insertOne(BsonDocument("""{ "name" : "asdf" }""")) - - // TODO - // Existing primary key object } + @Test + fun insertOne_throwsOnExistingPrimaryKey() { } + @Test + fun insertOne_throwsOnUnknownFields() { } + + @Test + fun insertOne_throwsOnMissingRequiredFields() { } + @Test fun insertOne_throwsOnTypeMismatch() = runBlocking { + val collectionWithFixedSchema = collection(SyncDog::class) assertFailsWithMessage("insert not permitted") { - collection.insertOne(BsonDocument("""{"x": "a" }""")) + collectionWithFixedSchema.insertOne(BsonDocument("_id", ObjectId())) } } @@ -513,31 +504,31 @@ abstract class MongoCollectionTests { // } // } - @Test - fun insertMany() = runBlocking { - RealmLog.level = LogLevel.ALL - val x: List = collection.insertMany(listOf(SyncDog("a"))) - val syncDogIntIdCollection = database.collection("SyncDogIntId") - val elements = SyncDogIntId("a", Random.nextInt()) - val y: List = syncDogIntIdCollection.insertMany(listOf(elements)) - - assertFailsWithMessage("dup key") { - val z: List = syncDogIntIdCollection.insertMany(listOf(elements)) - } - - val typedCollection = collection.collection() - val z: List = typedCollection.insertMany(listOf(SyncDog("sadf"))) - val tyz = typedCollection.insertMany(listOf(SyncDog("sadf"))) - - val bsonSyncDogs: MongoCollection = database.collection("SyncDog") - val insertMany /*: List */ = bsonSyncDogs.insertMany(listOf(BsonDocument("name", "x"))) - - val syncDogs: MongoCollection = database.collection("SyncDog") - - val objectIds = syncDogs.insertMany(listOf(SyncDog("name"))) - - val objectIds2: List = syncDogs.insertMany(listOf(BsonDocument("name", "asdf"))) - } +// @Test +// fun insertMany() = runBlocking { +// RealmLog.level = LogLevel.ALL +// val x: List = collection.insertMany(listOf(SyncDog("a"))) +// val syncDogIntIdCollection = collection(SyncDogIntId::class) +// val elements = SyncDogIntId("a", Random.nextInt()) +// val y: List = syncDogIntIdCollection.insertMany(listOf(elements)) +// +// assertFailsWithMessage("dup key") { +// val z: List = syncDogIntIdCollection.insertMany(listOf(elements)) +// } +// +// val typedCollection = collection.reshape() +// val z: List = typedCollection.insertMany(listOf(SyncDog("sadf"))) +// val tyz = typedCollection.insertMany(listOf(SyncDog("sadf"))) +// +// val bsonSyncDogs: MongoCollection = collection.reshape() +// val insertMany /*: List */ = bsonSyncDogs.insertMany(listOf(BsonDocument("name", "x"))) +// +// val syncDogs: MongoCollection = database.collection("SyncDog") +// +// val objectIds = syncDogs.insertMany(listOf(SyncDog("name"))) +// +// val objectIds2: List = syncDogs.insertMany(listOf(BsonDocument("name", "asdf"))) +// } // // @Test @@ -652,68 +643,53 @@ abstract class MongoCollectionTests { RealmLog.level = LogLevel.ALL assertFalse { collection.deleteOne(BsonDocument()) } - // A bit ann - val elements = SyncDog("x") - assertEquals(2, collection.insertMany(listOf(elements, elements)).size) - assertTrue { collection.deleteOne(BsonDocument("""{ "name": "x" }""")) } + assertEquals( + 2, + collection.insertMany( + listOf( + CollectionDataType("object-1"), + CollectionDataType("object-1") + ) + ).size + ) + assertEquals(2, collection.count(BsonDocument("""{ "name": "object-1" }"""))) + assertTrue { collection.deleteOne(BsonDocument("""{ "name": "object-1" }""")) } + assertEquals(1, collection.count(BsonDocument("""{ "name": "object-1" }"""))) + } + + @Test + fun deleteOne_fails() = runBlocking { + assertFailsWithMessage("unknown top level operator: \$who.") { + collection.deleteOne(BsonDocument("\$who", 1)) + } } -// @Test -// fun deleteOne_singleDocument() { -// with(getCollectionInternal()) { -// assertEquals(0, deleteOne(Document()).get()!!.deletedCount) -// assertEquals(0, deleteOne(Document("hello", "world")).get()!!.deletedCount) -// -// val doc1 = Document("hello", "world") -// -// insertOne(doc1).get() -// assertEquals(1, deleteOne(doc1).get()!!.deletedCount) -// assertEquals(0, count().get()) -// } -// } -// -// @Test -// fun deleteOne_fails() { -// with(getCollectionInternal()) { -// assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { -// deleteOne(Document("\$who", 1)).get() -// }.also { e -> -// assertTrue(e.errorMessage!!.contains("operator", true)) -// } -// } -// } -// -// @Test -// fun deleteOne_multipleDocuments() { -// with(getCollectionInternal()) { -// assertEquals(0, count().get()) -// -// val rawDoc = Document("hello", "world") -// val doc1 = Document(rawDoc) -// val doc1b = Document(rawDoc) -// val doc2 = Document("foo", "bar") -// val doc3 = Document("42", "666") -// insertMany(listOf(doc1, doc1b, doc2, doc3)).get() -// assertEquals(1, deleteOne(rawDoc).get()!!.deletedCount) -// assertEquals(1, deleteOne(Document()).get()!!.deletedCount) -// assertEquals(2, count().get()) -// } -// } -// @Test fun deleteMany() = runBlocking { // Argument wrapper DSL RealmLog.level = LogLevel.ALL assertEquals(0, collection.deleteMany(BsonDocument())) - // A bit ann - val elements = SyncDog("x") - assertEquals(2, collection.insertMany(listOf(elements, elements)).size) - assertEquals(2, collection.deleteMany(BsonDocument("""{ "name": "x" }"""))) + assertEquals( + 2, + collection.insertMany( + listOf( + CollectionDataType("object-1"), + CollectionDataType("object-1") + ) + ).size + ) + assertEquals(2, collection.deleteMany(BsonDocument("""{ "name": "object-1" }"""))) assertEquals( 3, - collection.insertMany(listOf(elements, elements, elements)).size + collection.insertMany( + listOf( + CollectionDataType("object-1"), + CollectionDataType("object-1"), + CollectionDataType("object-1") + ) + ).size ) assertEquals(3, collection.deleteMany(BsonDocument())) } @@ -767,16 +743,20 @@ abstract class MongoCollectionTests { // } // @Test - fun updateOne() = runBlocking { + open fun updateOne() = runBlocking { // Argument wrapper DSL assertEquals(0, collection.count()) RealmLog.level = LogLevel.ALL - val elements = SyncDog("x") assertEquals( 4, - collection.insertMany( - listOf(elements, elements, elements, elements) + collection.insertMany( + listOf( + CollectionDataType("object-1"), + CollectionDataType("object-1"), + CollectionDataType("object-1"), + CollectionDataType("object-1") + ) ).size ) assertEquals(4, collection.count()) @@ -790,32 +770,32 @@ abstract class MongoCollectionTests { // update with match match val updateWithMatch = collection.updateOne( - BsonDocument("""{ "name": "x"}"""), - BsonDocument("""{ "name": "y"}"""), + BsonDocument("""{ "name": "object-1"}"""), + BsonDocument("""{ "name": "object-2"}"""), ) assertEquals(true to null, updateWithMatch) assertEquals(4, collection.count()) - assertEquals(3, collection.count(filter = BsonDocument("""{"name": "x"}"""))) - assertEquals(1, collection.count(filter = BsonDocument("""{"name": "y"}"""))) + assertEquals(3, collection.count(filter = BsonDocument("""{"name": "object-1"}"""))) + assertEquals(1, collection.count(filter = BsonDocument("""{"name": "object-2"}"""))) // upsert no match - val upsertWithoutMatch = collection.updateOne( - BsonDocument("""{ "name": "z"}"""), BsonDocument(""" { "name": "y"}"""), upsert = true + val upsertWithoutMatch = collection.updateOne( + BsonDocument("""{ "name": "object-3"}"""), BsonDocument(""" { "name": "object-2", "_id" : ${Random.nextInt()}}"""), upsert = true ) upsertWithoutMatch.let { (updated, upsertedId) -> assertFalse(updated) - assertIs(upsertedId) + assertIs(upsertedId) } assertEquals(5, collection.count()) - assertEquals(2, collection.count(filter = BsonDocument("""{"name": "y"}"""))) + assertEquals(2, collection.count(filter = BsonDocument("""{"name": "object-2"}"""))) // upsert with match val upsertWithMatch = collection.updateOne( - BsonDocument("""{ "name": "y"}"""), BsonDocument(""" { "name": "z"}"""), upsert = true + BsonDocument("""{ "name": "object-2"}"""), BsonDocument(""" { "name": "object-3"}"""), upsert = true ) assertEquals(true to null, upsertWithMatch) assertEquals(5, collection.count()) - assertEquals(1, collection.count(filter = BsonDocument("""{"name": "y"}"""))) + assertEquals(1, collection.count(filter = BsonDocument("""{"name": "object-2"}"""))) } // @Test @@ -882,12 +862,12 @@ abstract class MongoCollectionTests { RealmLog.level = LogLevel.ALL assertEquals( 4, - collection.insertMany( + collection.insertMany( listOf( - SyncDog("x"), - SyncDog("x"), - SyncDog("y"), - SyncDog("z") + CollectionDataType("x"), + CollectionDataType("x"), + CollectionDataType("y"), + CollectionDataType("z") ) ).size ) @@ -910,12 +890,12 @@ abstract class MongoCollectionTests { assertEquals(4, collection.count()) // upsert no match - val upsertWithoutMatch = collection.updateMany( - BsonDocument("""{ "name": "NOMATCH"}"""), BsonDocument(""" { "name": "UPSERTED"}"""), upsert = true + val upsertWithoutMatch = collection.updateMany( + BsonDocument("""{ "name": "NOMATCH"}"""), BsonDocument(""" { "name": "UPSERTED", "_id" : ${Random.nextInt()}}"""), upsert = true ) upsertWithoutMatch.let { assertEquals(0, it.first) - assertIs(it.second) + assertIs(it.second) } assertEquals(5, collection.count()) assertEquals(1, collection.count(filter = BsonDocument("""{"name": "UPSERTED"}"""))) @@ -1006,17 +986,17 @@ abstract class MongoCollectionTests { @Test fun findOneAndUpdate() = runBlocking { RealmLog.level = LogLevel.ALL - assertNull(collection.findOneAndUpdate(BsonDocument(), BsonDocument())) - collection.insertMany( + assertNull(collection.findOneAndUpdate(BsonDocument(), BsonDocument())) + collection.insertMany( listOf( - SyncDog("dog1"), - SyncDog("dog1"), - SyncDog("dog2") + CollectionDataType("object-1"), + CollectionDataType("object-1"), + CollectionDataType("object-2") ) ) - collection.findOneAndUpdate( + collection.findOneAndUpdate( BsonDocument(), - BsonDocument("""{ "name": "dog1" }"""), + BsonDocument("""{ "name": "object-1" }"""), upsert = true ) } @@ -1185,17 +1165,17 @@ abstract class MongoCollectionTests { @Test fun findOneAndReplace() = runBlocking { RealmLog.level = LogLevel.ALL - assertNull(collection.findOneAndReplace(BsonDocument(), BsonDocument())) - collection.insertMany( + assertNull(collection.findOneAndReplace(BsonDocument(), BsonDocument())) + collection.insertMany( listOf( - SyncDog("dog1"), - SyncDog("dog1"), - SyncDog("dog2") + CollectionDataType("object-1"), + CollectionDataType("object-1"), + CollectionDataType("object-2") ) ) - val x = collection.findOneAndReplace( + val x = collection.findOneAndReplace( BsonDocument(), - BsonDocument("""{ "name": "dog1" }"""), + BsonDocument("""{ "name": "object-1" }"""), upsert = true ) } @@ -1335,17 +1315,17 @@ abstract class MongoCollectionTests { @Test fun findOneAndDelete() = runBlocking { RealmLog.level = LogLevel.ALL - assertNull(collection.findOneAndDelete(BsonDocument(), BsonDocument())) - collection.insertMany( + assertNull(collection.findOneAndDelete(BsonDocument(), BsonDocument())) + collection.insertMany( listOf( - SyncDog("dog1"), - SyncDog("dog1"), - SyncDog("dog2") + CollectionDataType("object-1"), + CollectionDataType("object-1"), + CollectionDataType("object-2") ) ) - val x: SyncDog? = collection.findOneAndDelete( + val x: CollectionDataType? = collection.findOneAndDelete( BsonDocument(), - BsonDocument("""{ "name": "dog1" }"""), + BsonDocument("""{ "name": "object-1" }"""), ) } // @@ -1430,6 +1410,13 @@ abstract class MongoCollectionTests { // } } +inline fun MongoCollectionTests.collection(clazz: KClass): MongoCollection { + return when (this) { + is MongoCollectionFromDatabaseTests -> database.collection(T::class.simpleName!!) + is MongoCollectionFromClientTests -> client.collection() + } +} + @Suppress("name") @OptIn(ExperimentalKBsonSerializerApi::class) inline operator fun BsonDocument.Companion.invoke( @@ -1439,15 +1426,23 @@ inline operator fun BsonDocument.Companion.invoke( return BsonDocument(key to EJson.Default.encodeToBsonValue(value)) } +@Serializable +class Unknown : RealmObject { + var name = "Unknown" +} + +// Strictly defined in schema in TestAppInitializer.kt @Serializable class SyncDog(var name: String) : RealmObject { constructor() : this("Default") } @Serializable -data class SyncDogIntId(val name: String, val _id: Int) +class CollectionDataType(var name: String? = "Default", var _id: Int? = Random.nextInt()) : RealmObject { + constructor() : this("Default") +} -@PersistedName("SyncDog") +@PersistedName("CollectionDataType") class CustomDataType(var name: String) : RealmObject { @Suppress("unused") constructor() : this("Default") @@ -1491,7 +1486,7 @@ class CustomIdSerializer : KSerializer { @OptIn(ExperimentalKBsonSerializerApi::class) val customEjsonSerializer = EJson( - ignoreUnknownKeys = false, +// ignoreUnknownKeys = false, serializersModule = SerializersModule { contextual(CustomDataType::class, CustomDataTypeSerializer()) contextual(CustomIdType::class, CustomIdSerializer()) diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoDatabaseTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoDatabaseTests.kt index ca7aa5d567..04b3e62c43 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoDatabaseTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoDatabaseTests.kt @@ -26,9 +26,10 @@ import io.realm.kotlin.mongodb.mongo.insertOne import io.realm.kotlin.test.mongodb.TestApp import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage import kotlinx.serialization.SerializationException +import org.mongodb.kbson.BsonDocument import org.mongodb.kbson.BsonValue import org.mongodb.kbson.ExperimentalKBsonSerializerApi -import org.mongodb.kbson.ObjectId +import kotlin.random.Random import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test @@ -70,19 +71,33 @@ class MongoDatabaseTests { assertEquals(databaseName, database.name) } + @Test + fun collection_defaultTypes() = runBlocking { + val collection = database.collection("CollectionDataType") + val value = collection.insertOne(BsonDocument("name", "object-1")) + assertIs(value) + } + + @Test + fun collection_typed() = runBlocking { + val collection = database.collection("CollectionDataType") + val value = collection.insertOne(CollectionDataType("object-1", Random.nextInt())) + assertIs(value) + } + @Test fun collection_defaultSerializer() = runBlocking { - assertIs(database.collection("SyncDog").insertOne(SyncDog("dog-1"))) + assertIs(database.collection("CollectionDataType").insertOne(CollectionDataType("object-1"))) } @Test fun collection_customSerializer() = runBlocking { - val collectionWithDefaultSerializer = database.collection("SyncDog") + val collectionWithDefaultSerializer = database.collection("CollectionDataType") assertFailsWithMessage("Serializer for class 'CustomDataType' is not found.") { - collectionWithDefaultSerializer.insertOne(CustomDataType("dog-1")) + collectionWithDefaultSerializer.insertOne(CustomDataType("object-1")) } - val collectionWithCustomSerializer = database.collection("SyncDog", customEjsonSerializer) - assertIs(collectionWithCustomSerializer.insertOne(CustomDataType("dog-1"))) + val collectionWithCustomSerializer = database.collection("CollectionDataType", customEjsonSerializer) + assertIs(collectionWithCustomSerializer.insertOne(CustomDataType("object-1"))) } } From bc3acb8a57f4a814005b965ac3571596862aa839 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Mon, 18 Dec 2023 09:35:37 +0100 Subject: [PATCH 09/36] Another round for linting --- .../io/realm/kotlin/test/mongodb/common/UserTests.kt | 2 -- .../test/mongodb/common/mongo/MongoCollectionTests.kt | 10 ++++------ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserTests.kt index f2e54b88bb..0dd338d119 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserTests.kt @@ -22,8 +22,6 @@ import io.realm.kotlin.Realm import io.realm.kotlin.annotations.ExperimentalRealmSerializerApi import io.realm.kotlin.internal.platform.fileExists import io.realm.kotlin.internal.platform.runBlocking -import io.realm.kotlin.log.LogLevel -import io.realm.kotlin.log.RealmLog import io.realm.kotlin.mongodb.AuthenticationProvider import io.realm.kotlin.mongodb.Credentials import io.realm.kotlin.mongodb.User diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt index f895d9ba5c..ed40cdb356 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt @@ -222,7 +222,7 @@ abstract sealed class MongoCollectionTests { assertEquals("object-0", this.name) } - // projection + // Projection val collectionDataType = CollectionDataType("object-6") assertEquals(collectionDataType._id, collection.insertOne(collectionDataType)) @@ -244,7 +244,7 @@ abstract sealed class MongoCollectionTests { assertEquals(collectionDataType._id, this._id) } - // sort + // Sort collection.findOne(sort = BsonDocument(mapOf("name" to BsonInt32(-1)))).run { assertIs(this) assertEquals("object-6", this.name) @@ -287,13 +287,11 @@ abstract sealed class MongoCollectionTests { @Test fun find() = runBlocking { - RealmLog.level = LogLevel.ALL assertTrue { collection.find().isEmpty() } - assertTrue { collection.find().isEmpty() } - val x: List = collection.insertMany(listOf(CollectionDataType("dog1"), CollectionDataType("dog2"))) + val ids: List = collection.insertMany((1..10).map { CollectionDataType("object-${it % 5}") }) assertEquals(2, collection.find().size) - // + collection.find(filter = null, projection = null, sort = null, limit = null) } From 82c85ac845df2ce466baeea21affc1d7706f982c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Mon, 18 Dec 2023 09:38:29 +0100 Subject: [PATCH 10/36] Add CHANGELOG entry --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 223128527d..58af441976 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ * None. ### Enhancements -* None. +* Add Mongo Client API to access Atlas App Service collections. (Issue [#972](https://github.com/realm/realm-kotlin/issues/972)) ### Fixed * None. From e1c00c8f6fa99b75cef1f0137c271a103c89336b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Mon, 18 Dec 2023 14:01:59 +0100 Subject: [PATCH 11/36] More test coverage --- .../common/mongo/MongoCollectionTests.kt | 497 +++++++----------- 1 file changed, 186 insertions(+), 311 deletions(-) diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt index ed40cdb356..dcb6f9907e 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt @@ -69,6 +69,8 @@ import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertIs +import kotlin.test.assertNotEquals +import kotlin.test.assertNotNull import kotlin.test.assertNull import kotlin.test.assertTrue @@ -181,7 +183,6 @@ abstract sealed class MongoCollectionTests { @Test fun count() = runBlocking { - RealmLog.level = LogLevel.ALL assertEquals(0, collection.count()) collection.insertMany((1..10).map { CollectionDataType("object-${it % 5}") }) @@ -208,7 +209,6 @@ abstract sealed class MongoCollectionTests { RealmLog.level = LogLevel.ALL // Empty collections assertNull(collection.findOne()) - assertNull(collection.findOne()) collection.insertMany((1..10).map { CollectionDataType("object-${it % 5}") }) @@ -232,6 +232,7 @@ abstract sealed class MongoCollectionTests { assertEquals(collectionDataType._id, this._id) } + // Project without name collection.findOne( filter = BsonDocument("name", "object-6"), projection = BsonDocument("""{ "name" : 0}""") @@ -243,6 +244,17 @@ abstract sealed class MongoCollectionTests { // _id is included by default assertEquals(collectionDataType._id, this._id) } + // Project without _id, have to be explicitly excluded + collection.findOne( + filter = BsonDocument("name", "object-6"), + projection = BsonDocument("""{ "_id" : 0}""") + ).run { + assertIs(this) + assertEquals(collectionDataType.name, name) + // FIXME Should be "Default" but serialization fails if field is non-nullable even though descriptor correctly has isOptional=true + // _id is included by default + assertNotEquals(collectionDataType._id, this._id) + } // Sort collection.findOne(sort = BsonDocument(mapOf("name" to BsonInt32(-1)))).run { @@ -289,65 +301,75 @@ abstract sealed class MongoCollectionTests { fun find() = runBlocking { assertTrue { collection.find().isEmpty() } - val ids: List = collection.insertMany((1..10).map { CollectionDataType("object-${it % 5}") }) - assertEquals(2, collection.find().size) + val names = (1..10).map { "object-${it % 5}" } + val ids: List = collection.insertMany(names.map { CollectionDataType(it) }) + + assertEquals(10, collection.find().size) - collection.find(filter = null, projection = null, sort = null, limit = null) + collection.find(filter = BsonDocument("name", "object-1")).let { + assertEquals(2, it.size) + } + + // Limit + collection.find( + limit = 5, + ).let { + assertEquals(5, it.size) + } + + // Projection + collection.find( + filter = BsonDocument("name", "object-1"), + projection = BsonDocument("""{ "name" : 0}"""), + ).let { + assertEquals(2, it.size) + it.forEach { + // FIXME Should be "Default" but serialization fails if field is non-nullable even though descriptor correctly has isOptional=true + // assertEquals("Default", it.name) + assertNull(it.name) + // _id is included by default + assertTrue(it._id in ids) + } + } + collection.find( + filter = BsonDocument("name", "object-1"), + projection = BsonDocument("""{ "_id" : 0}"""), + ).let { + assertEquals(2, it.size) + it.forEach { + assertNotNull(it.name) + assertNotEquals("Default", it.name) + // Objects have new ids + // FIXME Should be assigned new Objects ids, but serialization fails if field is non-nullable even though descriptor correctly has isOptional=true + // assertFalse(it._id in ids) + assertNull(it._id) + } + } + + // Sort + collection.find(sort = BsonDocument("""{ "name" : -1}""")).let { results -> + assertEquals(names.sorted().reversed(), results.map { it.name }) + } + collection.find(sort = BsonDocument("""{ "name" : 1}""")).let { results -> + assertEquals(names.sorted(), results.map { it.name }) + } + } + + @Test + fun find_explicitTypes() = runBlocking { + collection.find().let { assertTrue { it.isEmpty() } } + + val names = (1..10).map { "object-${it % 5}" } + collection.insertMany(names.map { BsonDocument("name", it) }) + + collection.find().let { results -> + results.forEach { + assertIs(it) + assertTrue { it.asDocument()["name"]!!.asString().value in names } + } + } } -// -// @Test -// fun find() { -// with(getCollectionInternal()) { -// // Find on an empty collection returns false on hasNext and null on first -// var iter = find() -// assertFalse(iter.iterator().get()!!.hasNext()) -// assertNull(iter.first().get()) -// -// val doc1 = Document("hello", "world") -// val doc2 = Document("hello", "friend") -// doc2["proj"] = "field" -// insertMany(listOf(doc1, doc2)).get() -// -// // Iterate after inserting two documents -// assertTrue(iter.iterator().get()!!.hasNext()) -// assertEquals(doc1, iter.first().get()!!.withoutId()) -// -// // Get next with sort by desc "_id" and limit to 1 document -// assertEquals(doc2, -// iter.limit(1) -// .sort(Document("_id", -1)) -// .iterator().get()!! -// .next().withoutId()) -// -// // Find first document -// iter = find(doc1) -// assertTrue(iter.iterator().get()!!.hasNext()) -// assertEquals(doc1, -// iter.iterator().get()!! -// .next().withoutId()) -// -// // Find with filter for first document -// iter = find().filter(doc1) -// assertTrue(iter.iterator().get()!!.hasNext()) -// assertEquals(doc1, -// iter.iterator().get()!! -// .next().withoutId()) -// -// // Find with projection shows "proj" in result -// val expected = Document("proj", "field") -// assertEquals(expected, -// find(doc2) -// .projection(Document("proj", 1)) -// .iterator().get()!! -// .next().withoutId()) -// -// // Getting a new iterator returns first element on tryNext -// val asyncIter = iter.iterator().get()!! -// assertEquals(doc1, asyncIter.tryNext().withoutId()) -// } -// } -// @Test fun find_fails() = runBlocking { assertFailsWithMessage("unknown top level operator: \$who.") { @@ -357,283 +379,136 @@ abstract sealed class MongoCollectionTests { @Test fun aggregate() = runBlocking { - RealmLog.level = LogLevel.ALL - collection.aggregate(listOf()) - collection.aggregate(listOf()) - collection.aggregate(listOf()) + collection.aggregate(listOf()).let { assertTrue { it.isEmpty() } } + + val names = (1..10).map { "object-${it % 5}" } + val ids: List = collection.insertMany(names.map { CollectionDataType(it) }) - val x: List = collection.insertMany(listOf(CollectionDataType(name = "dog1"), CollectionDataType(name = "dog2"))) - collection.aggregate(listOf()) + collection.aggregate(listOf()).let { + assertEquals(10, it.size) + it.forEach { + assertTrue { it.name in names } + } + } - collection.aggregate(listOf(BsonDocument("\$sort", BsonDocument("name", -1)), BsonDocument("\$limit", 1))) + collection.aggregate(listOf(BsonDocument("\$sort", BsonDocument("name", -1)), BsonDocument("\$limit", 2))).let { + assertEquals(2, it.size) + it.forEach { + assertEquals("object-4", it.name) + } + } } -// @Test -// fun aggregate() { -// with(getCollectionInternal()) { -// // Aggregate on an empty collection returns false on hasNext and null on first -// var iter = aggregate(listOf()) -// assertFalse(iter.iterator().get()!!.hasNext()) -// assertNull(iter.first().get()) -// -// // Iterate after inserting two documents -// val doc1 = Document("hello", "world") -// val doc2 = Document("hello", "friend") -// insertMany(listOf(doc1, doc2)).get() -// assertTrue(iter.iterator().get()!!.hasNext()) -// assertEquals(doc1.withoutId(), iter.first().get()!!.withoutId()) -// -// // Aggregate with pipeline, sort by desc "_id" and limit to 1 document -// iter = aggregate(listOf(Document("\$sort", Document("_id", -1)), Document("\$limit", 1))) -// assertEquals(doc2.withoutId(), -// iter.iterator().get()!! -// .next().withoutId()) -// -// // Aggregate with pipeline, match first document -// iter = aggregate(listOf(Document("\$match", doc1))) -// assertTrue(iter.iterator().get()!!.hasNext()) -// assertEquals(doc1.withoutId(), iter.iterator().get()!!.next().withoutId()) -// } -// } -// -// @Test -// fun aggregate_fails() { -// with(getCollectionInternal()) { -// assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { -// aggregate(listOf(Document("\$who", 1))).first().get() -// }.also { e -> -// assertTrue(e.errorMessage!!.contains("pipeline", true)) -// } -// } -// } -// -// @Test -// fun insertOne() = runBlocking { -// with(getCollectionInternal()) { -// val doc1 = Document("hello", "world").apply { this["_id"] = ObjectId() } -// assertEquals(doc1.getObjectId("_id"), insertOne(doc1).get()!!.insertedId.asObjectId().value) -// assertEquals(1, count().get()) -// -// val doc2 = Document("hello", "world") -// val insertOneResult = insertOne(doc2).get()!! -// assertNotNull(insertOneResult.insertedId.asObjectId().value) -// assertEquals(2, count().get()) -// } -// } + @Test + fun aggregate_fails() = runBlocking { + assertFailsWithMessage("Unrecognized pipeline stage name: '\$who'.") { + collection.aggregate(pipeline = listOf(BsonDocument("\$who", 1))) + } + } @Test fun insertOne() = runBlocking { -// with(getCollectionInternal()) { -// val doc1 = Document("hello", "world").apply { this["_id"] = ObjectId() } -// assertEquals(doc1.getObjectId("_id"), insertOne(doc1).get()!!.insertedId.asObjectId().value) -// assertEquals(1, count().get()) -// -// val doc2 = Document("hello", "world") -// val insertOneResult = insertOne(doc2).get()!! -// assertNotNull(insertOneResult.insertedId.asObjectId().value) -// assertEquals(2, count().get()) -// } + assertEquals(0, collection.find().size) - RealmLog.level = LogLevel.ALL - // Option 1 - Typed return value - BsonValue - val insertedIdDocument: BsonValue = collection.insertOne(BsonDocument("name", "sadffds")) - val insertedIdDocument2: Int = collection.insertOne(CollectionDataType("sadf")) + collection.insertOne(CollectionDataType("object-1")).let { + assertIs(it) + } + assertEquals(1, collection.find().size) + } - // Option 2 - Explicit generic arguments to enabling fluent API - val id = collection.insertOne(CollectionDataType("sadf")) + @Test + fun insertOne_explicitTypes() = runBlocking { + assertEquals(0, collection.find().size) + // Inserting document without _id will use ObjectId as _id + collection.insertOne(BsonDocument("name", "object-1")).let { + assertIs(it) + } + // Inserted document will have ObjectId key and cannot be serialized into CollectionDataType + // so find must also use BsonDocument + assertEquals(1, collection.find().size) + } - // Option 3 - Automatically serialized object - val x2: Int = collection.insertOne(CollectionDataType("sadf")) + @Test + fun insertOne_throwsOnExistingPrimaryKey() = runBlocking { + assertEquals(0, collection.find().size) - val x3: ObjectId = collection.insertOne(BsonDocument("""{ "name" : "asdf" }""")) + val document = CollectionDataType("object-1") + collection.insertOne(document).let { + assertIs(it) + } + assertFailsWithMessage("Duplicate key error") { + collection.insertOne(document) + } + assertEquals(1, collection.find().size) + } + + @Test + fun insertOne_throwsOnMissingRequiredFields() = runBlocking { + assertFailsWithMessage("insert not permitted") { + collection.insertOne(BsonDocument()) + } } @Test - fun insertOne_throwsOnExistingPrimaryKey() { } + fun insertOne_throwsOnTypeMismatch() = runBlocking { + assertFailsWithMessage("insert not permitted") { + collection.insertOne(BsonDocument(mapOf("_id" to ObjectId(), "name" to BsonString("object-1")))) + } + } @Test - fun insertOne_throwsOnUnknownFields() { } + fun insertMany() = runBlocking { + assertEquals(0, collection.find().size) + + collection.insertMany((1..10).map { CollectionDataType("object-${it % 5}") }) + + assertEquals(10, collection.find().size) + } @Test - fun insertOne_throwsOnMissingRequiredFields() { } + fun insertMany_explictTyped() = runBlocking { + assertEquals(0, collection.find().size) + + collection.insertMany((1..10).map { BsonDocument("name", "object-${it % 5}") }).let { + assertEquals(10, it.size) + it.forEach { + assertIs(it) + } + } + assertEquals(10, collection.find().size) + } @Test - fun insertOne_throwsOnTypeMismatch() = runBlocking { - val collectionWithFixedSchema = collection(SyncDog::class) - assertFailsWithMessage("insert not permitted") { - collectionWithFixedSchema.insertOne(BsonDocument("_id", ObjectId())) + fun insertMany_throwsOnEmptyList() = runBlocking { + assertFailsWithMessage("must provide at least one element") { + collection.insertMany(emptyList()) } } -// -// @Test -// fun insertOne_throwsWhenMixingIdTypes() { -// with(getCollectionInternal()) { -// // The default collection uses ObjectId for "_id" -// val doc1 = Document("hello", "world").apply { this["_id"] = 666 } -// assertFailsWith { -// insertOne(doc1).get()!! -// }.let { e -> -// assertEquals("insert not permitted", e.errorMessage) -// } -// } -// } -// -// @Test -// fun insertOne_integerId() { -// with(getCollectionInternal(COLLECTION_NAME_ALT)) { -// val doc1 = Document("hello", "world").apply { this["_id"] = 666 } -// val insertOneResult = insertOne(doc1).get()!! -// assertEquals(doc1.getInteger("_id"), insertOneResult.insertedId.asInt32().value) -// assertEquals(1, count().get()) -// } -// } -// -// @Test -// fun insertOne_fails() { -// with(getCollectionInternal()) { -// val doc1 = Document("hello", "world").apply { this["_id"] = ObjectId() } -// insertOne(doc1).get() -// -// assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { -// insertOne(doc1).get() -// }.also { e -> -// assertTrue(e.errorMessage!!.contains("duplicate", true)) -// } -// } -// } + @Test + fun insertMany_throwsOnExistingPrimaryKey() = runBlocking { + assertEquals(0, collection.find().size) -// @Test -// fun insertMany() = runBlocking { -// RealmLog.level = LogLevel.ALL -// val x: List = collection.insertMany(listOf(SyncDog("a"))) -// val syncDogIntIdCollection = collection(SyncDogIntId::class) -// val elements = SyncDogIntId("a", Random.nextInt()) -// val y: List = syncDogIntIdCollection.insertMany(listOf(elements)) -// -// assertFailsWithMessage("dup key") { -// val z: List = syncDogIntIdCollection.insertMany(listOf(elements)) -// } -// -// val typedCollection = collection.reshape() -// val z: List = typedCollection.insertMany(listOf(SyncDog("sadf"))) -// val tyz = typedCollection.insertMany(listOf(SyncDog("sadf"))) -// -// val bsonSyncDogs: MongoCollection = collection.reshape() -// val insertMany /*: List */ = bsonSyncDogs.insertMany(listOf(BsonDocument("name", "x"))) -// -// val syncDogs: MongoCollection = database.collection("SyncDog") -// -// val objectIds = syncDogs.insertMany(listOf(SyncDog("name"))) -// -// val objectIds2: List = syncDogs.insertMany(listOf(BsonDocument("name", "asdf"))) -// } + val document = CollectionDataType("object-1") + assertFailsWithMessage("Duplicate key error") { + collection.insertMany(listOf(document.apply { name = "sadf" }, document)) + } + // FIXME Wouldn't have expected one of the documents to be inserted :thinking: + assertEquals(1, collection.find().size) + } -// -// @Test -// fun insertMany_singleDocument() { -// with(getCollectionInternal()) { -// val doc1 = Document("hello", "world").apply { this["_id"] = ObjectId() } -// -// assertEquals(doc1.getObjectId("_id"), -// insertMany(listOf(doc1)).get()!!.insertedIds[0]!!.asObjectId().value) -// val doc2 = Document("hello", "world") -// -// assertNotEquals(doc1.getObjectId("_id"), insertMany(listOf(doc2)).get()!!.insertedIds[0]!!.asObjectId().value) -// -// val doc3 = Document("one", "two") -// val doc4 = Document("three", 4) -// -// insertMany(listOf(doc3, doc4)).get() -// } -// } -// -// @Test -// fun insertMany_singleDocument_fails() { -// with(getCollectionInternal()) { -// val doc1 = Document("hello", "world").apply { this["_id"] = ObjectId() } -// insertMany(listOf(doc1)).get() -// -// assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { -// insertMany(listOf(doc1)).get() -// }.also { e -> -// assertTrue(e.errorMessage!!.contains("duplicate", true)) -// } -// } -// } -// -// @Test -// fun insertMany_multipleDocuments() { -// with(getCollectionInternal()) { -// val doc1 = Document("hello", "world").apply { this["_id"] = ObjectId() } -// val doc2 = Document("hello", "world").apply { this["_id"] = ObjectId() } -// val documents = listOf(doc1, doc2) -// -// insertMany(documents).get()!! -// .insertedIds -// .forEach { entry -> -// assertEquals(documents[entry.key.toInt()]["_id"], entry.value.asObjectId().value) -// } -// -// val doc3 = Document("one", "two") -// val doc4 = Document("three", 4) -// -// insertMany(listOf(doc3, doc4)).get() -// assertEquals(4, count().get()) -// } -// } -// -// @Test -// fun insertMany_multipleDocuments_IntegerId() { -// with(getCollectionInternal(COLLECTION_NAME_ALT)) { -// val doc1 = Document("hello", "world").apply { this["_id"] = 42 } -// val doc2 = Document("hello", "world").apply { this["_id"] = 42 + 1 } -// val documents = listOf(doc1, doc2) -// -// insertMany(documents).get()!! -// .insertedIds -// .forEach { entry -> -// assertEquals(documents[entry.key.toInt()]["_id"], entry.value.asInt32().value) -// } -// -// val doc3 = Document("one", "two") -// val doc4 = Document("three", 4) -// -// insertMany(listOf(doc3, doc4)).get() -// assertEquals(4, count().get()) -// } -// } -// -// @Test -// fun insertMany_throwsWhenMixingIdTypes() { -// with(getCollectionInternal()) { -// val doc1 = Document("hello", "world").apply { this["_id"] = 42 } -// val doc2 = Document("hello", "world").apply { this["_id"] = 42 + 1 } -// val documents = listOf(doc1, doc2) -// -// assertFailsWith { -// insertMany(documents).get()!! -// }.let { e -> -// assertEquals("insert not permitted", e.errorMessage) -// } -// } -// } -// -// @Test -// fun insertMany_multipleDocuments_fails() { -// with(getCollectionInternal()) { -// val doc1 = Document("hello", "world").apply { this["_id"] = ObjectId() } -// val doc2 = Document("hello", "world").apply { this["_id"] = ObjectId() } -// val documents = listOf(doc1, doc2) -// insertMany(documents).get() -// -// assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { -// insertMany(documents).get() -// }.also { e -> -// assertTrue(e.errorMessage!!.contains("duplicate", true)) -// } -// } -// } -// + @Test + fun insertMany_throwsOnMissingRequiredFields() = runBlocking { + assertFailsWithMessage("insert not permitted") { + collection.insertOne(BsonDocument()) + } + } + + @Test + fun insertMany_throwsOnTypeMismatch() = runBlocking { + assertFailsWithMessage("insert not permitted") { + collection.insertOne(BsonDocument(mapOf("_id" to ObjectId(), "name" to BsonString("object-1")))) + } + } @Test fun deleteOne() = runBlocking { From e492629fb43b5ae1bf9fc56af13b8267908c7ce4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Mon, 18 Dec 2023 17:19:50 +0100 Subject: [PATCH 12/36] More testing --- .../mongodb/internal/MongoCollectionImpl.kt | 6 +- .../kotlin/mongodb/mongo/MongoCollection.kt | 4 +- .../common/mongo/MongoCollectionTests.kt | 1060 +++++++---------- 3 files changed, 416 insertions(+), 654 deletions(-) diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt index 725d105571..ea3cb82fa7 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt @@ -97,7 +97,7 @@ internal abstract class MongoCollectionImpl constructor( internal suspend fun find(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null, limit: Long? = null): BsonValue = call("find") { filter?.let { put("query", it) } - projection?.let { put("projection", it) } + projection?.let { put("project", it) } sort?.let { put("sort", it) } limit?.let { put("limit", BsonInt64(it)) } } @@ -182,7 +182,7 @@ internal abstract class MongoCollectionImpl constructor( projection?.let { put("projection", projection) } sort?.let { put("sort", sort) } put("upsert", BsonBoolean(upsert)) - put("returnNewDoc", BsonBoolean(returnNewDoc)) + put("returnNewDocument", BsonBoolean(returnNewDoc)) } @Suppress("LongParameterList") @@ -200,7 +200,7 @@ internal abstract class MongoCollectionImpl constructor( projection?.let { put("projection", projection) } sort?.let { put("sort", sort) } put("upsert", BsonBoolean(upsert)) - put("returnNewDoc", BsonBoolean(returnNewDoc)) + put("returnNewDocument", BsonBoolean(returnNewDoc)) } @PublishedApi diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt index ddea4a42d4..0201837ebc 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt @@ -194,14 +194,14 @@ public suspend inline fun MongoCollection<*, *>.findOneAndUpdate( @Suppress("LongParameterList") public suspend inline fun MongoCollection.findOneAndReplace( filter: BsonDocument, - update: BsonDocument, + document: BsonDocument, projection: BsonDocument? = null, sort: BsonDocument? = null, upsert: Boolean = false, returnNewDoc: Boolean = false, ): T? { isType>(this) - return decodeFromBsonValue(findOneAndReplace(filter, update, projection, sort, upsert, returnNewDoc)) + return decodeFromBsonValue(findOneAndReplace(filter, document, projection, sort, upsert, returnNewDoc)) } @Suppress("LongParameterList") diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt index dcb6f9907e..b5ae865417 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt @@ -89,7 +89,7 @@ class MongoCollectionFromDatabaseTests : MongoCollectionTests() { @Test override fun findOne_unknownCollection() = runBlocking { // Unknown collections will create the collection - val unknownCollection = collection(Unknown::class) + val unknownCollection = collection(NonSchemaType::class) assertNull(unknownCollection.findOne()) } } @@ -108,7 +108,7 @@ class MongoCollectionFromClientTests : MongoCollectionTests() { @Test override fun findOne_unknownCollection() = runBlocking { - val unknownCollection = collection(Unknown::class) + val unknownCollection = collection(NonSchemaType::class) assertFailsWithMessage("no matching collection found that maps to a table with title \"Unknown\"") { RealmLog.level = LogLevel.ALL unknownCollection.findOne() @@ -282,10 +282,10 @@ abstract sealed class MongoCollectionTests { } @Test - fun findOne_extraFieldsAreDiscarded() { } + fun findOne_extraFieldsAreDiscarded() { TODO() } @Test - fun findOne_missingFieldsGetsDefaults() { } + fun findOne_missingFieldsGetsDefaults() { TODO() } @Test abstract fun findOne_unknownCollection() @@ -459,7 +459,12 @@ abstract sealed class MongoCollectionTests { fun insertMany() = runBlocking { assertEquals(0, collection.find().size) - collection.insertMany((1..10).map { CollectionDataType("object-${it % 5}") }) + collection.insertMany((1..10).map { CollectionDataType("object-${it % 5}") }).let { ids -> + assertEquals(10, ids.size) + ids.forEach { id -> + assertIs(id) + } + } assertEquals(10, collection.find().size) } @@ -492,7 +497,7 @@ abstract sealed class MongoCollectionTests { assertFailsWithMessage("Duplicate key error") { collection.insertMany(listOf(document.apply { name = "sadf" }, document)) } - // FIXME Wouldn't have expected one of the documents to be inserted :thinking: + // Above call will throw an error, but we have actually inserted one document assertEquals(1, collection.find().size) } @@ -526,10 +531,13 @@ abstract sealed class MongoCollectionTests { ).size ) assertEquals(2, collection.count(BsonDocument("""{ "name": "object-1" }"""))) + assertTrue { collection.deleteOne(BsonDocument("""{ "name": "object-1" }""")) } assertEquals(1, collection.count(BsonDocument("""{ "name": "object-1" }"""))) } + // Explicit types + @Test fun deleteOne_fails() = runBlocking { assertFailsWithMessage("unknown top level operator: \$who.") { @@ -539,88 +547,29 @@ abstract sealed class MongoCollectionTests { @Test fun deleteMany() = runBlocking { - // Argument wrapper DSL - RealmLog.level = LogLevel.ALL assertEquals(0, collection.deleteMany(BsonDocument())) - assertEquals( - 2, - collection.insertMany( - listOf( - CollectionDataType("object-1"), - CollectionDataType("object-1") - ) - ).size - ) + collection.insertMany((1..10).map { CollectionDataType("object-${it % 5}") }) + assertEquals(10, collection.find().size) + assertEquals(2, collection.deleteMany(BsonDocument("""{ "name": "object-1" }"""))) - assertEquals( - 3, - collection.insertMany( - listOf( - CollectionDataType("object-1"), - CollectionDataType("object-1"), - CollectionDataType("object-1") - ) - ).size - ) - assertEquals(3, collection.deleteMany(BsonDocument())) + assertEquals(8, collection.find().size) + + assertEquals(8, collection.deleteMany(BsonDocument())) + } + + @Test + fun deleteMany_fails() = runBlocking { + assertFailsWithMessage("unknown top level operator: \$who.") { + collection.deleteMany(BsonDocument("\$who", 1)) + } } -// @Test -// fun deleteMany_singleDocument() { -// with(getCollectionInternal()) { -// assertEquals(0, count().get()) -// -// val rawDoc = Document("hello", "world") -// val doc1 = Document(rawDoc) -// -// insertOne(doc1).get() -// assertEquals(1, count().get()) -// assertEquals(1, deleteMany(doc1).get()!!.deletedCount) -// assertEquals(0, count().get()) -// } -// } -// -// @Test -// fun deleteMany_multipleDocuments() { -// with(getCollectionInternal()) { -// assertEquals(0, count().get()) -// -// val rawDoc = Document("hello", "world") -// val doc1 = Document(rawDoc) -// val doc1b = Document(rawDoc) -// val doc2 = Document("foo", "bar") -// val doc3 = Document("42", "666") -// insertMany(listOf(doc1, doc1b, doc2, doc3)).get() -// assertEquals(2, deleteMany(rawDoc).get()!!.deletedCount) // two docs will be deleted -// assertEquals(2, count().get()) // two docs still present -// assertEquals(2, deleteMany(Document()).get()!!.deletedCount) // delete all -// assertEquals(0, count().get()) -// -// insertMany(listOf(doc1, doc1b, doc2, doc3)).get() -// assertEquals(4, deleteMany(Document()).get()!!.deletedCount) // delete all -// assertEquals(0, count().get()) -// } -// } -// -// @Test -// fun deleteMany_fails() { -// with(getCollectionInternal()) { -// assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { -// deleteMany(Document("\$who", 1)).get() -// }.also { e -> -// assertTrue(e.errorMessage!!.contains("operator", true)) -// } -// } -// } -// @Test open fun updateOne() = runBlocking { - // Argument wrapper DSL assertEquals(0, collection.count()) - RealmLog.level = LogLevel.ALL assertEquals( 4, collection.insertMany( @@ -634,25 +583,25 @@ abstract sealed class MongoCollectionTests { ) assertEquals(4, collection.count()) - // update no match - val updateWithoutMatch = collection.updateOne( + // Update no match + val updateWithoutMatch = collection.updateOne( BsonDocument("""{ "name": "NOMATCH"}"""), - BsonDocument("""{ "name": "UPDATED"}"""), + BsonDocument("\$set", BsonDocument("""{ "name": "UPDATED"}""")), ) assertEquals(false to null, updateWithoutMatch) - // update with match match + // Update with match match val updateWithMatch = collection.updateOne( BsonDocument("""{ "name": "object-1"}"""), - BsonDocument("""{ "name": "object-2"}"""), + BsonDocument("\$set", BsonDocument("""{ "name": "object-2"}""")), ) assertEquals(true to null, updateWithMatch) assertEquals(4, collection.count()) assertEquals(3, collection.count(filter = BsonDocument("""{"name": "object-1"}"""))) assertEquals(1, collection.count(filter = BsonDocument("""{"name": "object-2"}"""))) - // upsert no match - val upsertWithoutMatch = collection.updateOne( + // Upsert no match + val upsertWithoutMatch = collection.updateOne( BsonDocument("""{ "name": "object-3"}"""), BsonDocument(""" { "name": "object-2", "_id" : ${Random.nextInt()}}"""), upsert = true ) upsertWithoutMatch.let { (updated, upsertedId) -> @@ -662,8 +611,8 @@ abstract sealed class MongoCollectionTests { assertEquals(5, collection.count()) assertEquals(2, collection.count(filter = BsonDocument("""{"name": "object-2"}"""))) - // upsert with match - val upsertWithMatch = collection.updateOne( + // Upsert with match + val upsertWithMatch = collection.updateOne( BsonDocument("""{ "name": "object-2"}"""), BsonDocument(""" { "name": "object-3"}"""), upsert = true ) assertEquals(true to null, upsertWithMatch) @@ -671,64 +620,24 @@ abstract sealed class MongoCollectionTests { assertEquals(1, collection.count(filter = BsonDocument("""{"name": "object-2"}"""))) } - // @Test -// fun updateOne_emptyCollection() { -// with(getCollectionInternal()) { -// val doc1 = Document("hello", "world") -// -// // Update on an empty collection -// updateOne(Document(), doc1) -// .get()!! -// .let { -// assertEquals(0, it.matchedCount) -// assertEquals(0, it.modifiedCount) -// assertNull(it.upsertedId) -// } -// -// // Update on an empty collection adding some values -// val doc2 = Document("\$set", Document("woof", "meow")) -// updateOne(Document(), doc2) -// .get()!! -// .let { -// assertEquals(0, it.matchedCount) -// assertEquals(0, it.modifiedCount) -// assertNull(it.upsertedId) -// assertEquals(0, count().get()) -// } -// } -// } -// -// @Test -// fun updateOne_emptyCollectionWithUpsert() { -// with(getCollectionInternal()) { -// val doc1 = Document("hello", "world") -// -// // Update on empty collection with upsert -// val options = UpdateOptions().upsert(true) -// updateOne(Document(), doc1, options) -// .get()!! -// .let { -// assertEquals(0, it.matchedCount) -// assertEquals(0, it.modifiedCount) -// assertFalse(it.upsertedId!!.isNull) -// } -// assertEquals(1, count().get()) -// -// assertEquals(doc1, find(Document()).first().get()!!.withoutId()) -// } -// } -// -// @Test -// fun updateOne_fails() { -// with(getCollectionInternal()) { -// assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { -// updateOne(Document("\$who", 1), Document()).get() -// }.also { e -> -// assertTrue(e.errorMessage!!.contains("operator", true)) -// } -// } -// } -// + @Test + fun updateOne_explicitTypes() = runBlocking { + val upsertWithoutMatch = collection.updateOne( + BsonDocument("""{ "name": "object-3"}"""), BsonDocument(""" { "name": "object-2", "_id" : ${Random.nextInt()}}"""), upsert = true + ) + upsertWithoutMatch.let { (updated, upsertedId) -> + assertFalse(updated) + assertIs(upsertedId) + } + } + + @Test + fun updateOne_fails() = runBlocking { + assertFailsWithMessage("unknown top level operator: \$who.") { + collection.updateOne(BsonDocument("\$who", 1), BsonDocument()) + } + } + @Test fun updateMany() = runBlocking { assertEquals(0, collection.count()) @@ -745,7 +654,8 @@ abstract sealed class MongoCollectionTests { ).size ) assertEquals(4, collection.count()) - val updateWithoutMatch = collection.updateMany( + // Update with no match + val updateWithoutMatch = collection.updateMany( BsonDocument("""{"name": "NOMATCH"}"""), BsonDocument("""{"name": "UPDATED"}"""), ) @@ -753,7 +663,7 @@ abstract sealed class MongoCollectionTests { assertEquals(0, collection.count(filter = BsonDocument("""{"name": "UPDATED"}"""))) assertEquals(4, collection.count()) - // update with match match + // Update with match val updateWithMatch = collection.updateMany( BsonDocument("""{ "name": "x"}"""), BsonDocument("""{ "name": "UPDATED"}"""), @@ -762,8 +672,8 @@ abstract sealed class MongoCollectionTests { assertEquals(2, collection.count(filter = BsonDocument("""{"name": "UPDATED"}"""))) assertEquals(4, collection.count()) - // upsert no match - val upsertWithoutMatch = collection.updateMany( + // Upsert no match + val upsertWithoutMatch = collection.updateMany( BsonDocument("""{ "name": "NOMATCH"}"""), BsonDocument(""" { "name": "UPSERTED", "_id" : ${Random.nextInt()}}"""), upsert = true ) upsertWithoutMatch.let { @@ -772,8 +682,8 @@ abstract sealed class MongoCollectionTests { } assertEquals(5, collection.count()) assertEquals(1, collection.count(filter = BsonDocument("""{"name": "UPSERTED"}"""))) - // upsert with match - val upsertWithMatch = collection.updateMany( + // Upsert with match + val upsertWithMatch = collection.updateMany( BsonDocument("""{ "name": "y"}"""), BsonDocument(""" { "name": "z"}"""), upsert = true ) assertEquals(1L to null, upsertWithMatch) @@ -781,508 +691,367 @@ abstract sealed class MongoCollectionTests { assertEquals(0, collection.count(filter = BsonDocument("""{"name": "y"}"""))) } - // @Test -// fun updateMany_emptyCollection() { -// with(getCollectionInternal()) { -// val doc1 = Document("hello", "world") -// -// // Update on empty collection -// updateMany(Document(), doc1) -// .get()!! -// .let { -// assertEquals(0, it.matchedCount) -// assertEquals(0, it.modifiedCount) -// assertNull(it.upsertedId) -// } -// assertEquals(0, count().get()) -// } -// } -// -// @Test -// fun updateMany_emptyCollectionWithUpsert() { -// with(getCollectionInternal()) { -// val doc1 = Document("hello", "world") -// -// // Update on empty collection with upsert -// updateMany(Document(), doc1, UpdateOptions().upsert(true)) -// .get()!! -// .let { -// assertEquals(0, it.matchedCount) -// assertEquals(0, it.modifiedCount) -// assertNotNull(it.upsertedId) -// } -// assertEquals(1, count().get()) -// -// // Add new value using update -// val update = Document("woof", "meow") -// updateMany(Document(), Document("\$set", update)) -// .get()!! -// .let { -// assertEquals(1, it.matchedCount) -// assertEquals(1, it.modifiedCount) -// assertNull(it.upsertedId) -// } -// assertEquals(1, count().get()) -// val expected = Document(doc1).apply { this["woof"] = "meow" } -// assertEquals(expected, find().first().get()!!.withoutId()) -// -// // Insert empty document, add ["woof", "meow"] to it and check it worked -// insertOne(Document()).get() -// updateMany(Document(), Document("\$set", update)) -// .get()!! -// .let { -// assertEquals(2, it.matchedCount) -// assertEquals(2, it.modifiedCount) -// } -// assertEquals(2, count().get()) -// find().iterator() -// .get()!! -// .let { -// assertEquals(expected, it.next().withoutId()) -// assertEquals(update, it.next().withoutId()) -// assertFalse(it.hasNext()) -// } -// } -// } -// -// @Test -// fun updateMany_fails() { -// with(getCollectionInternal()) { -// assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { -// updateMany(Document("\$who", 1), Document()).get() -// }.also { e -> -// assertTrue(e.errorMessage!!.contains("operator", true)) -// } -// } -// } -// + @Test + fun updateMany_explicitTypes() = runBlocking { + collection.updateMany( + BsonDocument("""{ "name": "object-3"}"""), + BsonDocument(""" { "name": "object-2", "_id" : ${Random.nextInt()}}"""), + upsert = true + ).let { (updated, upsertedId) -> + assertEquals(0, updated) + assertIs(upsertedId) + } + } + + @Test + fun updateMany_fails() = runBlocking { + assertFailsWithMessage("Unknown modifier: \$who") { + collection.updateOne(BsonDocument(), BsonDocument("\$who", 1)) + } + } + @Test fun findOneAndUpdate() = runBlocking { - RealmLog.level = LogLevel.ALL - assertNull(collection.findOneAndUpdate(BsonDocument(), BsonDocument())) - collection.insertMany( - listOf( - CollectionDataType("object-1"), - CollectionDataType("object-1"), - CollectionDataType("object-2") + assertNull(collection.findOneAndUpdate(BsonDocument(), BsonDocument())) + + val names = (1..10).map { "object-${it % 5}" } + val ids: List = collection.insertMany(names.map { CollectionDataType(it) }) + + // Update with no match + assertNull( + collection.findOneAndUpdate( + BsonDocument("""{"name": "NOMATCH"}"""), + BsonDocument("""{"name": "UPDATED"}"""), ) ) - collection.findOneAndUpdate( - BsonDocument(), - BsonDocument("""{ "name": "object-1" }"""), - upsert = true + assertEquals(0, collection.count(filter = BsonDocument("""{"name": "UPDATED"}"""))) + + // Update with match - return old + collection.findOneAndUpdate( + BsonDocument("""{ "name": "object-1"}"""), + BsonDocument("""{ "name": "UPDATED"}"""), + )!!.let { + assertEquals("object-1", it.name) + } + assertEquals(1, collection.count(filter = BsonDocument("""{"name": "UPDATED"}"""))) + // Update with match - return new + collection.findOneAndUpdate( + BsonDocument("""{ "name": "object-1"}"""), + BsonDocument("""{ "name": "UPDATED"}"""), + returnNewDoc = true + )!!.let { + assertEquals("UPDATED", it.name) + } + assertEquals(2, collection.count(filter = BsonDocument("""{"name": "UPDATED"}"""))) + + // Upsert no match + assertNull( + collection.findOneAndUpdate( + filter = BsonDocument("""{ "name": "NOMATCH"}"""), + update = BsonDocument(""" { "name": "UPSERTED", "_id" : ${Random.nextInt()}}"""), + upsert = true, + ) ) + // Upsert no match - return new document + collection.findOneAndUpdate( + filter = BsonDocument("""{ "name": "NOMATCH"}"""), + update = BsonDocument(""" { "name": "UPSERTED", "_id" : ${Random.nextInt()}}"""), + upsert = true, + returnNewDoc = true, + )!!.let { + assertEquals("UPSERTED", it.name) + } + + // Upsert with match + collection.findOneAndUpdate( + filter = BsonDocument("""{ "name": "object-2"}"""), + update = BsonDocument(""" { "name": "UPSERTED" }"""), + upsert = true, + )!!.let { + assertEquals("object-2", it.name) + } + collection.findOneAndUpdate( + filter = BsonDocument("""{ "name": "object-2"}"""), + update = BsonDocument(""" { "name": "UPSERTED"}"""), + upsert = true, + returnNewDoc = true, + )!!.let { + assertEquals("UPSERTED", it.name) + } + + // Project without name + collection.findOneAndUpdate( + filter = BsonDocument("name", "object-3"), + update = BsonDocument(""" { "name": "UPDATED"}"""), + projection = BsonDocument("""{ "name" : 0}""") + )!!.run { + // FIXME Should be "Default" but serialization fails if field is non-nullable even though descriptor correctly has isOptional=true + // assertEquals("Default", this.name) + assertNull(this.name) // Currently null because of nullability, but should have default value "Default" + // _id is included by default and matched one of the previously inserted objects + assertTrue { this._id in ids } + } + // Project without _id, have to be explicitly excluded + collection.findOneAndUpdate( + filter = BsonDocument("name", "object-3"), + update = BsonDocument(""" { "name": "UPDATED"}"""), + projection = BsonDocument("""{ "_id" : 0}""") + )!!.run { + assertEquals("object-3", name) + // FIXME Should be constructor default arguments but serialization fails if field is non-nullable even though descriptor correctly has isOptional=true + // We don't know the id as the constructor default generated a new one + // assertFalse { this._id in ids} + assertNull(this._id) + } + + // Sort + val sortedNames: List = collection.find().map { it.name!! }.sorted() + collection.findOneAndUpdate( + filter = BsonDocument(), + update = BsonDocument(""" { "name": "FIRST"}"""), + sort = BsonDocument(mapOf("name" to BsonInt32(1))) + )!!.run { + assertEquals(sortedNames.first(), this.name) + } + collection.findOneAndUpdate( + filter = BsonDocument(), + update = BsonDocument(""" { "name": "LAST"}"""), + sort = BsonDocument(mapOf("name" to BsonInt32(-1))) + )!!.run { + assertEquals(sortedNames.last(), this.name) + } } -// @Test -// fun findOneAndUpdate_emptyCollection() { -// with(getCollectionInternal()) { -// // Test null return format -// assertNull(findOneAndUpdate(Document(), Document()).get()) -// } -// } -// -// @Test -// fun findOneAndUpdate_noUpdates() { -// with(getCollectionInternal()) { -// assertNull(findOneAndUpdate(Document(), Document()).get()) -// assertEquals(0, count().get()) -// } -// } -// -// @Test -// fun findOneAndUpdate_noUpsert() { -// with(getCollectionInternal()) { -// val sampleDoc = Document("hello", "world1") -// sampleDoc["num"] = 2 -// -// // Insert a sample Document -// insertOne(sampleDoc).get() -// assertEquals(1, count().get()) -// -// // Sample call to findOneAndUpdate() where we get the previous document back -// val sampleUpdate = Document("\$set", Document("hello", "hellothere")).apply { -// this["\$inc"] = Document("num", 1) -// } -// findOneAndUpdate(Document("hello", "world1"), sampleUpdate) -// .get()!! -// .withoutId() -// .let { -// assertEquals(sampleDoc.withoutId(), it) -// } -// assertEquals(1, count().get()) -// -// // Make sure the update took place -// val expectedDoc = Document("hello", "hellothere") -// expectedDoc["num"] = 3 -// assertEquals(expectedDoc.withoutId(), find().first().get()!!.withoutId()) -// assertEquals(1, count().get()) -// -// // Call findOneAndUpdate() again but get the new document -// sampleUpdate.remove("\$set") -// expectedDoc["num"] = 4 -// val options = FindOneAndModifyOptions() -// .returnNewDocument(true) -// findOneAndUpdate(Document("hello", "hellothere"), sampleUpdate, options) -// .get()!! -// .withoutId() -// .let { -// assertEquals(expectedDoc.withoutId(), it) -// } -// assertEquals(1, count().get()) -// -// // Test null behaviour again with a filter that should not match any documents -// assertNull(findOneAndUpdate(Document("hello", "zzzzz"), Document()).get()) -// assertEquals(1, count().get()) -// } -// } -// -// @Test -// fun findOneAndUpdate_upsert() { -// with(getCollectionInternal()) { -// val doc1 = Document("hello", "world1").apply { this["num"] = 1 } -// val doc2 = Document("hello", "world2").apply { this["num"] = 2 } -// val doc3 = Document("hello", "world3").apply { this["num"] = 3 } -// -// val filter = Document("hello", "hellothere") -// -// // Test the upsert option where it should not actually be invoked -// var options = FindOneAndModifyOptions() -// .returnNewDocument(true) -// .upsert(true) -// val update1 = Document("\$set", doc1) -// assertEquals(doc1, -// findOneAndUpdate(filter, update1, options) -// .get()!! -// .withoutId()) -// assertEquals(1, count().get()) -// assertEquals(doc1.withoutId(), -// find().first() -// .get()!! -// .withoutId()) -// -// // Test the upsert option where the server should perform upsert and return new document -// val update2 = Document("\$set", doc2) -// assertEquals(doc2, -// findOneAndUpdate(filter, update2, options) -// .get()!! -// .withoutId()) -// assertEquals(2, count().get()) -// -// // Test the upsert option where the server should perform upsert and return old document -// // The old document should be empty -// options = FindOneAndModifyOptions() -// .upsert(true) -// val update = Document("\$set", doc3) -// assertNull(findOneAndUpdate(filter, update, options).get()) -// assertEquals(3, count().get()) -// } -// } -// -// @Test -// fun findOneAndUpdate_withProjectionAndSort() { -// with(getCollectionInternal()) { -// insertMany(listOf( -// Document(mapOf(Pair("team", "Fearful Mallards"), Pair("score", 25000))), -// Document(mapOf(Pair("team", "Tactful Mooses"), Pair("score", 23500))), -// Document(mapOf(Pair("team", "Aquatic Ponies"), Pair("score", 19250))), -// Document(mapOf(Pair("team", "Cuddly Zebras"), Pair("score", 15235))), -// Document(mapOf(Pair("team", "Garrulous Bears"), Pair("score", 18000))) -// )).get() -// -// assertEquals(5, count().get()) -// assertNotNull(findOne(Document("team", "Cuddly Zebras"))) -// -// // Project: team, hide _id; Sort: score ascending -// val project = Document(mapOf(Pair("_id", 0), Pair("team", 1), Pair("score", 1))) -// val sort = Document("score", 1) -// -// // This results in the update of Cuddly Zebras -// val updatedDocument = findOneAndUpdate( -// Document("score", Document("\$lt", 22250)), -// Document("\$inc", Document("score", 1)), -// FindOneAndModifyOptions() -// .projection(project) -// .sort(sort) -// ).get() -// -// assertEquals(5, count().get()) -// assertEquals( -// Document(mapOf(Pair("team", "Cuddly Zebras"), Pair("score", 15235))), -// updatedDocument -// ) -// assertEquals( -// Document(mapOf(Pair("team", "Cuddly Zebras"), Pair("score", 15235 + 1))), -// findOne(Document("team", "Cuddly Zebras")).get().withoutId() -// ) -// } -// } -// -// @Test -// fun findOneAndUpdate_fails() { -// with(getCollectionInternal()) { -// assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { -// findOneAndUpdate(Document(), Document("\$who", 1)).get() -// }.also { e -> -// assertTrue(e.errorMessage!!.contains("modifier", true)) -// } -// -// assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { -// findOneAndUpdate(Document(), Document("\$who", 1), FindOneAndModifyOptions().upsert(true)).get() -// }.also { e -> -// assertTrue(e.errorMessage!!.contains("modifier", true)) -// } -// } -// } - - // FIXME Invalid fields?~? + + @Test + fun findOneAndUpdate_explicitTypes() = runBlocking { + collection.insertOne(CollectionDataType("object-1")) + + collection.findOneAndUpdate( + BsonDocument("""{ "name": "object-1"}"""), + BsonDocument("""{ "name": "UPDATED"}"""), + )!!.let { + assertEquals("object-1", it.asDocument()["name"]!!.asString().value) + } + } + + @Test + fun findOneAndUpdate_fails() = runBlocking { + assertFailsWithMessage("Unknown modifier: \$who") { + collection.findOneAndUpdate(BsonDocument(), BsonDocument("\$who", 1)) + } + } + @Test fun findOneAndReplace() = runBlocking { - RealmLog.level = LogLevel.ALL - assertNull(collection.findOneAndReplace(BsonDocument(), BsonDocument())) - collection.insertMany( - listOf( - CollectionDataType("object-1"), - CollectionDataType("object-1"), - CollectionDataType("object-2") + assertNull(collection.findOneAndReplace(BsonDocument(), BsonDocument())) + + val names = (1..10).map { "object-${it % 5}" } + val ids: List = collection.insertMany(names.map { CollectionDataType(it) }) + + // Replace with no match + assertNull( + collection.findOneAndReplace( + BsonDocument("""{"name": "NOMATCH"}"""), + BsonDocument(""" { "name": "REPLACED", "_id" : ${Random.nextInt()}}"""), ) ) - val x = collection.findOneAndReplace( - BsonDocument(), - BsonDocument("""{ "name": "object-1" }"""), - upsert = true + assertEquals(0, collection.count(filter = BsonDocument("""{"name": "REPLACED"}"""))) + + // Replace with match - return old + collection.findOneAndReplace( + BsonDocument("""{ "name": "object-1"}"""), + BsonDocument(""" { "name": "REPLACED"}"""), + )!!.let { + assertEquals("object-1", it.name) + } + assertEquals(1, collection.count(filter = BsonDocument("""{"name": "REPLACED"}"""))) + + // Replace with match - return new + collection.findOneAndReplace( + BsonDocument("""{ "name": "object-1"}"""), + BsonDocument("""{ "name": "REPLACED"}"""), + returnNewDoc = true + )!!.let { + assertEquals("REPLACED", it.name) + } + assertEquals(2, collection.count(filter = BsonDocument("""{"name": "REPLACED"}"""))) + + // Upsert no match + assertNull( + collection.findOneAndReplace( + filter = BsonDocument("""{ "name": "NOMATCH"}"""), + document = BsonDocument(""" { "name": "UPSERTED", "_id" : ${Random.nextInt()}}"""), + upsert = true, + ) ) + // Upsert no match - return new document + collection.findOneAndReplace( + filter = BsonDocument("""{ "name": "NOMATCH"}"""), + document = BsonDocument(""" { "name": "UPSERTED", "_id" : ${Random.nextInt()}}"""), + upsert = true, + returnNewDoc = true, + )!!.let { + assertEquals("UPSERTED", it.name) + } + + // Upsert with match + collection.findOneAndReplace( + filter = BsonDocument("""{ "name": "object-2"}"""), + document = BsonDocument(""" { "name": "UPSERTED" }"""), + upsert = true, + )!!.let { + assertEquals("object-2", it.name) + } + collection.findOneAndReplace( + filter = BsonDocument("""{ "name": "object-2"}"""), + document = BsonDocument(""" { "name": "UPSERTED"}"""), + upsert = true, + returnNewDoc = true, + )!!.let { + assertEquals("UPSERTED", it.name) + } + + // Project without name + collection.findOneAndReplace( + filter = BsonDocument("name", "object-3"), + document = BsonDocument(""" { "name": "REPLACED"}"""), + projection = BsonDocument("""{ "name" : 0}""") + )!!.run { + // FIXME Should be "Default" but serialization fails if field is non-nullable even though descriptor correctly has isOptional=true + // assertEquals("Default", this.name) + assertNull(this.name) // Currently null because of nullability, but should have default value "Default" + // _id is included by default and matched one of the previously inserted objects + assertTrue { this._id in ids } + } + // Project without _id, have to be explicitly excluded + collection.findOneAndReplace( + filter = BsonDocument("name", "object-3"), + document = BsonDocument(""" { "name": "REPLACED"}"""), + projection = BsonDocument("""{ "_id" : 0}""") + )!!.run { + assertEquals("object-3", name) + // FIXME Should be constructor default arguments but serialization fails if field is non-nullable even though descriptor correctly has isOptional=true + // We don't know the id as the constructor default generated a new one + // assertFalse { this._id in ids} + assertNull(this._id) + } + + // Sort + val sortedNames: List = collection.find().map { it.name!! }.sorted() + collection.findOneAndReplace( + filter = BsonDocument(), + document = BsonDocument(""" { "name": "FIRST"}"""), + sort = BsonDocument(mapOf("name" to BsonInt32(1))) + )!!.run { + assertEquals(sortedNames.first(), this.name) + } + collection.findOneAndReplace( + filter = BsonDocument(), + document = BsonDocument(""" { "name": "LAST"}"""), + sort = BsonDocument(mapOf("name" to BsonInt32(-1))) + )!!.run { + assertEquals(sortedNames.last(), this.name) + } + } + + @Test + fun findOneAndReplace_explicitTypes() = runBlocking { + collection.insertOne(CollectionDataType("object-1")) + + collection.findOneAndReplace( + BsonDocument("""{ "name": "object-1"}"""), + BsonDocument("""{ "name": "REPLACED"}"""), + )!!.let { + assertEquals("object-1", it.asDocument()["name"]!!.asString().value) + } + } + + @Test + fun findOneAndReplace_fails() = runBlocking { + assertFailsWithMessage("the replace operation document must not contain atomic operators") { + collection.findOneAndReplace(BsonDocument(), BsonDocument("\$who", 1)) + } } -// -// @Test -// fun findOneAndReplace_noUpdates() { -// with(getCollectionInternal()) { -// // Test null behaviour again with a filter that should not match any documents -// assertNull(findOneAndReplace(Document("hello", "zzzzz"), Document()).get()) -// assertEquals(0, count().get()) -// assertNull(findOneAndReplace(Document(), Document()).get()) -// assertEquals(0, count().get()) -// } -// } -// -// @Test -// fun findOneAndReplace_noUpsert() { -// with(getCollectionInternal()) { -// val sampleDoc = Document("hello", "world1").apply { this["num"] = 2 } -// -// // Insert a sample Document -// insertOne(sampleDoc).get() -// assertEquals(1, count().get()) -// -// // Sample call to findOneAndReplace() where we get the previous document back -// var sampleUpdate = Document("hello", "world2").apply { this["num"] = 2 } -// assertEquals(sampleDoc.withoutId(), -// findOneAndReplace(Document("hello", "world1"), sampleUpdate).get()!!.withoutId()) -// assertEquals(1, count().get()) -// -// // Make sure the update took place -// val expectedDoc = Document("hello", "world2").apply { this["num"] = 2 } -// assertEquals(expectedDoc.withoutId(), find().first().get()!!.withoutId()) -// assertEquals(1, count().get()) -// -// // Call findOneAndReplace() again but get the new document -// sampleUpdate = Document("hello", "world3").apply { this["num"] = 3 } -// val options = FindOneAndModifyOptions().returnNewDocument(true) -// assertEquals(sampleUpdate.withoutId(), -// findOneAndReplace(Document(), sampleUpdate, options).get()!!.withoutId()) -// assertEquals(1, count().get()) -// -// // Test null behaviour again with a filter that should not match any documents -// assertNull(findOneAndReplace(Document("hello", "zzzzz"), Document()).get()) -// assertEquals(1, count().get()) -// } -// } -// -// @Test -// fun findOneAndReplace_upsert() { -// with(getCollectionInternal()) { -// val doc4 = Document("hello", "world4").apply { this["num"] = 4 } -// val doc5 = Document("hello", "world5").apply { this["num"] = 5 } -// val doc6 = Document("hello", "world6").apply { this["num"] = 6 } -// -// // Test the upsert option where it should not actually be invoked -// val sampleUpdate = Document("hello", "world4").apply { this["num"] = 4 } -// var options = FindOneAndModifyOptions() -// .returnNewDocument(true) -// .upsert(true) -// assertEquals(doc4.withoutId(), -// findOneAndReplace(Document("hello", "world3"), doc4, options) -// .get()!! -// .withoutId()) -// assertEquals(1, count().get()) -// assertEquals(doc4.withoutId(), find().first().get()!!.withoutId()) -// -// // Test the upsert option where the server should perform upsert and return new document -// options = FindOneAndModifyOptions().returnNewDocument(true).upsert(true) -// assertEquals(doc5.withoutId(), findOneAndReplace(Document("hello", "hellothere"), doc5, options).get()!!.withoutId()) -// assertEquals(2, count().get()) -// -// // Test the upsert option where the server should perform upsert and return old document -// // The old document should be empty -// options = FindOneAndModifyOptions().upsert(true) -// assertNull(findOneAndReplace(Document("hello", "hellothere"), doc6, options).get()) -// assertEquals(3, count().get()) -// } -// } -// -// @Test -// fun findOneAndReplace_withProjectionAndSort() { -// with(getCollectionInternal()) { -// insertMany(listOf( -// Document(mapOf(Pair("team", "Fearful Mallards"), Pair("score", 25000))), -// Document(mapOf(Pair("team", "Tactful Mooses"), Pair("score", 23500))), -// Document(mapOf(Pair("team", "Aquatic Ponies"), Pair("score", 19250))), -// Document(mapOf(Pair("team", "Cuddly Zebras"), Pair("score", 15235))), -// Document(mapOf(Pair("team", "Garrulous Bears"), Pair("score", 18000))) -// )).get() -// -// assertEquals(5, count().get()) -// assertNotNull(findOne(Document("team", "Cuddly Zebras"))) -// -// // Project: team, hide _id; Sort: score ascending -// val project = Document(mapOf(Pair("_id", 0), Pair("team", 1))) -// val sort = Document("score", 1) -// -// // This results in the replacement of Cuddly Zebras -// val replacedDocument = findOneAndReplace( -// Document("score", Document("\$lt", 22250)), -// Document(mapOf(Pair("team", "Therapeutic Hamsters"), Pair("score", 22250))), -// FindOneAndModifyOptions() -// .projection(project) -// .sort(sort) -// ).get() -// -// assertEquals(5, count().get()) -// assertEquals(Document("team", "Cuddly Zebras"), replacedDocument) -// assertNull(findOne(Document("team", "Cuddly Zebras")).get()) -// assertNotNull(findOne(Document("team", "Therapeutic Hamsters")).get()) -// -// // Check returnNewDocument -// val newDocument = findOneAndReplace( -// Document("score", 22250), -// Document(mapOf(Pair("team", "New Therapeutic Hamsters"), Pair("score", 30000))), -// FindOneAndModifyOptions().returnNewDocument(true) -// ).get() -// -// assertEquals(Document(mapOf(Pair("team", "New Therapeutic Hamsters"), Pair("score", 30000))), newDocument.withoutId()) -// } -// } -// -// @Test -// fun findOneAndReplace_fails() { -// with(getCollectionInternal()) { -// assertFailsWithErrorCode(ErrorCode.INVALID_PARAMETER) { -// findOneAndReplace(Document(), Document("\$who", 1)).get() -// } -// -// assertFailsWithErrorCode(ErrorCode.INVALID_PARAMETER) { -// findOneAndReplace(Document(), Document("\$who", 1), FindOneAndModifyOptions().upsert(true)).get() -// } -// } -// } @Test fun findOneAndDelete() = runBlocking { + assertNull(collection.findOneAndDelete(BsonDocument(), BsonDocument())) + + val names = (1..10).map { "object-${it % 5}" } + val ids: List = collection.insertMany(names.map { CollectionDataType(it) }) + RealmLog.level = LogLevel.ALL - assertNull(collection.findOneAndDelete(BsonDocument(), BsonDocument())) - collection.insertMany( - listOf( - CollectionDataType("object-1"), - CollectionDataType("object-1"), - CollectionDataType("object-2") - ) - ) - val x: CollectionDataType? = collection.findOneAndDelete( - BsonDocument(), - BsonDocument("""{ "name": "object-1" }"""), - ) + // Delete with no match + assertNull(collection.findOneAndDelete(BsonDocument("""{"name": "NOMATCH"}"""))) + assertEquals(10, collection.count(filter = BsonDocument())) + + // Delete with match + collection.findOneAndDelete( + BsonDocument("""{ "name": "object-1"}"""), + )!!.let { + assertEquals("object-1", it.name) + } + assertEquals(9, collection.count(filter = BsonDocument())) + + // Project without name + collection.findOneAndDelete( + filter = BsonDocument("name", "object-3"), + projection = BsonDocument("""{ "name" : 0}""") + )!!.run { + // FIXME Should be "Default" but serialization fails if field is non-nullable even though descriptor correctly has isOptional=true + // assertEquals("Default", this.name) + assertNull(this.name) // Currently null because of nullability, but should have default value "Default" + // _id is included by default and matched one of the previously inserted objects + assertTrue { this._id in ids } + } + // Project without _id, have to be explicitly excluded + collection.findOneAndDelete( + filter = BsonDocument("name", "object-3"), + projection = BsonDocument("""{ "_id" : 0}""") + )!!.run { + assertEquals("object-3", name) + // FIXME Should be constructor default arguments but serialization fails if field is non-nullable even though descriptor correctly has isOptional=true + // We don't know the id as the constructor default generated a new one + // assertFalse { this._id in ids} + assertNull(this._id) + } + + // Sort + val sortedNames: List = collection.find().map { it.name!! }.sorted() + collection.findOneAndDelete( + filter = BsonDocument(), + sort = BsonDocument(mapOf("name" to BsonInt32(1))) + )!!.run { + assertEquals(sortedNames.first(), this.name) + } + collection.findOneAndDelete( + filter = BsonDocument(), + sort = BsonDocument(mapOf("name" to BsonInt32(-1))) + )!!.run { + assertEquals(sortedNames.last(), this.name) + } + } + + @Test + fun findOneAndDelete_explicitTypes() = runBlocking { + collection.insertOne(CollectionDataType("object-1")) + + collection.findOneAndDelete( + BsonDocument("""{ "name": "object-1"}"""), + )!!.let { + assertEquals("object-1", it.asDocument()["name"]!!.asString().value) + } + } + + @Test + fun findOneAndDelete_fails() = runBlocking { + assertFailsWithMessage("unknown top level operator: \$who.") { + collection.findOneAndDelete(BsonDocument("\$who", 1)) + } } - // -// @Test -// fun findOneAndDelete() { -// with(getCollectionInternal()) { -// val sampleDoc = Document("hello", "world1").apply { this["num"] = 1 } -// -// // Collection should start out empty -// // This also tests the null return format -// assertNull(findOneAndDelete(Document()).get()) -// -// // Insert a sample Document -// insertOne(sampleDoc).get() -// assertEquals(1, count().get()) -// -// // Sample call to findOneAndDelete() where we delete the only doc in the collection -// assertEquals(sampleDoc.withoutId(), -// findOneAndDelete(Document()).get()!!.withoutId()) -// -// // There should be no documents in the collection now -// assertEquals(0, count().get()) -// -// // Insert a sample Document -// insertOne(sampleDoc).get() -// assertEquals(1, count().get()) -// -// // Call findOneAndDelete() again but this time with a filter -// assertEquals(sampleDoc.withoutId(), -// findOneAndDelete(Document("hello", "world1")).get()!!.withoutId()) -// -// // There should be no documents in the collection now -// assertEquals(0, count().get()) -// -// // Insert a sample Document -// insertOne(sampleDoc).get() -// assertEquals(1, count().get()) -// -// // Test null behaviour again with a filter that should not match any documents -// assertNull(findOneAndDelete(Document("hello", "zzzzz")).get()) -// assertEquals(1, count().get()) -// -// val doc2 = Document("hello", "world2").apply { this["num"] = 2 } -// val doc3 = Document("hello", "world3").apply { this["num"] = 3 } -// -// // Insert new documents -// insertMany(listOf(doc2, doc3)).get() -// assertEquals(3, count().get()) -// } -// } -// -// @Test -// fun findOneAndDelete_withProjectionAndSort() { -// with(getCollectionInternal()) { -// insertMany(listOf( -// Document(mapOf(Pair("team", "Fearful Mallards"), Pair("score", 25000))), -// Document(mapOf(Pair("team", "Tactful Mooses"), Pair("score", 23500))), -// Document(mapOf(Pair("team", "Aquatic Ponies"), Pair("score", 19250))), -// Document(mapOf(Pair("team", "Cuddly Zebras"), Pair("score", 15235))), -// Document(mapOf(Pair("team", "Garrulous Bears"), Pair("score", 18000))) -// )).get() -// -// assertEquals(5, count().get()) -// assertNotNull(findOne(Document("team", "Cuddly Zebras"))) -// -// // Project: team, hide _id; Sort: score ascending -// val project = Document(mapOf(Pair("_id", 0), Pair("team", 1))) -// val sort = Document("score", 1) -// -// // This results in the deletion of Cuddly Zebras -// val deletedDocument = findOneAndDelete( -// Document("score", Document("\$lt", 22250)), -// FindOneAndModifyOptions() -// .projection(project) -// .sort(sort) -// ).get() -// -// assertEquals(4, count().get()) -// assertEquals(Document("team", "Cuddly Zebras"), deletedDocument.withoutId()) -// assertNull(findOne(Document("team", "Cuddly Zebras")).get()) -// } -// } } +// Helper method to be able to differentiate collection creation across test classes inline fun MongoCollectionTests.collection(clazz: KClass): MongoCollection { return when (this) { is MongoCollectionFromDatabaseTests -> database.collection(T::class.simpleName!!) @@ -1290,6 +1059,7 @@ inline fun MongoCollectionTests.collection(claz } } +// Helper method to easy BsonDocument construction from Kotlin types and avoid BsonValue wrappers. @Suppress("name") @OptIn(ExperimentalKBsonSerializerApi::class) inline operator fun BsonDocument.Companion.invoke( @@ -1299,35 +1069,29 @@ inline operator fun BsonDocument.Companion.invoke( return BsonDocument(key to EJson.Default.encodeToBsonValue(value)) } +// Class that is unknown to the server. Should never be inserted as the server would then +// automatically create the collection. @Serializable -class Unknown : RealmObject { +class NonSchemaType : RealmObject { var name = "Unknown" } -// Strictly defined in schema in TestAppInitializer.kt -@Serializable -class SyncDog(var name: String) : RealmObject { - constructor() : this("Default") -} - @Serializable class CollectionDataType(var name: String? = "Default", var _id: Int? = Random.nextInt()) : RealmObject { constructor() : this("Default") } +// Distinct data type with same fields as the above CollectionDataType used to showcase injection +// of custom serializers. @PersistedName("CollectionDataType") class CustomDataType(var name: String) : RealmObject { @Suppress("unused") constructor() : this("Default") } - -@Serializable -class NonSchemaType : RealmObject { - var name: String = "Default" -} - +// Custom Id type to showcase that we can use custom serializers for primary key return values. class CustomIdType(val id: ObjectId) +// Custom serializers to showcase that we can inject serializers throughout the MongoClient APIs. class CustomDataTypeSerializer : KSerializer { val serializer = BsonValue.serializer() @@ -1342,7 +1106,6 @@ class CustomDataTypeSerializer : KSerializer { encoder.encodeSerializableValue(serializer, BsonDocument("name", value.name)) } } - class CustomIdSerializer : KSerializer { val serializer = BsonValue.serializer() override val descriptor: SerialDescriptor = serializer.descriptor @@ -1359,7 +1122,6 @@ class CustomIdSerializer : KSerializer { @OptIn(ExperimentalKBsonSerializerApi::class) val customEjsonSerializer = EJson( -// ignoreUnknownKeys = false, serializersModule = SerializersModule { contextual(CustomDataType::class, CustomDataTypeSerializer()) contextual(CustomIdType::class, CustomIdSerializer()) From 4818ad20f1a70240bfc762ebaebe85923d85a19e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 19 Dec 2023 14:48:06 +0100 Subject: [PATCH 13/36] Update test after fixing bson deserializer --- buildSrc/src/main/kotlin/Config.kt | 2 +- .../common/mongo/MongoCollectionTests.kt | 75 ++++++++++--------- 2 files changed, 40 insertions(+), 37 deletions(-) diff --git a/buildSrc/src/main/kotlin/Config.kt b/buildSrc/src/main/kotlin/Config.kt index b71f9e4fb8..8835fe8a7a 100644 --- a/buildSrc/src/main/kotlin/Config.kt +++ b/buildSrc/src/main/kotlin/Config.kt @@ -124,7 +124,7 @@ object Versions { const val jmh = "1.34" // https://github.com/openjdk/jmh const val jmhPlugin = "0.6.6" // https://github.com/melix/jmh-gradle-plugin const val junit = "4.13.2" // https://mvnrepository.com/artifact/junit/junit - const val kbson = "0.3.0" // https://github.com/mongodb/kbson + const val kbson = "0.4.0-SNAPSHOT" // https://github.com/mongodb/kbson // When updating the Kotlin version, also remember to update /examples/min-android-sample/build.gradle.kts const val kotlin = "1.9.0" // https://github.com/JetBrains/kotlin and https://kotlinlang.org/docs/releases.html#release-details const val kotlinJvmTarget = "1.8" // Which JVM bytecode version is kotlin compiled to. diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt index b5ae865417..a980355567 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt @@ -70,7 +70,6 @@ import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertIs import kotlin.test.assertNotEquals -import kotlin.test.assertNotNull import kotlin.test.assertNull import kotlin.test.assertTrue @@ -233,14 +232,12 @@ abstract sealed class MongoCollectionTests { } // Project without name - collection.findOne( + collection.findOne( filter = BsonDocument("name", "object-6"), projection = BsonDocument("""{ "name" : 0}""") ).run { assertIs(this) - // FIXME Should be "Default" but serialization fails if field is non-nullable even though descriptor correctly has isOptional=true - // assertEquals("Default", this.name) - assertNull(this.name) // Currently null because of nullability, but should have default value "Default" + assertEquals("Default", this.name) // _id is included by default assertEquals(collectionDataType._id, this._id) } @@ -251,8 +248,6 @@ abstract sealed class MongoCollectionTests { ).run { assertIs(this) assertEquals(collectionDataType.name, name) - // FIXME Should be "Default" but serialization fails if field is non-nullable even though descriptor correctly has isOptional=true - // _id is included by default assertNotEquals(collectionDataType._id, this._id) } @@ -282,10 +277,35 @@ abstract sealed class MongoCollectionTests { } @Test - fun findOne_extraFieldsAreDiscarded() { TODO() } + fun findOne_extraFieldsAreDiscarded() = runBlocking { + collection.insertOne( + BsonDocument( + mapOf( + "_id" to BsonInt32(Random.nextInt()), + "name" to BsonString("object-1"), + "extra" to BsonString("extra"), + ) + ) + ) + + // Show that remote method returns extra properties + collection.findOne()!!.let { + assertEquals("extra", it["extra"]!!.asString().value) + } + // But these properties are silently discarded by the serialization framework + assertIs(collection.findOne()) + } @Test - fun findOne_missingFieldsGetsDefaults() { TODO() } + fun findOne_missingFieldsGetsDefaults() = runBlocking { + collection.insertOne(CollectionDataType("object-1")) + collection.findOne( + projection = BsonDocument("""{ "name" : 0}""") + ).run { + assertIs(this) + assertEquals("Default", this.name) + } + } @Test abstract fun findOne_unknownCollection() @@ -324,9 +344,7 @@ abstract sealed class MongoCollectionTests { ).let { assertEquals(2, it.size) it.forEach { - // FIXME Should be "Default" but serialization fails if field is non-nullable even though descriptor correctly has isOptional=true - // assertEquals("Default", it.name) - assertNull(it.name) + assertEquals("Default", it.name) // _id is included by default assertTrue(it._id in ids) } @@ -337,12 +355,9 @@ abstract sealed class MongoCollectionTests { ).let { assertEquals(2, it.size) it.forEach { - assertNotNull(it.name) - assertNotEquals("Default", it.name) + assertEquals("object-1", it.name) // Objects have new ids - // FIXME Should be assigned new Objects ids, but serialization fails if field is non-nullable even though descriptor correctly has isOptional=true - // assertFalse(it._id in ids) - assertNull(it._id) + assertFalse(it._id in ids) } } @@ -785,9 +800,7 @@ abstract sealed class MongoCollectionTests { update = BsonDocument(""" { "name": "UPDATED"}"""), projection = BsonDocument("""{ "name" : 0}""") )!!.run { - // FIXME Should be "Default" but serialization fails if field is non-nullable even though descriptor correctly has isOptional=true - // assertEquals("Default", this.name) - assertNull(this.name) // Currently null because of nullability, but should have default value "Default" + assertEquals("Default", this.name) // _id is included by default and matched one of the previously inserted objects assertTrue { this._id in ids } } @@ -798,10 +811,8 @@ abstract sealed class MongoCollectionTests { projection = BsonDocument("""{ "_id" : 0}""") )!!.run { assertEquals("object-3", name) - // FIXME Should be constructor default arguments but serialization fails if field is non-nullable even though descriptor correctly has isOptional=true // We don't know the id as the constructor default generated a new one - // assertFalse { this._id in ids} - assertNull(this._id) + assertFalse { this._id in ids } } // Sort @@ -917,9 +928,7 @@ abstract sealed class MongoCollectionTests { document = BsonDocument(""" { "name": "REPLACED"}"""), projection = BsonDocument("""{ "name" : 0}""") )!!.run { - // FIXME Should be "Default" but serialization fails if field is non-nullable even though descriptor correctly has isOptional=true - // assertEquals("Default", this.name) - assertNull(this.name) // Currently null because of nullability, but should have default value "Default" + assertEquals("Default", this.name) // _id is included by default and matched one of the previously inserted objects assertTrue { this._id in ids } } @@ -930,10 +939,8 @@ abstract sealed class MongoCollectionTests { projection = BsonDocument("""{ "_id" : 0}""") )!!.run { assertEquals("object-3", name) - // FIXME Should be constructor default arguments but serialization fails if field is non-nullable even though descriptor correctly has isOptional=true // We don't know the id as the constructor default generated a new one - // assertFalse { this._id in ids} - assertNull(this._id) + assertFalse { this._id in ids } } // Sort @@ -998,9 +1005,7 @@ abstract sealed class MongoCollectionTests { filter = BsonDocument("name", "object-3"), projection = BsonDocument("""{ "name" : 0}""") )!!.run { - // FIXME Should be "Default" but serialization fails if field is non-nullable even though descriptor correctly has isOptional=true - // assertEquals("Default", this.name) - assertNull(this.name) // Currently null because of nullability, but should have default value "Default" + assertEquals("Default", this.name) // _id is included by default and matched one of the previously inserted objects assertTrue { this._id in ids } } @@ -1010,10 +1015,8 @@ abstract sealed class MongoCollectionTests { projection = BsonDocument("""{ "_id" : 0}""") )!!.run { assertEquals("object-3", name) - // FIXME Should be constructor default arguments but serialization fails if field is non-nullable even though descriptor correctly has isOptional=true // We don't know the id as the constructor default generated a new one - // assertFalse { this._id in ids} - assertNull(this._id) + assertFalse { this._id in ids } } // Sort @@ -1077,7 +1080,7 @@ class NonSchemaType : RealmObject { } @Serializable -class CollectionDataType(var name: String? = "Default", var _id: Int? = Random.nextInt()) : RealmObject { +class CollectionDataType(var name: String = "Default", var _id: Int = Random.nextInt()) : RealmObject { constructor() : this("Default") } From caa9e6b9e8700002b580d6f4166d01640cd5a139 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 19 Dec 2023 14:52:27 +0100 Subject: [PATCH 14/36] Minor clean ups --- .../kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt | 1 - .../kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt | 6 ------ 2 files changed, 7 deletions(-) diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt index fd1c70e2e6..c961526739 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt @@ -30,7 +30,6 @@ public interface MongoClient { /** * The name of the data source that the [MongoClient] is connecting to. */ - // FIXME Data Source name?? public val serviceName: String /** diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt index 0201837ebc..a62fad3488 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt @@ -14,12 +14,6 @@ * limitations under the License. */ -// TODO - QUESTIONS -// - should we allow serialization of update, sort and projection arguments? -// - Experimental annotation as a safegaurd around all the serialization stuff? We dont support an API without Bson-serialization, so will depend on ExperimentalKBsonSerializerApi internally anyway, so can't avoid the KSerialization-dependency -// - #naming App Services seems to use "Data source", Data -// - What about missing fields in server response -> ServiceException? - package io.realm.kotlin.mongodb.mongo import io.realm.kotlin.internal.util.Validation.isType From 58ded6d442e0ffa6749709a183813e706d9378d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 19 Dec 2023 14:59:48 +0100 Subject: [PATCH 15/36] Updates according to review comments --- CHANGELOG.md | 2 +- .../io/realm/kotlin/mongodb/internal/FunctionsImpl.kt | 8 ++++---- .../kotlin/io/realm/kotlin/mongodb/mongo/MongoDatabase.kt | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58af441976..762f8b6055 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ * None. ### Enhancements -* Add Mongo Client API to access Atlas App Service collections. (Issue [#972](https://github.com/realm/realm-kotlin/issues/972)) +* Add Mongo Client API to access Atlas App Service collections. It can be accessed through `User.mongoClient`. (Issue [#972](https://github.com/realm/realm-kotlin/issues/972)) ### Fixed * None. diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/FunctionsImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/FunctionsImpl.kt index 98f3d21b49..2862672300 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/FunctionsImpl.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/FunctionsImpl.kt @@ -30,13 +30,13 @@ internal class FunctionsImpl( ) : Functions { @PublishedApi internal suspend fun callInternal( - name: String, + functionName: String, serializedEjsonArgs: String ): String = Channel>(1).use { channel -> RealmInterop.realm_app_call_function( app = app.nativePointer, user = user.nativePointer, - name = name, + name = functionName, serviceName = serviceName, serializedEjsonArgs = serializedEjsonArgs, callback = channelResultCallback(channel) { ejsonEncodedObject: String -> @@ -49,6 +49,6 @@ internal class FunctionsImpl( return channel.receive().getOrThrow() } - internal suspend fun callInternal(name: String, bsonValue: BsonValue): BsonValue = - Bson(callInternal(name, Bson.toJson(bsonValue))) + internal suspend fun callInternal(functionName: String, bsonValue: BsonValue): BsonValue = + Bson(callInternal(functionName, Bson.toJson(bsonValue))) } diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoDatabase.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoDatabase.kt index f31875013a..fdf3d57f89 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoDatabase.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoDatabase.kt @@ -54,7 +54,7 @@ public interface MongoDatabase { * to. * @param K the default type that primary keys will be serialized into. * @return a [MongoCollection] that will accept and return entities from the remote collection - * as [BsonValue] values. + * as [T] values. */ @OptIn(ExperimentalKBsonSerializerApi::class) public fun collection(collectionName: String, eJson: EJson? = null): MongoCollection From d5591da1553bc124b5b13132f7b7a3cb3f803807 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 19 Dec 2023 15:02:15 +0100 Subject: [PATCH 16/36] Updates according to review comments --- .../io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt | 4 ++-- .../kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt | 6 +++--- .../test/mongodb/common/mongo/MongoCollectionTests.kt | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt index ea3cb82fa7..8e866f5a20 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt @@ -39,7 +39,7 @@ internal class MongoDatabaseCollection(@PublishedApi internal val database "collection" to BsonString(name), ) @OptIn(ExperimentalKBsonSerializerApi::class) - override fun reshape(eJson: EJson?): MongoCollection { + override fun withDocumentClass(eJson: EJson?): MongoCollection { return MongoDatabaseCollection(this.database, this.name, eJson ?: this.eJson) } } @@ -51,7 +51,7 @@ internal class MongoClientCollection(@PublishedApi internal val clientImpl "schema_name" to BsonString(name), ) @OptIn(ExperimentalKBsonSerializerApi::class) - override fun reshape(eJson: EJson?): MongoCollection { + override fun withDocumentClass(eJson: EJson?): MongoCollection { return MongoClientCollection(clientImpl, name, eJson ?: this.eJson) } } diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt index a62fad3488..2703b84857 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt @@ -38,15 +38,15 @@ import kotlin.jvm.JvmName public interface MongoCollection { /** - * Name of the remote collection. Will be `null` for collections + * Name of the remote collection. */ - public val name: String? + public val name: String /** * Get an instance of the same collection with a different set of default types serialization. */ @OptIn(ExperimentalKBsonSerializerApi::class) - public fun reshape(eJson: EJson? = null): MongoCollection + public fun withDocumentClass(eJson: EJson? = null): MongoCollection } public suspend fun MongoCollection<*, *>.count(filter: BsonDocument? = null, limit: Long? = null): Long { diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt index a980355567..56e85272fc 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt @@ -159,7 +159,7 @@ abstract sealed class MongoCollectionTests { assertIs(collection.findOne()) // Reshaped - val bsonCollection: MongoCollection = collection.reshape() + val bsonCollection: MongoCollection = collection.withDocumentClass() assertIs(bsonCollection.insertOne(BsonDocument("name", "object-2"))) assertIs(bsonCollection.findOne()) assertEquals(2, bsonCollection.count()) @@ -168,14 +168,14 @@ abstract sealed class MongoCollectionTests { @Test fun reshape_withCustomSerialization() = runBlocking { val reshapedCollectionWithDefaultSerializer: MongoCollection = - collection.reshape() + collection.withDocumentClass() assertFailsWithMessage("Serializer for class 'CustomDataType' is not found.") { reshapedCollectionWithDefaultSerializer.insertOne(CustomDataType("object-2")) } val reshapedCollectionWithCustomSerializer: MongoCollection = - collection.reshape(customEjsonSerializer) + collection.withDocumentClass(customEjsonSerializer) assertIs(reshapedCollectionWithCustomSerializer.insertOne(CustomDataType("object-2"))) } From 9e65380e35a30d88c288d5258f539fdd3cdd003a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Wed, 20 Dec 2023 13:15:58 +0100 Subject: [PATCH 17/36] Clean up experimental kbson serialization annotation usage --- .../kotlin/io/realm/kotlin/mongodb/User.kt | 2 +- .../io/realm/kotlin/mongodb/ext/UserExt.kt | 43 ------------------- .../mongodb/internal/MongoClientImpl.kt | 2 +- .../mongodb/internal/MongoCollectionImpl.kt | 5 +-- .../realm/kotlin/mongodb/internal/UserImpl.kt | 2 +- .../realm/kotlin/mongodb/mongo/MongoClient.kt | 4 +- .../kotlin/mongodb/mongo/MongoCollection.kt | 8 ++-- .../kotlin/mongodb/mongo/MongoDatabase.kt | 2 +- .../kotlin/test/mongodb/common/UserTests.kt | 9 +++- .../common/mongo/MongoCollectionTests.kt | 26 +++++------ 10 files changed, 28 insertions(+), 75 deletions(-) diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/User.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/User.kt index 23f4f7d145..a799cdf4c1 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/User.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/User.kt @@ -212,7 +212,7 @@ public interface User { * primary keys with. Will default to the apps [EJson] instance configured with * [AppConfiguration.Builder.ejson]. */ - @OptIn(ExperimentalKBsonSerializerApi::class) + @ExperimentalKBsonSerializerApi public fun mongoClient(serviceName: String, eJson: EJson? = null): MongoClient /** diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/UserExt.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/UserExt.kt index ea6af48f7c..72384cff01 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/UserExt.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/UserExt.kt @@ -69,49 +69,6 @@ public inline fun User.profile(serializer: KSerializer = (this as } } -/** - * Returns the profile for this user as a [T]. - * - * **Note** This method supports full document serialization. The user profile will be deserialized with - * the built-in serializer for [T] and decoded with [AppConfiguration.ejson]. - * - * @param T the type to decoded the user profile. - * @return The profile for this user. - */ -// @ExperimentalRealmSerializerApi -// @OptIn(ExperimentalKBsonSerializerApi::class) -// public inline fun User.profile(): T = -// profile( -// (this as UserImpl).app -// .configuration -// .ejson -// .serializersModule -// .serializerOrRealmBuiltInSerializer() -// ) -// -/** - * Returns the custom user data associated with the user in the Realm App as [T]. - * - * The data is only refreshed when the user's access token is refreshed or when explicitly - * calling [User.refreshCustomData]. - * - * **Note** This method supports full document serialization. Custom data will be deserialized with - * the built-in serializer for [T] and decoded with [AppConfiguration.ejson]. - * - * @param T the type to decoded the user custom data. - * @return The custom user data associated with the user. - */ -// @ExperimentalRealmSerializerApi -// @OptIn(ExperimentalKBsonSerializerApi::class) -// public inline fun User.customData(): T? = -// customData( -// (this as UserImpl).app -// .configuration -// .ejson -// .serializersModule -// .serializerOrRealmBuiltInSerializer() -// ) - /** * Returns the custom user data associated with the user in the Realm App as [T]. * diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoClientImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoClientImpl.kt index bc3b4e3cd1..aecc890765 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoClientImpl.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoClientImpl.kt @@ -21,8 +21,8 @@ import io.realm.kotlin.mongodb.mongo.MongoDatabase import org.mongodb.kbson.ExperimentalKBsonSerializerApi import org.mongodb.kbson.serialization.EJson -@OptIn(ExperimentalKBsonSerializerApi::class) @PublishedApi +@OptIn(ExperimentalKBsonSerializerApi::class) internal class MongoClientImpl( internal val user: UserImpl, override val serviceName: String, diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt index 8e866f5a20..d90b2040b3 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt @@ -16,7 +16,6 @@ package io.realm.kotlin.mongodb.internal -import io.realm.kotlin.annotations.ExperimentalRealmSerializerApi import io.realm.kotlin.mongodb.exceptions.ServiceException import io.realm.kotlin.mongodb.mongo.MongoCollection import org.mongodb.kbson.BsonArray @@ -38,7 +37,6 @@ internal class MongoDatabaseCollection(@PublishedApi internal val database "database" to BsonString(database.name), "collection" to BsonString(name), ) - @OptIn(ExperimentalKBsonSerializerApi::class) override fun withDocumentClass(eJson: EJson?): MongoCollection { return MongoDatabaseCollection(this.database, this.name, eJson ?: this.eJson) } @@ -50,14 +48,13 @@ internal class MongoClientCollection(@PublishedApi internal val clientImpl override val defaults: Map = mapOf( "schema_name" to BsonString(name), ) - @OptIn(ExperimentalKBsonSerializerApi::class) override fun withDocumentClass(eJson: EJson?): MongoCollection { return MongoClientCollection(clientImpl, name, eJson ?: this.eJson) } } -@OptIn(ExperimentalKBsonSerializerApi::class, ExperimentalRealmSerializerApi::class) @PublishedApi +@OptIn(ExperimentalKBsonSerializerApi::class) internal abstract class MongoCollectionImpl constructor( val functions: FunctionsImpl, val eJson: EJson, diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/UserImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/UserImpl.kt index 35cf549e0d..5aad6ffe67 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/UserImpl.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/UserImpl.kt @@ -185,7 +185,7 @@ public class UserImpl( } } - @OptIn(ExperimentalKBsonSerializerApi::class) + @ExperimentalKBsonSerializerApi override fun mongoClient(serviceName: String, eJson: EJson?): MongoClient = MongoClientImpl(this, serviceName, eJson ?: app.configuration.ejson) override fun equals(other: Any?): Boolean { diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt index c961526739..975fb42b45 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt @@ -39,11 +39,11 @@ public interface MongoClient { * @param eJson the EJson serializer that the [MongoDatabase] should use to convert objects and * primary keys with. Will default to the client's [EJson] instance. */ - @OptIn(ExperimentalKBsonSerializerApi::class) + @ExperimentalKBsonSerializerApi public fun database(databaseName: String, eJson: EJson? = null): MongoDatabase } -@OptIn(ExperimentalKBsonSerializerApi::class) +@ExperimentalKBsonSerializerApi public inline fun MongoClient.collection(eJson: EJson? = null): MongoCollection { @Suppress("invisible_reference", "invisible_member") return MongoClientCollection(this as MongoClientImpl, io.realm.kotlin.internal.platform.realmObjectCompanionOrThrow(T::class).io_realm_kotlin_className, eJson ?: this.eJson) diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt index 2703b84857..a18637067c 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt @@ -45,7 +45,7 @@ public interface MongoCollection { /** * Get an instance of the same collection with a different set of default types serialization. */ - @OptIn(ExperimentalKBsonSerializerApi::class) + @ExperimentalKBsonSerializerApi public fun withDocumentClass(eJson: EJson? = null): MongoCollection } @@ -56,8 +56,8 @@ public suspend fun MongoCollection<*, *>.count(filter: BsonDocument? = null, lim public suspend inline fun < reified T, R : Any> MongoCollection.findOne(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null): T? { isType>(this) - val bsonValue: BsonValue? = findOne(filter, projection, sort) - return decodeFromBsonValue(bsonValue!!) + val bsonValue: BsonValue = findOne(filter, projection, sort) + return decodeFromBsonValue(bsonValue) } @JvmName("findOneTyped") @@ -117,8 +117,6 @@ public suspend fun MongoCollection<*, *>.deleteMany(filter: BsonDocument): Long return deleteMany(filter) } -// FIXME Could just return Boolean, since matchedCount=1,modifiedCount=1 even if multiple documents should be matching :thinking: -// FIXME Should we split into upsertOne, since response only contains 'upsertedId' if call has 'upsert:true` public suspend inline fun MongoCollection.updateOne( filter: BsonDocument, update: BsonDocument, diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoDatabase.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoDatabase.kt index fdf3d57f89..257872f2eb 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoDatabase.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoDatabase.kt @@ -56,6 +56,6 @@ public interface MongoDatabase { * @return a [MongoCollection] that will accept and return entities from the remote collection * as [T] values. */ - @OptIn(ExperimentalKBsonSerializerApi::class) + @ExperimentalKBsonSerializerApi public fun collection(collectionName: String, eJson: EJson? = null): MongoCollection } diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserTests.kt index 0dd338d119..58b46dff6b 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserTests.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -@file:OptIn(ExperimentalKBsonSerializerApi::class, ExperimentalRealmSerializerApi::class) +@file:OptIn(ExperimentalRealmSerializerApi::class) package io.realm.kotlin.test.mongodb.common @@ -29,6 +29,7 @@ import io.realm.kotlin.mongodb.exceptions.CredentialsCannotBeLinkedException import io.realm.kotlin.mongodb.exceptions.ServiceException import io.realm.kotlin.mongodb.ext.customData import io.realm.kotlin.mongodb.ext.customDataAsBsonDocument +import io.realm.kotlin.mongodb.mongo.MongoClient import io.realm.kotlin.mongodb.mongo.insertOne import io.realm.kotlin.mongodb.sync.SyncConfiguration import io.realm.kotlin.test.mongodb.TestApp @@ -736,16 +737,19 @@ class UserTests { } @Test + @OptIn(ExperimentalKBsonSerializerApi::class) fun mongoClient_defaultSerializer() = runBlocking { val (email, password) = randomEmail() to "123456" val user = runBlocking { createUserAndLogin(email, password) } - val client = user.mongoClient(TEST_SERVICE_NAME) + @OptIn(ExperimentalRealmSerializerApi::class) + val client: MongoClient = user.mongoClient(TEST_SERVICE_NAME) assertIs(client.database(app.clientAppId).collection("CollectionDataType").insertOne(CollectionDataType("object-1"))) } @Test + @OptIn(ExperimentalKBsonSerializerApi::class) fun mongoClient_customSerializer() = runBlocking { val (email, password) = randomEmail() to "123456" val user = runBlocking { @@ -765,6 +769,7 @@ class UserTests { } @Test + @OptIn(ExperimentalKBsonSerializerApi::class) fun mongoClient_unknownClient() = runBlocking { val (email, password) = randomEmail() to "123456" val user = runBlocking { diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt index 56e85272fc..20daf9614a 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt @@ -14,13 +14,9 @@ * limitations under the License. */ -@file:OptIn(ExperimentalKBsonSerializerApi::class) - package io.realm.kotlin.test.mongodb.common.mongo import io.realm.kotlin.internal.platform.runBlocking -import io.realm.kotlin.log.LogLevel -import io.realm.kotlin.log.RealmLog import io.realm.kotlin.mongodb.AppConfiguration import io.realm.kotlin.mongodb.exceptions.ServiceException import io.realm.kotlin.mongodb.mongo.MongoClient @@ -81,13 +77,15 @@ class MongoCollectionFromDatabaseTests : MongoCollectionTests() { override fun setUp() { super.setUp() val databaseName = app.configuration.appId + @OptIn(ExperimentalKBsonSerializerApi::class) database = client.database(databaseName) collection = collection(CollectionDataType::class) } @Test override fun findOne_unknownCollection() = runBlocking { - // Unknown collections will create the collection + // Unknown collections will create the collection if inserting document, so only use + // NonSchemaType for queries val unknownCollection = collection(NonSchemaType::class) assertNull(unknownCollection.findOne()) } @@ -102,14 +100,14 @@ class MongoCollectionFromClientTests : MongoCollectionTests() { @Test fun name_persistedName() { + @OptIn(ExperimentalKBsonSerializerApi::class) assertEquals("CollectionDataType", client.collection().name) } @Test override fun findOne_unknownCollection() = runBlocking { val unknownCollection = collection(NonSchemaType::class) - assertFailsWithMessage("no matching collection found that maps to a table with title \"Unknown\"") { - RealmLog.level = LogLevel.ALL + assertFailsWithMessage("no matching collection found that maps to a table with title \"NonSchemaType\"") { unknownCollection.findOne() } } @@ -136,6 +134,7 @@ abstract sealed class MongoCollectionTests { } } val user = app.createUserAndLogin() + @OptIn(ExperimentalKBsonSerializerApi::class) client = user.mongoClient(TEST_SERVICE_NAME) } @@ -153,12 +152,13 @@ abstract sealed class MongoCollectionTests { } @Test - open fun reshape() = runBlocking { + open fun withDocumentClass() = runBlocking { // Original typing assertIs(collection.insertOne(CollectionDataType("object-1", Random.nextInt()))) assertIs(collection.findOne()) // Reshaped + @OptIn(ExperimentalKBsonSerializerApi::class) val bsonCollection: MongoCollection = collection.withDocumentClass() assertIs(bsonCollection.insertOne(BsonDocument("name", "object-2"))) assertIs(bsonCollection.findOne()) @@ -167,6 +167,7 @@ abstract sealed class MongoCollectionTests { @Test fun reshape_withCustomSerialization() = runBlocking { + @OptIn(ExperimentalKBsonSerializerApi::class) val reshapedCollectionWithDefaultSerializer: MongoCollection = collection.withDocumentClass() @@ -174,6 +175,7 @@ abstract sealed class MongoCollectionTests { reshapedCollectionWithDefaultSerializer.insertOne(CustomDataType("object-2")) } + @OptIn(ExperimentalKBsonSerializerApi::class) val reshapedCollectionWithCustomSerializer: MongoCollection = collection.withDocumentClass(customEjsonSerializer) @@ -205,7 +207,6 @@ abstract sealed class MongoCollectionTests { @Test open fun findOne() = runBlocking { - RealmLog.level = LogLevel.ALL // Empty collections assertNull(collection.findOne()) @@ -532,8 +533,6 @@ abstract sealed class MongoCollectionTests { @Test fun deleteOne() = runBlocking { - // Argument wrapper DSL - RealmLog.level = LogLevel.ALL assertFalse { collection.deleteOne(BsonDocument()) } assertEquals( @@ -551,8 +550,6 @@ abstract sealed class MongoCollectionTests { assertEquals(1, collection.count(BsonDocument("""{ "name": "object-1" }"""))) } - // Explicit types - @Test fun deleteOne_fails() = runBlocking { assertFailsWithMessage("unknown top level operator: \$who.") { @@ -656,7 +653,6 @@ abstract sealed class MongoCollectionTests { @Test fun updateMany() = runBlocking { assertEquals(0, collection.count()) - RealmLog.level = LogLevel.ALL assertEquals( 4, collection.insertMany( @@ -987,7 +983,6 @@ abstract sealed class MongoCollectionTests { val names = (1..10).map { "object-${it % 5}" } val ids: List = collection.insertMany(names.map { CollectionDataType(it) }) - RealmLog.level = LogLevel.ALL // Delete with no match assertNull(collection.findOneAndDelete(BsonDocument("""{"name": "NOMATCH"}"""))) assertEquals(10, collection.count(filter = BsonDocument())) @@ -1055,6 +1050,7 @@ abstract sealed class MongoCollectionTests { } // Helper method to be able to differentiate collection creation across test classes +@OptIn(ExperimentalKBsonSerializerApi::class) inline fun MongoCollectionTests.collection(clazz: KClass): MongoCollection { return when (this) { is MongoCollectionFromDatabaseTests -> database.collection(T::class.simpleName!!) From b1e740e7e8b3d2322a7e7dc9c2007c27ab1742d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Wed, 20 Dec 2023 13:42:53 +0100 Subject: [PATCH 18/36] Add data classes for UpdateOne and UpdateMany responses --- .../kotlin/mongodb/mongo/MongoCollection.kt | 28 ++++++-- .../common/mongo/MongoCollectionTests.kt | 71 +++++++++++-------- 2 files changed, 63 insertions(+), 36 deletions(-) diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt index a18637067c..f8c086191a 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt @@ -117,14 +117,21 @@ public suspend fun MongoCollection<*, *>.deleteMany(filter: BsonDocument): Long return deleteMany(filter) } +/** + * Wrapper of results of an [updateOne] call. + * + * @param updated boolean indicating that a document was updated. + * @param upsertedId primary key of the new document if created. + */ +public data class UpdateOneResult(val updated: Boolean, val upsertedId: R?) public suspend inline fun MongoCollection.updateOne( filter: BsonDocument, update: BsonDocument, upsert: Boolean = false -): Pair { +): UpdateOneResult { isType>(this) - return updateOne(filter, update, upsert).let { (updated, asdf) -> - updated to asdf?.let { decodeFromBsonValue(it) } + return updateOne(filter, update, upsert).let { (updated, upsertedId) -> + UpdateOneResult(updated, upsertedId?.let { decodeFromBsonValue(it) }) } } @@ -133,18 +140,25 @@ public suspend inline fun MongoCollection<*, *>.updateOne( filter: BsonDocument, update: BsonDocument, upsert: Boolean = false -): Pair { +): UpdateOneResult { return (this as MongoCollection).updateOne(filter, update, upsert) } +/** + * Wrapper of results of an [updateMany] call. + * + * @param modifiedCount number of documents that was updated by the operation. + * @param upsertedId primary key of the new document if created. + */ +public data class UpdateManyResult(val modifiedCount: Long, val upsertedId: R?) public suspend inline fun MongoCollection.updateMany( filter: BsonDocument, update: BsonDocument, upsert: Boolean = false -): Pair { +): UpdateManyResult { isType>(this) return updateMany(filter, update, upsert).let { (updatedCount, upsertedId) -> - updatedCount to upsertedId?.let { decodeFromBsonValue(it) } + UpdateManyResult(updatedCount, upsertedId?.let { decodeFromBsonValue(it) }) } } @@ -153,7 +167,7 @@ public suspend inline fun MongoCollection<*, *>.updateMany( filter: BsonDocument, update: BsonDocument, upsert: Boolean = false -): Pair { +): UpdateManyResult { return (this as MongoCollection).updateMany(filter, update, upsert) } diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt index 20daf9614a..020a4c3959 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt @@ -596,27 +596,30 @@ abstract sealed class MongoCollectionTests { assertEquals(4, collection.count()) // Update no match - val updateWithoutMatch = collection.updateOne( + collection.updateOne( BsonDocument("""{ "name": "NOMATCH"}"""), BsonDocument("\$set", BsonDocument("""{ "name": "UPDATED"}""")), - ) - assertEquals(false to null, updateWithoutMatch) + ).let { (updated, upsertedId) -> + assertFalse(updated) + assertNull(upsertedId) + } // Update with match match - val updateWithMatch = collection.updateOne( + collection.updateOne( BsonDocument("""{ "name": "object-1"}"""), BsonDocument("\$set", BsonDocument("""{ "name": "object-2"}""")), - ) - assertEquals(true to null, updateWithMatch) + ).let { (updated, upsertedId) -> + assertTrue(updated) + assertNull(upsertedId) + } assertEquals(4, collection.count()) assertEquals(3, collection.count(filter = BsonDocument("""{"name": "object-1"}"""))) assertEquals(1, collection.count(filter = BsonDocument("""{"name": "object-2"}"""))) // Upsert no match - val upsertWithoutMatch = collection.updateOne( + collection.updateOne( BsonDocument("""{ "name": "object-3"}"""), BsonDocument(""" { "name": "object-2", "_id" : ${Random.nextInt()}}"""), upsert = true - ) - upsertWithoutMatch.let { (updated, upsertedId) -> + ).let { (updated, upsertedId) -> assertFalse(updated) assertIs(upsertedId) } @@ -624,10 +627,12 @@ abstract sealed class MongoCollectionTests { assertEquals(2, collection.count(filter = BsonDocument("""{"name": "object-2"}"""))) // Upsert with match - val upsertWithMatch = collection.updateOne( + collection.updateOne( BsonDocument("""{ "name": "object-2"}"""), BsonDocument(""" { "name": "object-3"}"""), upsert = true - ) - assertEquals(true to null, upsertWithMatch) + ).let { (updated, upsertedId) -> + assertTrue(updated) + assertNull(upsertedId) + } assertEquals(5, collection.count()) assertEquals(1, collection.count(filter = BsonDocument("""{"name": "object-2"}"""))) } @@ -666,38 +671,46 @@ abstract sealed class MongoCollectionTests { ) assertEquals(4, collection.count()) // Update with no match - val updateWithoutMatch = collection.updateMany( + collection.updateMany( BsonDocument("""{"name": "NOMATCH"}"""), BsonDocument("""{"name": "UPDATED"}"""), - ) - assertEquals(0L to null, updateWithoutMatch) + ).let { (modifiedCount, upsertedId) -> + assertEquals(0L, modifiedCount) + assertNull(upsertedId) + } assertEquals(0, collection.count(filter = BsonDocument("""{"name": "UPDATED"}"""))) assertEquals(4, collection.count()) // Update with match - val updateWithMatch = collection.updateMany( + collection.updateMany( BsonDocument("""{ "name": "x"}"""), BsonDocument("""{ "name": "UPDATED"}"""), - ) - assertEquals(2L to null, updateWithMatch) + ).let { (modifiedCount, upsertedId) -> + assertEquals(2L, modifiedCount) + assertNull(upsertedId) + } assertEquals(2, collection.count(filter = BsonDocument("""{"name": "UPDATED"}"""))) assertEquals(4, collection.count()) // Upsert no match - val upsertWithoutMatch = collection.updateMany( - BsonDocument("""{ "name": "NOMATCH"}"""), BsonDocument(""" { "name": "UPSERTED", "_id" : ${Random.nextInt()}}"""), upsert = true - ) - upsertWithoutMatch.let { - assertEquals(0, it.first) - assertIs(it.second) + collection.updateMany( + BsonDocument("""{ "name": "NOMATCH"}"""), + BsonDocument(""" { "name": "UPSERTED", "_id" : ${Random.nextInt()}}"""), + upsert = true + ).let { (modifiedCount, upsertedId) -> + assertEquals(0L, modifiedCount) + assertIs(upsertedId) } assertEquals(5, collection.count()) assertEquals(1, collection.count(filter = BsonDocument("""{"name": "UPSERTED"}"""))) + // Upsert with match - val upsertWithMatch = collection.updateMany( + collection.updateMany( BsonDocument("""{ "name": "y"}"""), BsonDocument(""" { "name": "z"}"""), upsert = true - ) - assertEquals(1L to null, upsertWithMatch) + ).let { (modifiedCount, upsertedId) -> + assertEquals(1L, modifiedCount) + assertNull(upsertedId) + } assertEquals(5, collection.count()) assertEquals(0, collection.count(filter = BsonDocument("""{"name": "y"}"""))) } @@ -708,8 +721,8 @@ abstract sealed class MongoCollectionTests { BsonDocument("""{ "name": "object-3"}"""), BsonDocument(""" { "name": "object-2", "_id" : ${Random.nextInt()}}"""), upsert = true - ).let { (updated, upsertedId) -> - assertEquals(0, updated) + ).let { (modifiedCount, upsertedId) -> + assertEquals(0, modifiedCount) assertIs(upsertedId) } } From 27d2e51d8cbb6ffd7fb1e476ecc19a7e3f48074b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Wed, 20 Dec 2023 15:10:07 +0100 Subject: [PATCH 19/36] Add documentation of collection operations --- .../kotlin/io/realm/kotlin/mongodb/User.kt | 3 + .../realm/kotlin/mongodb/mongo/MongoClient.kt | 22 ++ .../kotlin/mongodb/mongo/MongoCollection.kt | 231 +++++++++++++++++- .../kotlin/mongodb/mongo/MongoDatabase.kt | 6 + 4 files changed, 261 insertions(+), 1 deletion(-) diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/User.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/User.kt index a799cdf4c1..c3d85163a0 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/User.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/User.kt @@ -207,6 +207,9 @@ public interface User { /** * Get a [MongoClient] for accessing documents from App Service's _Data Source_. * + * Serialization to and from EJSON is performed with [KBSON](https://github.com/mongodb/kbson) + * and requires to opt-in to the experimental [ExperimentalKBsonSerializerApi]-feature. + * * @param serviceName the name of the data service. * @param eJson the EJson serializer that the [MongoClient] should use to convert objects and * primary keys with. Will default to the apps [EJson] instance configured with diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt index 975fb42b45..945388b4c5 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt @@ -24,6 +24,10 @@ import org.mongodb.kbson.serialization.EJson /** * A **Mongo client** is used to access an App Service's Data Source directly without Sync support. + * + * This API corresponds to the Atlas App Service "MongoDB API". Please consult the + * [MongoDB API Reference](https://www.mongodb.com/docs/atlas/app-services/functions/mongodb/api/) + * for a detailed description of methods and arguments. */ public interface MongoClient { @@ -35,6 +39,9 @@ public interface MongoClient { /** * Get a [MongoDatabase] object to access data from the remote collections of the data source. * + * Serialization to and from EJSON is performed with [KBSON](https://github.com/mongodb/kbson) + * and requires to opt-in to the experimental [ExperimentalKBsonSerializerApi]-feature. + * * @param databaseName name of the database from the data source. * @param eJson the EJson serializer that the [MongoDatabase] should use to convert objects and * primary keys with. Will default to the client's [EJson] instance. @@ -43,6 +50,21 @@ public interface MongoClient { public fun database(databaseName: String, eJson: EJson? = null): MongoDatabase } +/** + * Get a [MongoCollection] that exposes methods to retrieve and update data from the remote + * collection of objects of schema type [T]. + * + * Serialization to and from EJSON is performed with [KBSON](https://github.com/mongodb/kbson) + * and requires to opt-in to the experimental [ExperimentalKBsonSerializerApi]-feature. + * + * @param eJson the EJson serializer that the [MongoCollection] should use to convert objects and + * primary keys with. Will default to the databases [EJson] instance. + * @param T the schema type indicating which for which remote entities of the collection will be + * serialized from and to. + * @param K the default type that primary keys will be serialized into. + * @return a [MongoCollection] that will accept and return entities from the remote collection + * as [T] values. + */ @ExperimentalKBsonSerializerApi public inline fun MongoClient.collection(eJson: EJson? = null): MongoCollection { @Suppress("invisible_reference", "invisible_member") diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt index f8c086191a..44c093dc8a 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt @@ -31,6 +31,10 @@ import kotlin.jvm.JvmName * A __mongo collection__ provides access to retrieve and update data from the database's * collection with specific typed serialization. * + * This API corresponds to the Atlas App Service "MongoDB API". Please consult the + * [MongoDB API Reference](https://www.mongodb.com/docs/atlas/app-services/functions/mongodb/api/) + * for a detailed description of methods and arguments. + * * @param T the default type that remote entities of the collection will be serialized from and * to. * @param K the default type that primary keys will be serialized into. @@ -49,52 +53,138 @@ public interface MongoCollection { public fun withDocumentClass(eJson: EJson? = null): MongoCollection } +/** + * Returns the number of documents in the collection. + * + * @param filter a filter to select specific documents. If `null` then no filtering will be done. + * @param limit an upper bound of the number of documents to consider. If `null` then no limit is + * applied. + */ public suspend fun MongoCollection<*, *>.count(filter: BsonDocument? = null, limit: Long? = null): Long { isType>(this) return count(filter, limit) } -public suspend inline fun < reified T, R : Any> MongoCollection.findOne(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null): T? { +/** + * Retrieve a single object from the remote collection. + * + * @param filter a filter to select specific documents. If `null` then no filtering will be done. + * @param projection a BsonDocument that describes which fields that are returned from the server. + * If `null` then all fields will be returned. + * @param sort a document describing one or more fields used to sort documents before selecting the + * single document to return. If `null` then no sorting will be applied. + * @return the result of the remote `findOne` invocation deserialized into a [T]-instance. + */ +public suspend inline fun MongoCollection.findOne(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null): T? { isType>(this) val bsonValue: BsonValue = findOne(filter, projection, sort) return decodeFromBsonValue(bsonValue) } +/** + * Retrieve a single object from the remote collection. + * + * @param filter a filter to select specific documents. If `null` then no filtering will be done. + * @param projection a BsonDocument that describes which fields that are returned from the server. + * If `null` then all fields will be returned. + * @param sort a document describing one or more fields used to sort documents before selecting the + * single document to return. If `null` then no sorting will be applied. + * @param T the type that the result of the remote `findOne` invocation should be deserialized into. + * @return the result of the remote `findOne` invocation deserialized into a [T]-instance. + */ @JvmName("findOneTyped") public suspend inline fun MongoCollection<*, *>.findOne(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null): T? { return (this as MongoCollection).findOne(filter, projection, sort) } +/** + * Retrieve multiple object from the remote collection. + * + * @param filter a filter to select specific documents. If `null` then no filtering will be done. + * @param projection a BsonDocument that describes which fields that are returned from the server. + * If `null` then all fields will be returned. + * @param sort a document describing one or more fields used to sort documents before selecting the + * single document to return. If `null` then no sorting will be applied. + * @param limit an upper bound of the number of documents to consider. If `null` then no limit is + * applied. + * @return the result of the remote `find` invocation deserialized into a list of [T]-instances. + */ public suspend inline fun MongoCollection.find(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null, limit: Long? = null): List { isType>(this) return decodeFromBsonValueList(find(filter, projection, sort, limit).asArray().toList()) } +/** + * Retrieve multiple object from the remote collection. + * + * @param filter a filter to select specific documents. If `null` then no filtering will be done. + * @param projection a BsonDocument that describes which fields that are returned from the server. + * If `null` then all fields will be returned. + * @param sort a document describing one or more fields used to sort documents before selecting the + * single document to return. If `null` then no sorting will be applied. + * @param limit an upper bound of the number of documents to consider. If `null` then no limit is + * applied. + * @param T the type that the results of the remote `find` invocation should be deserialized into. + * @return the result of the remote `find` invocation deserialized into a list of [T]-instances. + */ @JvmName("findTyped") public suspend inline fun MongoCollection<*, *>.find(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null, limit: Long? = null): List { return (this as MongoCollection).find(filter, projection, sort, limit) } +/** + * Execute an aggregate pipeline on the remote collection. + * + * @param pipeline a list of aggregation pipeline stages. + * @return the result of the remote `aggregate` invocation deserialized into a list of [T]-instances. + */ public suspend inline fun MongoCollection.aggregate(pipeline: List): List { isType>(this) return decodeFromBsonValueList(aggregate(pipeline)) } +/** + * Execute an aggregate pipeline on the remote collection. + * + * @param pipeline a list of aggregation pipeline stages. + * @param T the type that the results of the remote `find` invocation should be deserialized into. + * @return the result of the remote `aggregate` invocation deserialized into a list of [T]-instances. + */ @JvmName("aggregateTyped") public suspend inline fun MongoCollection<*, *>.aggregate(pipeline: List): List { return (this as MongoCollection).aggregate(pipeline) } +/** + * Insert a single object into the remote collection. + * + * @param document the object to serialize and insert into the remote collection. + * @return the `_id` value of the document insert in the collection deserialized to a [R]-instance. + */ public suspend inline fun MongoCollection.insertOne(document: T): R { isType>(this) return decodeFromBsonValue(insertOne(encodeToBsonValue(document).asDocument())) } +/** + * Insert a single object into the remote collection. + * + * @param document the object to serialize and insert into the remote collection. + * @param T the type of object that should be serializer and inserted to the collection. + * @param R the type that the returned `_id` value should be deserialized into. + * @return the `_id` value of the document inserted in the collection deserialized to a [R]-instance. + */ @JvmName("insertOneTyped") public suspend inline fun MongoCollection<*, *>.insertOne(document: T): R { return (this as MongoCollection).insertOne(document) } +/** + * Insert a list of object into the remote collection. + * + * @param documents the objects to serialize and insert into the remote collection. + * @return the `_id` values of the documents inserted in the collection deserialized to a [R]-instance. + */ public suspend inline fun MongoCollection.insertMany( documents: Collection, ): List { @@ -102,16 +192,36 @@ public suspend inline fun MongoCollection MongoCollection<*, *>.insertMany(documents: Collection): List { return (this as MongoCollection).insertMany(documents) } +/** + * Delete a single object from the remote collection. + * + * @param filter a filter to specify the documents to delete. + * @return a boolean indicating if a document was delete or not. + */ public suspend fun MongoCollection<*, *>.deleteOne(filter: BsonDocument): Boolean { isType>(this) return deleteOne(filter) } +/** + * Delete multiple objects from the remote collection. + * + * @param filter a filter to specify the documents to delete. + * @return the number of documents that have been deleted. + */ public suspend fun MongoCollection<*, *>.deleteMany(filter: BsonDocument): Long { isType>(this) return deleteMany(filter) @@ -124,6 +234,16 @@ public suspend fun MongoCollection<*, *>.deleteMany(filter: BsonDocument): Long * @param upsertedId primary key of the new document if created. */ public data class UpdateOneResult(val updated: Boolean, val upsertedId: R?) + +/** + * Update or insert a single object in the remote collection. + * + * @param filter a filter to select the document to update. + * @param update a BsonDocument specifying the updates that should be applied to the document. + * @param upsert a boolean indicating if a new document should be inserted if the [filter] does not + * match any existing documents in the collection. + * @return the result of the `updateOne` operation. + */ public suspend inline fun MongoCollection.updateOne( filter: BsonDocument, update: BsonDocument, @@ -135,6 +255,16 @@ public suspend inline fun MongoCollection.updateOne( } } +/** + * Update or insert a single object in the remote collection. + * + * @param filter a filter to select the document to update. + * @param update a BsonDocument specifying the updates that should be applied to the document. + * @param upsert a boolean indicating if a new document should be inserted if the [filter] does not + * match any existing documents in the collection. + * @param R the type that the returned `_id` of a newly insert document should be deserialized into. + * @return the result of the `updateOne` operation. + */ @JvmName("updateOneTyped") public suspend inline fun MongoCollection<*, *>.updateOne( filter: BsonDocument, @@ -151,6 +281,16 @@ public suspend inline fun MongoCollection<*, *>.updateOne( * @param upsertedId primary key of the new document if created. */ public data class UpdateManyResult(val modifiedCount: Long, val upsertedId: R?) + +/** + * Update multiple objects or insert a single new object in the remote collection. + * + * @param filter a filter to select the documents to update. + * @param update a BsonDocument specifying the updates that should be applied to the documents. + * @param upsert a boolean indicating if a new document should be inserted if the [filter] does not + * match any existing documents in the collection. + * @return the result of the `updateMany` operation. + */ public suspend inline fun MongoCollection.updateMany( filter: BsonDocument, update: BsonDocument, @@ -162,6 +302,16 @@ public suspend inline fun MongoCollection.updat } } +/** + * Update multiple objects or insert a single new object in the remote collection. + * + * @param filter a filter to select the documents to update. + * @param update a BsonDocument specifying the updates that should be applied to the documents. + * @param upsert a boolean indicating if a new document should be inserted if the [filter] does not + * match any existing documents in the collection. + * @param R the type that the returned `_id` of a newly insert document should be deserialized into. + * @return the result of the `updateMany` operation. + */ @JvmName("updateManyTyped") public suspend inline fun MongoCollection<*, *>.updateMany( filter: BsonDocument, @@ -171,6 +321,20 @@ public suspend inline fun MongoCollection<*, *>.updateMany( return (this as MongoCollection).updateMany(filter, update, upsert) } +/** + * Find and update or insert a single new object in the remote collection. + * + * @param filter a filter to select the documents to update. + * @param update a BsonDocument specifying the updates that should be applied to the documents. + * @param projection a BsonDocument that describes which fields that are returned from the server. + * If `null` then all fields will be returned. + * @param sort a document describing one or more fields used to sort documents before selecting the + * single document to return. If `null` then no sorting will be applied. + * @param upsert a boolean indicating if a new document should be inserted if the [filter] does not + * match any existing documents in the collection. + * @param returnNewDoc a boolean indicating whether to return the document before or after the update. + * @return the result of the remote `findOneAndUpdate` invocation deserialized into a [T]-instance. + */ @Suppress("LongParameterList") public suspend inline fun MongoCollection.findOneAndUpdate( filter: BsonDocument, @@ -184,6 +348,21 @@ public suspend inline fun MongoCollection.findOneAndU return decodeFromBsonValue(findOneAndUpdate(filter, update, projection, sort, upsert, returnNewDoc)) } +/** + * Find and update or insert a single new object in the remote collection. + * + * @param filter a filter to select the documents to update. + * @param update a BsonDocument specifying the updates that should be applied to the documents. + * @param projection a BsonDocument that describes which fields that are returned from the server. + * If `null` then all fields will be returned. + * @param sort a document describing one or more fields used to sort documents before selecting the + * single document to return. If `null` then no sorting will be applied. + * @param upsert a boolean indicating if a new document should be inserted if the [filter] does not + * match any existing documents in the collection. + * @param returnNewDoc a boolean indicating whether to return the document before or after the update. + * @param T the type that the result of the remote `findOne` invocation should be deserialized into. + * @return the result of the remote `findOneAndUpdate` invocation deserialized into a [T]-instance. + */ @Suppress("LongParameterList") @JvmName("findAndUpdateTyped") public suspend inline fun MongoCollection<*, *>.findOneAndUpdate( @@ -197,6 +376,20 @@ public suspend inline fun MongoCollection<*, *>.findOneAndUpdate( return (this as MongoCollection).findOneAndUpdate(filter, update, projection, sort, upsert, returnNewDoc) } +/** + * Find and replace or insert a single new object in the remote collection. + * + * @param filter a filter to select the documents to update. + * @param document a BsonDocument specifying the updates that should be applied to the documents. + * @param projection a BsonDocument that describes which fields that are returned from the server. + * If `null` then all fields will be returned. + * @param sort a document describing one or more fields used to sort documents before selecting the + * single document to return. If `null` then no sorting will be applied. + * @param upsert a boolean indicating if a new document should be inserted if the [filter] does not + * match any existing documents in the collection. + * @param returnNewDoc a boolean indicating whether to return the document before or after the update. + * @return the result of the remote `findOneAndUpdate` invocation deserialized into a [T]-instance. + */ @Suppress("LongParameterList") public suspend inline fun MongoCollection.findOneAndReplace( filter: BsonDocument, @@ -210,6 +403,21 @@ public suspend inline fun MongoCollection.findOneAndR return decodeFromBsonValue(findOneAndReplace(filter, document, projection, sort, upsert, returnNewDoc)) } +/** + * Find and replace or insert a single new object in the remote collection. + * + * @param filter a filter to select the documents to update. + * @param document a BsonDocument specifying the updates that should be applied to the documents. + * @param projection a BsonDocument that describes which fields that are returned from the server. + * If `null` then all fields will be returned. + * @param sort a document describing one or more fields used to sort documents before selecting the + * single document to return. If `null` then no sorting will be applied. + * @param upsert a boolean indicating if a new document should be inserted if the [filter] does not + * match any existing documents in the collection. + * @param returnNewDoc a boolean indicating whether to return the document before or after the update. + * @param T the type that the result of the remote `findOne` invocation should be deserialized into. + * @return the result of the remote `findOneAndUpdate` invocation deserialized into a [T]-instance. + */ @Suppress("LongParameterList") @JvmName("findAndReplaceTyped") public suspend inline fun MongoCollection<*, *>.findOneAndReplace( @@ -223,6 +431,16 @@ public suspend inline fun MongoCollection<*, *>.findOneAndReplace( return (this as MongoCollection).findOneAndReplace(filter, update, projection, sort, upsert, returnNewDoc) } +/** + * Find and delete a single object in the remote collection. + * + * @param filter a filter to select the documents to update. + * @param projection a BsonDocument that describes which fields that are returned from the server. + * If `null` then all fields will be returned. + * @param sort a document describing one or more fields used to sort documents before selecting the + * single document to return. If `null` then no sorting will be applied. + * @return the result of the remote `findOneAndDelete` invocation deserialized into a [T]-instance. + */ public suspend inline fun MongoCollection.findOneAndDelete( filter: BsonDocument, projection: BsonDocument? = null, @@ -232,6 +450,17 @@ public suspend inline fun MongoCollection.findOneAndD return decodeFromBsonValue(findOneAndDelete(filter, projection, sort)) } +/** + * Find and delete a single object in the remote collection. + * + * @param filter a filter to select the documents to update. + * @param projection a BsonDocument that describes which fields that are returned from the server. + * If `null` then all fields will be returned. + * @param sort a document describing one or more fields used to sort documents before selecting the + * single document to return. If `null` then no sorting will be applied. + * @param T the type that the result of the remote `findOne` invocation should be deserialized into. + * @return the result of the remote `findOneAndDelete` invocation deserialized into a [T]-instance. + */ @JvmName("findAndDeleteTyped") public suspend inline fun MongoCollection<*, *>.findOneAndDelete( filter: BsonDocument, diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoDatabase.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoDatabase.kt index 257872f2eb..a0d54c5fcb 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoDatabase.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoDatabase.kt @@ -35,6 +35,9 @@ public interface MongoDatabase { * Get a [MongoCollection] that exposed methods to retrieve and update data from the database's * collection. * + * Serialization to and from EJSON is performed with [KBSON](https://github.com/mongodb/kbson) + * and requires to opt-in to the experimental [ExperimentalKBsonSerializerApi]-feature. + * * @param collectionName the name of the collection name that the [MongoCollection] will * connect to. * @return a [MongoCollection] that will accept and return entities from the remote collection @@ -46,6 +49,9 @@ public interface MongoDatabase { * Get a [MongoCollection] that exposed methods to retrieve and update data from the database's * collection with specific typed serialization. * + * Serialization to and from EJSON is performed with [KBSON](https://github.com/mongodb/kbson) + * and requires to opt-in to the experimental [ExperimentalKBsonSerializerApi]-feature. + * * @param collectionName the name of the collection name that the [MongoCollection] will * connect to. * @param eJson the EJson serializer that the [MongoCollection] should use to convert objects and From f46cad514e4ac71ea484c10e0b1f09f40ee0c577 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 2 Jan 2024 10:01:23 +0100 Subject: [PATCH 20/36] Apply suggestions from code review Co-authored-by: Christian Melchior --- CHANGELOG.md | 1 - .../kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt | 6 +++--- .../test/mongodb/common/mongo/MongoCollectionTests.kt | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d90454e644..aa82f7e9be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,7 +38,6 @@ ### Enhancements * None. ->>>>>>> main ### Fixed * Using keypaths in Flows could sometimes throw `java.lang.IllegalStateException: [RLM_ERR_WRONG_THREAD]: Realm accessed from incorrect thread.`. (Issue [#1594](https://github.com/realm/realm-kotlin/pull/1594, since 1.13.0) diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt index 44c093dc8a..005b848215 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt @@ -209,7 +209,7 @@ public suspend inline fun MongoCollection<*, * Delete a single object from the remote collection. * * @param filter a filter to specify the documents to delete. - * @return a boolean indicating if a document was delete or not. + * @return a boolean indicating if a document was deleted or not. */ public suspend fun MongoCollection<*, *>.deleteOne(filter: BsonDocument): Boolean { isType>(this) @@ -388,7 +388,7 @@ public suspend inline fun MongoCollection<*, *>.findOneAndUpdate( * @param upsert a boolean indicating if a new document should be inserted if the [filter] does not * match any existing documents in the collection. * @param returnNewDoc a boolean indicating whether to return the document before or after the update. - * @return the result of the remote `findOneAndUpdate` invocation deserialized into a [T]-instance. + * @return the result of the remote `findOneAndReplace` invocation deserialized into a [T]-instance. */ @Suppress("LongParameterList") public suspend inline fun MongoCollection.findOneAndReplace( @@ -416,7 +416,7 @@ public suspend inline fun MongoCollection.findOneAndR * match any existing documents in the collection. * @param returnNewDoc a boolean indicating whether to return the document before or after the update. * @param T the type that the result of the remote `findOne` invocation should be deserialized into. - * @return the result of the remote `findOneAndUpdate` invocation deserialized into a [T]-instance. + * @return the result of the remote `findOneAndReplace` invocation deserialized into a [T]-instance. */ @Suppress("LongParameterList") @JvmName("findAndReplaceTyped") diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt index 020a4c3959..e0feaf2f7c 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt @@ -166,7 +166,7 @@ abstract sealed class MongoCollectionTests { } @Test - fun reshape_withCustomSerialization() = runBlocking { + fun withDocumentClass_withCustomSerialization() = runBlocking { @OptIn(ExperimentalKBsonSerializerApi::class) val reshapedCollectionWithDefaultSerializer: MongoCollection = collection.withDocumentClass() From 7ce74af923c1b2d3caba4461de23c594edb45fc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 13 Feb 2024 13:58:13 +0100 Subject: [PATCH 21/36] Link serialization --- .../kotlin/internal/RealmObjectCompanion.kt | 2 +- .../internal/schema/CachedClassKeyMap.kt | 2 +- .../types/annotations/MongoDBSerializable.kt | 19 ++ .../mongodb/internal/MongoDBSerializer.kt | 205 ++++++++++++++ ...RealmModelSyntheticPropertiesGeneration.kt | 76 ++++-- .../kotlin/compiler/SyncLoweringExtension.kt | 8 +- .../kotlin/io/realm/kotlin/entities/Sample.kt | 1 + .../realm/kotlin/test/common/SampleTests.kt | 7 + .../test/mongodb/util/TestAppInitializer.kt | 26 ++ .../test/mongodb/common/UserProfileTests.kt | 26 +- .../kotlin/test/mongodb/common/UserTests.kt | 9 +- .../mongodb/common/mongo/MongoClientTests.kt | 8 +- .../common/mongo/MongoCollectionTests.kt | 252 ++++++++++++++++-- 13 files changed, 567 insertions(+), 74 deletions(-) create mode 100644 packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/annotations/MongoDBSerializable.kt create mode 100644 packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDBSerializer.kt diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectCompanion.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectCompanion.kt index 7f345fdcac..f176abbd90 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectCompanion.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectCompanion.kt @@ -30,7 +30,7 @@ import kotlin.reflect.KProperty1 public interface RealmObjectCompanion { public val `io_realm_kotlin_class`: KClass public val `io_realm_kotlin_className`: String - public val `io_realm_kotlin_fields`: Map> + public val `io_realm_kotlin_fields`: Map, KProperty1>> public val `io_realm_kotlin_primaryKey`: KMutableProperty1<*, *>? public val `io_realm_kotlin_classKind`: RealmClassKind public fun `io_realm_kotlin_schema`(): RealmClassImpl 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 7da918259f..4f781f60a5 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 @@ -147,7 +147,7 @@ public class CachedClassMetadata( properties = interopProperties.map { propertyInfo: PropertyInfo -> CachedPropertyMetadata( propertyInfo, - companion?.io_realm_kotlin_fields?.get(propertyInfo.name) + companion?.io_realm_kotlin_fields?.get(propertyInfo.name)?.second ) } } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/annotations/MongoDBSerializable.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/annotations/MongoDBSerializable.kt new file mode 100644 index 0000000000..f10f179440 --- /dev/null +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/annotations/MongoDBSerializable.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2024 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.realm.kotlin.types.annotations + +public annotation class MongoDBSerializable diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDBSerializer.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDBSerializer.kt new file mode 100644 index 0000000000..327fdd6bf3 --- /dev/null +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDBSerializer.kt @@ -0,0 +1,205 @@ +/* + * Copyright 2024 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.realm.kotlin.mongodb.internal + +import io.realm.kotlin.internal.ObjectIdImpl +import io.realm.kotlin.internal.RealmObjectCompanion +import io.realm.kotlin.internal.platform.realmObjectCompanionOrThrow +import io.realm.kotlin.internal.realmObjectCompanionOrNull +import io.realm.kotlin.internal.util.Validation +import io.realm.kotlin.schema.RealmStorageType +import io.realm.kotlin.types.BaseRealmObject +import io.realm.kotlin.types.MutableRealmInt +import io.realm.kotlin.types.RealmAny +import io.realm.kotlin.types.RealmDictionary +import io.realm.kotlin.types.RealmInstant +import io.realm.kotlin.types.RealmObject +import io.realm.kotlin.types.RealmUUID +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerializationException +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import org.mongodb.kbson.BsonArray +import org.mongodb.kbson.BsonBinary +import org.mongodb.kbson.BsonBoolean +import org.mongodb.kbson.BsonDocument +import org.mongodb.kbson.BsonDouble +import org.mongodb.kbson.BsonInt32 +import org.mongodb.kbson.BsonInt64 +import org.mongodb.kbson.BsonNull +import org.mongodb.kbson.BsonObjectId +import org.mongodb.kbson.BsonString +import org.mongodb.kbson.BsonType +import org.mongodb.kbson.BsonValue +import org.mongodb.kbson.Decimal128 +import org.mongodb.kbson.ObjectId +import kotlin.reflect.KClass +import kotlin.reflect.KMutableProperty1 +import kotlin.reflect.KProperty1 + +public open class MongoDBSerializer(clazz: KClass): KSerializer { + override val descriptor: SerialDescriptor = BsonDocument.serializer().descriptor + private val companion = realmObjectCompanionOrThrow(clazz as KClass) + override fun deserialize(decoder: Decoder): T { + val x: BsonDocument = decoder.decodeSerializableValue(BsonDocument.serializer()) + return bsonToObject(companion, x) + } + + private fun bsonToObject(companion: RealmObjectCompanion, bsonDocument: BsonDocument): T { + val instance = companion.io_realm_kotlin_newInstance() as T + val fields: Map, KProperty1>> = companion.io_realm_kotlin_fields + val schema = companion.io_realm_kotlin_schema() + bsonDocument.keys.forEach { + // FIXME Test exception path + val fieldsDescriptor = fields[it]?: throw SerializationException("Unknown field $it for type ${companion.io_realm_kotlin_className}") + val type = schema[it]?.type + val value: Any? = if (type?.storageType == RealmStorageType.OBJECT) { + // FIXME Should we rather embed targetCompanion directly and make it nullable for non RealmObjects + val targetCompanion = fieldsDescriptor.first.realmObjectCompanionOrNull()!! + val primaryKeyAccessor = + (targetCompanion.io_realm_kotlin_primaryKey as KMutableProperty1?) + when (primaryKeyAccessor) { + // Embedded objects does not have primary keys + null -> { + val bsonValue: BsonValue? = bsonDocument[it] + if (bsonValue != null && bsonValue != BsonNull) { + bsonToObject(targetCompanion, bsonValue.asDocument()) + } else { + null + } + } + else -> { + val targetInstance = (targetCompanion.io_realm_kotlin_newInstance() as BaseRealmObject) + primaryKeyAccessor.set(targetInstance, bsonDocument[it]?.asPrimitiveValue()) + targetInstance + } + } + } else { + bsonDocument[it]?.asPrimitiveValue() + } + // FIXME Check validity of nullability + (fieldsDescriptor.second as KMutableProperty1).set(instance, value) + } + return instance + } + + // FIXME Serialize null values? + // FIXME @Ignore + override fun serialize(encoder: Encoder, value: T) { + val document = objectToBson(companion, value) + encoder.encodeSerializableValue(BsonDocument.serializer(), document) + } + + private fun objectToBson(companion: RealmObjectCompanion, realmObject: BaseRealmObject): BsonDocument { + val fields: Map, KProperty1>> = companion.io_realm_kotlin_fields + val schema = companion.io_realm_kotlin_schema() + val document = BsonDocument() + fields.forEach { (fieldName, fieldDetails) -> + val (_, accessor) = fieldDetails + val type = schema[fieldName]?.type ?: Validation.sdkError("Schema does not contain property ${fieldName}") + val value = if (type.storageType == RealmStorageType.OBJECT) { + val target = accessor.get(realmObject) + if (target != null && target != BsonNull) { + val targetCompanion = + realmObjectCompanionOrThrow((target as BaseRealmObject)::class) + val primaryKeyProperty: KMutableProperty1? = targetCompanion.io_realm_kotlin_primaryKey as KMutableProperty1? + when (primaryKeyProperty) { + null -> objectToBson(targetCompanion, target) + else -> BsonValue(primaryKeyProperty.get(target)) + } + } else return@forEach + } else { + BsonValue(accessor.get(realmObject)) + } + document[fieldName] = value + } + return document + } +} + +internal operator fun BsonValue.Companion.invoke(any: Any?): BsonValue { + return when (any) { + null -> BsonNull + is String -> BsonString(any) + is Boolean -> BsonBoolean(any) + is Byte -> BsonInt32(any.toInt()) + is Char -> BsonInt32(any.code) + is Short -> BsonInt32(any.toInt()) + is Int -> BsonInt32(any) + is Long -> BsonInt64(any) + is Float -> BsonDouble(any.toDouble()) + is Double -> BsonDouble(any) + is ObjectId -> any + is io.realm.kotlin.types.ObjectId -> BsonObjectId((any as ObjectIdImpl).bytes) + is BsonValue -> any + is Decimal128 -> any + is ByteArray -> BsonBinary(any) + is MutableRealmInt -> BsonInt64(any.get()) + is RealmAny -> TODO() + is RealmObject -> TODO() + is RealmInstant -> TODO() + is RealmUUID -> TODO() + // RealmSet and RealmList ends up here + is Collection<*> -> BsonArray(any.map { BsonValue(it) }) + // RealmDictionaries + is RealmDictionary<*> -> BsonDocument(any.mapValues { BsonValue(it.value) }) + else -> TODO("Serializer does not support object of type ${any}") +// BsonType.TIMESTAMP -> asTimestamp() +// BsonType.BINARY -> asBinary() +// BsonType.DATE_TIME -> asDateTime() +// BsonType.DOCUMENT, +// BsonType.END_OF_DOCUMENT, +// BsonType.ARRAY, +// BsonType.UNDEFINED, +// BsonType.REGULAR_EXPRESSION, +// BsonType.DB_POINTER, +// BsonType.JAVASCRIPT, +// BsonType.SYMBOL, +// BsonType.JAVASCRIPT_WITH_SCOPE, +// BsonType.MIN_KEY, +// BsonType.MAX_KEY -> TODO() + } +} + +internal fun BsonValue.asPrimitiveValue(): Any? { + + return when (this.bsonType) { + BsonType.DOUBLE -> asDouble().value + BsonType.STRING -> asString().value + BsonType.INT32 -> asInt32().value + BsonType.TIMESTAMP -> asTimestamp() + BsonType.INT64 -> asInt64().value + BsonType.DECIMAL128 -> asDecimal128() + BsonType.BINARY -> asBinary() + BsonType.OBJECT_ID -> asObjectId() + BsonType.BOOLEAN -> asBoolean().value + BsonType.DATE_TIME -> asDateTime() + BsonType.NULL -> null + BsonType.DOCUMENT, + BsonType.END_OF_DOCUMENT, + BsonType.ARRAY, + BsonType.UNDEFINED, + BsonType.REGULAR_EXPRESSION, + BsonType.DB_POINTER, + BsonType.JAVASCRIPT, + BsonType.SYMBOL, + BsonType.JAVASCRIPT_WITH_SCOPE, + BsonType.MIN_KEY, + BsonType.MAX_KEY -> TODO("Deserializer does not support ${this.bsonType}") + } +} diff --git a/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/RealmModelSyntheticPropertiesGeneration.kt b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/RealmModelSyntheticPropertiesGeneration.kt index db43326fcf..ef1f8f6408 100644 --- a/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/RealmModelSyntheticPropertiesGeneration.kt +++ b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/RealmModelSyntheticPropertiesGeneration.kt @@ -80,6 +80,7 @@ import org.jetbrains.kotlin.ir.declarations.IrEnumEntry import org.jetbrains.kotlin.ir.declarations.IrField import org.jetbrains.kotlin.ir.declarations.IrProperty import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction +import org.jetbrains.kotlin.ir.descriptors.toIrBasedDescriptor import org.jetbrains.kotlin.ir.expressions.IrConst import org.jetbrains.kotlin.ir.expressions.IrExpression import org.jetbrains.kotlin.ir.expressions.IrExpressionBody @@ -89,6 +90,7 @@ import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl import org.jetbrains.kotlin.ir.expressions.impl.IrConstructorCallImpl import org.jetbrains.kotlin.ir.expressions.impl.IrExpressionBodyImpl import org.jetbrains.kotlin.ir.expressions.impl.IrGetEnumValueImpl +import org.jetbrains.kotlin.ir.expressions.impl.IrGetObjectValueImpl import org.jetbrains.kotlin.ir.expressions.impl.IrPropertyReferenceImpl import org.jetbrains.kotlin.ir.expressions.impl.IrSetFieldImpl import org.jetbrains.kotlin.ir.expressions.impl.IrVarargImpl @@ -106,7 +108,6 @@ import org.jetbrains.kotlin.ir.util.defaultType import org.jetbrains.kotlin.ir.util.functions import org.jetbrains.kotlin.ir.util.getPropertyGetter import org.jetbrains.kotlin.ir.util.getPropertySetter -import org.jetbrains.kotlin.ir.util.hasAnnotation import org.jetbrains.kotlin.ir.util.isVararg import org.jetbrains.kotlin.ir.util.parentAsClass import org.jetbrains.kotlin.name.Name @@ -169,6 +170,11 @@ class RealmModelSyntheticPropertiesGeneration(private val pluginContext: IrPlugi realmObjectInterface.defaultType, pluginContext.irBuiltIns.anyNType.makeNullable() ) + // Pair, KMutableProperty1> + private val fieldTypeAndProperty = pairClass.typeWith( + pluginContext.irBuiltIns.kClassClass.starProjectedType, + realmObjectMutablePropertyType + ) private val mapOf = pluginContext.referenceFunctions(KOTLIN_COLLECTIONS_MAPOF) .first { val parameters = it.owner.valueParameters @@ -176,16 +182,11 @@ class RealmModelSyntheticPropertiesGeneration(private val pluginContext: IrPlugi } private val companionFieldsType = mapClass.typeWith( pluginContext.irBuiltIns.stringType, - realmObjectMutablePropertyType - ) - @Suppress("UnusedPrivateMember") - private val companionComputedFieldsType = mapClass.typeWith( - pluginContext.irBuiltIns.stringType, - realmObjectPropertyType + fieldTypeAndProperty, ) private val companionFieldsElementType = pairClass.typeWith( pluginContext.irBuiltIns.stringType, - realmObjectMutablePropertyType + fieldTypeAndProperty ) val realmClassImpl = pluginContext.lookupClassOrThrow(ClassIds.REALM_CLASS_IMPL) @@ -290,7 +291,7 @@ class RealmModelSyntheticPropertiesGeneration(private val pluginContext: IrPlugi IrConstImpl.string(startOffset, endOffset, pluginContext.irBuiltIns.stringType, className) } - // Add `public val `io_realm_kotlin_fields`: Map>` property. + // Add `public val `io_realm_kotlin_fields`: Map, KProperty1>>` property. companion.addValueProperty( pluginContext, realmObjectCompanionInterface, @@ -307,7 +308,7 @@ class RealmModelSyntheticPropertiesGeneration(private val pluginContext: IrPlugi superQualifierSymbol = null ).apply { putTypeArgument(index = 0, type = pluginContext.irBuiltIns.stringType) - putTypeArgument(index = 1, type = realmObjectPropertyType) + putTypeArgument(index = 1, type = fieldTypeAndProperty) putValueArgument( index = 0, valueArgument = IrVarargImpl( @@ -315,20 +316,30 @@ class RealmModelSyntheticPropertiesGeneration(private val pluginContext: IrPlugi UNDEFINED_OFFSET, pluginContext.irBuiltIns.arrayClass.typeWith(companionFieldsElementType), type, - // Generate list of properties: List>> + // Generate list of properties: List, KMutableProperty1<*, *>>>> properties!!.entries.map { val property = it.value.declaration - val propertyType = if (it.value.isComputed) realmObjectPropertyType else + val targetType = property.backingField!!.type + val targetIrClass = pluginContext.lookupClassOrThrow(targetType.classId!!) + val targetKClassRef = IrClassReferenceImpl( + startOffset = startOffset, + endOffset = endOffset, + type = pluginContext.irBuiltIns.kClassClass.typeWith(targetType), + symbol = targetIrClass.symbol, + classType = targetIrClass.defaultType, + ) + val objectPropertyType = if (it.value.isComputed) realmObjectPropertyType else realmObjectMutablePropertyType - // Pair>() + val elementType = pairClass.typeWith(pluginContext.irBuiltIns.kClassClass.typeWith(), objectPropertyType) + // Pair>>() IrConstructorCallImpl.fromSymbolOwner( startOffset = startOffset, endOffset = endOffset, - type = companionFieldsElementType, + type = elementType, constructorSymbol = pairCtor ).apply { putTypeArgument(0, pluginContext.irBuiltIns.stringType) - putTypeArgument(1, propertyType) + putTypeArgument(1, elementType) putValueArgument( 0, IrConstImpl.string( @@ -340,16 +351,35 @@ class RealmModelSyntheticPropertiesGeneration(private val pluginContext: IrPlugi ) putValueArgument( 1, - IrPropertyReferenceImpl( + IrConstructorCallImpl.fromSymbolOwner( startOffset = startOffset, endOffset = endOffset, - type = kPropertyType, - symbol = property.symbol, - typeArgumentsCount = 0, - field = null, - getter = property.getter?.symbol, - setter = property.setter?.symbol - ) + type = elementType, + constructorSymbol = pairCtor + ).apply { + putTypeArgument( + 0, + pluginContext.irBuiltIns.kClassClass.starProjectedType + ) + putTypeArgument(1, objectPropertyType) + putValueArgument( + 0, + targetKClassRef + ) + putValueArgument( + 1, + IrPropertyReferenceImpl( + startOffset = startOffset, + endOffset = endOffset, + type = kPropertyType, + symbol = property.symbol, + typeArgumentsCount = 0, + field = null, + getter = property.getter?.symbol, + setter = property.setter?.symbol + ) + ) + } ) } } diff --git a/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/SyncLoweringExtension.kt b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/SyncLoweringExtension.kt index 89ff28d87d..48ca10815d 100644 --- a/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/SyncLoweringExtension.kt +++ b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/SyncLoweringExtension.kt @@ -102,10 +102,10 @@ private class SyncLowering(private val pluginContext: IrPluginContext, private v appCreateAppId.symbol to ( appCreateAppIdBundleId to { expression: IrCall -> IrGetObjectValueImpl( - expression.startOffset, - expression.endOffset, - IrSimpleTypeImpl(appImplCompanionSymbol, false, emptyList(), emptyList()), - appImplCompanionSymbol + startOffset = expression.startOffset, + endOffset = expression.endOffset, + type = IrSimpleTypeImpl(appImplCompanionSymbol, false, emptyList(), emptyList()), + symbol = appImplCompanionSymbol ) } ), 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 d4617e5250..b59e3ea9a7 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 @@ -48,6 +48,7 @@ class Sample : RealmObject { var decimal128Field: Decimal128 = Decimal128("1.8446744073709551618E-6157") var timestampField: RealmInstant = RealmInstant.from(100, 1000) var objectIdField: ObjectId = ObjectId.from("507f1f77bcf86cd799439011") + var objectIdField2: org.mongodb.kbson.ObjectId = org.mongodb.kbson.ObjectId() var bsonObjectIdField: BsonObjectId = BsonObjectId("507f1f77bcf86cd799439011") var uuidField: RealmUUID = RealmUUID.from("46423f1b-ce3e-4a7e-812f-004cf9c42d76") var binaryField: ByteArray = byteArrayOf(42) diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/SampleTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/SampleTests.kt index 3931202124..f695221ebc 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/SampleTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/SampleTests.kt @@ -33,6 +33,7 @@ import io.realm.kotlin.query.find import io.realm.kotlin.test.platform.PlatformUtils import io.realm.kotlin.types.RealmInstant import org.mongodb.kbson.Decimal128 +import org.mongodb.kbson.ObjectId import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test @@ -179,6 +180,8 @@ class SampleTests { @Test @Suppress("LongMethod") fun primitiveTypes() { + val oid = io.realm.kotlin.types.ObjectId.create() + val iod2 = org.mongodb.kbson.ObjectId() realm.writeBlocking { copyToRealm(Sample()).apply { stringField = "Realm Kotlin" @@ -192,6 +195,8 @@ class SampleTests { doubleField = 1.19851106 decimal128Field = Decimal128("2.155544073709551618E-6157") timestampField = RealmInstant.from(42, 420) + objectIdField = oid + objectIdField2 = iod2 } } @@ -210,6 +215,8 @@ class SampleTests { assertEquals(1.19851106, objects[0].doubleField) assertEquals(Decimal128("2.155544073709551618E-6157"), objects[0].decimal128Field) assertEquals(RealmInstant.from(42, 420), objects[0].timestampField) + assertEquals(oid, objects[0].objectIdField) + assertEquals(iod2, objects[0].objectIdField2) } // querying on each type diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/TestAppInitializer.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/TestAppInitializer.kt index 413bc33fc6..41b945c1be 100644 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/TestAppInitializer.kt +++ b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/TestAppInitializer.kt @@ -146,6 +146,32 @@ object TestAppInitializer { } """.trimIndent() ) + + app.addSchema( + """ + { + "metadata": { + "data_source": "BackingDB", + "database": "$databaseName", + "collection": "CollectionDataType" + }, + "schema": { + "properties": { + "_id": { + "bsonType": "int" + }, + "name": { + "bsonType": "string" + } + }, + "required": [ + "name" + ], + "title": "CollectionDataType" + } + } + """.trimIndent() + ) } @Suppress("LongMethod") diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserProfileTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserProfileTests.kt index 929fe66582..0599a4d108 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserProfileTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserProfileTests.kt @@ -78,7 +78,7 @@ class UserProfileTests { } private lateinit var app: TestApp - lateinit var profileBody: Map + lateinit var profileBody: Map private fun setDefaultProfile() { profileBody = mapOf( @@ -89,8 +89,8 @@ class UserProfileTests { "last_name" to userProfile.lastName, "gender" to userProfile.gender, "birthday" to userProfile.birthday, - "min_age" to "${userProfile.minAge}", - "max_age" to "${userProfile.maxAge}" + "min_age" to userProfile.minAge, + "max_age" to userProfile.maxAge ) } @@ -115,6 +115,14 @@ class UserProfileTests { body: String, callback: ResponseCallback ) { + val profileData = profileBody.map { (k, v) -> + val value = when(v) { + is String -> Json.encodeToString(v) + is Long -> Json.encodeToString(v) + else -> TODO("Unsupported user data type $k : $v") + } + "\"$k\" : $value" + }.joinToString(separator = ",", prefix = "{", postfix = "}") { it } val result: String = when { url.endsWith("/providers/local-userpass/login") -> """ @@ -141,7 +149,7 @@ class UserProfileTests { } } ], - "data": ${Json.encodeToString(profileBody)}, + "data": ${profileData}, "type": "normal", "roles": [ { @@ -196,12 +204,12 @@ class UserProfileTests { document.entries.forEach { (key: String, value: BsonValue) -> assertContains(profileBody.keys, key) - val stringValue = when (value.bsonType) { + val parsedValue: Any = when (value.bsonType) { BsonType.STRING -> value.asString().value - BsonType.INT64 -> value.asInt64().value.toString() + BsonType.INT64 -> value.asInt64().value else -> TODO() } - assertEquals(profileBody[key], stringValue, "failed comparing key $key") + assertEquals(profileBody[key], parsedValue, "failed comparing key $key") } } @@ -223,10 +231,10 @@ class UserProfileTests { setEmptyProfile() val user = app.createUserAndLogin() - assertFailsWithMessage("Could not decode field 'name': Undefined value on a non-optional field") { + assertFailsWithMessage("Fields [name, email, picture_url, first_name, last_name, gender, birthday, min_age, max_age] are required for type with serial name 'io.realm.kotlin.test.mongodb.common.UserProfile', but they were missing") { user.profile() } - assertFailsWithMessage("Could not decode field 'name': Undefined value on a non-optional field") { + assertFailsWithMessage("Fields [name, email, picture_url, first_name, last_name, gender, birthday, min_age, max_age] are required for type with serial name 'io.realm.kotlin.test.mongodb.common.UserProfile', but they were missing") { user.profile(UserProfile.serializer()) } } diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserTests.kt index 58b46dff6b..620d63c633 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserTests.kt @@ -756,15 +756,16 @@ class UserTests { createUserAndLogin(email, password) } val collectionWithDefaultSerializer = - user.mongoClient(TEST_SERVICE_NAME).database(app.clientAppId) - .collection("SyncDog") + user.mongoClient(TEST_SERVICE_NAME) + .database(app.clientAppId) + .collection("CollectionDataType") assertFailsWithMessage("Serializer for class 'CustomDataType' is not found.") { collectionWithDefaultSerializer.insertOne(CustomDataType("dog-1")) } val collectionWithCustomSerializer = user.mongoClient(TEST_SERVICE_NAME, customEjsonSerializer).database(app.clientAppId) - .collection("SyncDog") + .collection("CollectionDataType") assertIs(collectionWithCustomSerializer.insertOne(CustomDataType("dog-1"))) } @@ -778,7 +779,7 @@ class UserTests { val mongoClient = user.mongoClient("UNKNOWN_SERVICE") val collection = mongoClient.database(app.clientAppId).collection("CollectionDataType") - assertFailsWithMessage("service not found: 'UNKNOWN_SERVICE'") { + assertFailsWithMessage("Cannot access member 'insertOne' of undefined") { collection.insertOne(CollectionDataType("object-1")) } } diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTests.kt index 8e94963dff..bcaf7a833e 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTests.kt @@ -88,10 +88,10 @@ class MongoClientTests { } @Test - fun database_unknownDatabase() = runBlocking { - RealmLog.level = LogLevel.ALL - assertIs(client.database("Unknown").collection("CollectionDataType").insertOne(CollectionDataType("object-1"))) - RealmLog.level = LogLevel.WARN + fun database_createsCollectionOnInsertToUnknownDatabase() = runBlocking { + val database = client.database("Unknown") + val collection = database.collection("NewCollection") + assertIs(collection.insertOne(CollectionDataType("object-1"))) } @Test diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt index 020a4c3959..9d29846c59 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt @@ -16,9 +16,15 @@ package io.realm.kotlin.test.mongodb.common.mongo +import io.realm.kotlin.Realm +import io.realm.kotlin.ext.query import io.realm.kotlin.internal.platform.runBlocking +import io.realm.kotlin.log.LogLevel +import io.realm.kotlin.log.RealmLog import io.realm.kotlin.mongodb.AppConfiguration +import io.realm.kotlin.mongodb.User import io.realm.kotlin.mongodb.exceptions.ServiceException +import io.realm.kotlin.mongodb.internal.MongoDBSerializer import io.realm.kotlin.mongodb.mongo.MongoClient import io.realm.kotlin.mongodb.mongo.MongoCollection import io.realm.kotlin.mongodb.mongo.MongoDatabase @@ -36,12 +42,23 @@ import io.realm.kotlin.mongodb.mongo.insertMany import io.realm.kotlin.mongodb.mongo.insertOne import io.realm.kotlin.mongodb.mongo.updateMany import io.realm.kotlin.mongodb.mongo.updateOne +import io.realm.kotlin.mongodb.sync.SyncConfiguration +import io.realm.kotlin.mongodb.syncSession +import io.realm.kotlin.notifications.ResultsChange +import io.realm.kotlin.test.mongodb.TEST_APP_FLEX import io.realm.kotlin.test.mongodb.TestApp import io.realm.kotlin.test.mongodb.asTestApp import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage +import io.realm.kotlin.test.util.receiveOrFail +import io.realm.kotlin.test.util.use import io.realm.kotlin.types.BaseRealmObject +import io.realm.kotlin.types.EmbeddedRealmObject import io.realm.kotlin.types.RealmObject import io.realm.kotlin.types.annotations.PersistedName +import io.realm.kotlin.types.annotations.PrimaryKey +import kotlinx.coroutines.async +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.delay import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.SerializationException @@ -58,7 +75,6 @@ import org.mongodb.kbson.ObjectId import org.mongodb.kbson.serialization.EJson import org.mongodb.kbson.serialization.encodeToBsonValue import kotlin.random.Random -import kotlin.reflect.KClass import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test @@ -68,6 +84,7 @@ import kotlin.test.assertIs import kotlin.test.assertNotEquals import kotlin.test.assertNull import kotlin.test.assertTrue +import kotlin.time.Duration.Companion.seconds class MongoCollectionFromDatabaseTests : MongoCollectionTests() { @@ -79,23 +96,27 @@ class MongoCollectionFromDatabaseTests : MongoCollectionTests() { val databaseName = app.configuration.appId @OptIn(ExperimentalKBsonSerializerApi::class) database = client.database(databaseName) - collection = collection(CollectionDataType::class) + @OptIn(ExperimentalKBsonSerializerApi::class) + collection = collection() } @Test override fun findOne_unknownCollection() = runBlocking { // Unknown collections will create the collection if inserting document, so only use // NonSchemaType for queries - val unknownCollection = collection(NonSchemaType::class) + @OptIn(ExperimentalKBsonSerializerApi::class) + val unknownCollection = collection() assertNull(unknownCollection.findOne()) } } + class MongoCollectionFromClientTests : MongoCollectionTests() { @BeforeTest override fun setUp() { super.setUp() - collection = collection(CollectionDataType::class) + @OptIn(ExperimentalKBsonSerializerApi::class) + collection = collection() } @Test @@ -106,7 +127,8 @@ class MongoCollectionFromClientTests : MongoCollectionTests() { @Test override fun findOne_unknownCollection() = runBlocking { - val unknownCollection = collection(NonSchemaType::class) + @OptIn(ExperimentalKBsonSerializerApi::class) + val unknownCollection = collection() assertFailsWithMessage("no matching collection found that maps to a table with title \"NonSchemaType\"") { unknownCollection.findOne() } @@ -116,6 +138,7 @@ class MongoCollectionFromClientTests : MongoCollectionTests() { abstract sealed class MongoCollectionTests { lateinit var app: TestApp + lateinit var user: User lateinit var client: MongoClient lateinit var collection: MongoCollection @@ -123,6 +146,7 @@ abstract sealed class MongoCollectionTests { open fun setUp() { app = TestApp( this::class.simpleName, + TEST_APP_FLEX, builder = { builder: AppConfiguration.Builder -> builder.httpLogObfuscator(null) } @@ -131,9 +155,12 @@ abstract sealed class MongoCollectionTests { app.asTestApp.run { runBlocking { deleteDocuments(app.configuration.appId, "CollectionDataType", "{}") + deleteDocuments(app.configuration.appId, "Parent", "{}") + deleteDocuments(app.configuration.appId, "Child", "{}") + deleteDocuments(app.configuration.appId, "EmbeddedChild", "{}") } } - val user = app.createUserAndLogin() + user = app.createUserAndLogin() @OptIn(ExperimentalKBsonSerializerApi::class) client = user.mongoClient(TEST_SERVICE_NAME) } @@ -141,6 +168,14 @@ abstract sealed class MongoCollectionTests { @AfterTest fun teadDown() { RealmLog.level = LogLevel.WARN + app.asTestApp.run { + runBlocking { + deleteDocuments(app.configuration.appId, "CollectionDataType", "{}") + deleteDocuments(app.configuration.appId, "Parent", "{}") + deleteDocuments(app.configuration.appId, "Child", "{}") + deleteDocuments(app.configuration.appId, "EmbeddedChild", "{}") + } + } if (this::app.isInitialized) { app.close() } @@ -160,7 +195,7 @@ abstract sealed class MongoCollectionTests { // Reshaped @OptIn(ExperimentalKBsonSerializerApi::class) val bsonCollection: MongoCollection = collection.withDocumentClass() - assertIs(bsonCollection.insertOne(BsonDocument("name", "object-2"))) + assertIs(bsonCollection.insertOne(BsonDocument("_id" to BsonInt32(Random.nextInt()), "name" to BsonString("object-2")))) assertIs(bsonCollection.findOne()) assertEquals(2, bsonCollection.count()) } @@ -277,6 +312,49 @@ abstract sealed class MongoCollectionTests { } } + @Test + open fun findOne_links() = runBlocking { + RealmLog.level = LogLevel.ALL + Realm.open(SyncConfiguration.Builder(user, setOf(Parent::class, Child::class, EmbeddedChild::class, CollectionDataType::class)) + .initialSubscriptions { + add(it.query()) + add(it.query()) + } + .build()).use { + val syncedParent = it.write { + copyToRealm(Parent().apply { child = Child() }) + } + // We need to upload schema before proceeding + it.syncSession.uploadAllLocalChanges(30.seconds) + // The translator should have some time to integrate the synced data + delay(5.seconds) + + @OptIn(ExperimentalKBsonSerializerApi::class) + val mongoDBClientParent = collection().findOne() + assertEquals(syncedParent._id, mongoDBClientParent!!._id) + assertEquals(syncedParent.child!!._id, mongoDBClientParent!!.child!!._id) + } + } + + @OptIn(ExperimentalKBsonSerializerApi::class) + @Test + open fun findOne_embeddedObjects() = runBlocking { + // Empty collections + RealmLog.level = RealmLog.level + assertNull(collection.findOne()) + + val parentCollection = collection() + parentCollection.insertOne( + Parent().apply { + embeddedChild = EmbeddedChild().apply { name = "EMBEDDED-NAME" } + } + ) + + parentCollection.find().single().run { + assertEquals("EMBEDDED-NAME", embeddedChild!!.name) + } + } + @Test fun findOne_extraFieldsAreDiscarded() = runBlocking { collection.insertOne( @@ -376,7 +454,7 @@ abstract sealed class MongoCollectionTests { collection.find().let { assertTrue { it.isEmpty() } } val names = (1..10).map { "object-${it % 5}" } - collection.insertMany(names.map { BsonDocument("name", it) }) + collection.insertMany(names.map { BsonDocument("_id" to BsonInt32(Random.nextInt()), "name" to BsonString(it)) }) collection.find().let { results -> results.forEach { @@ -432,12 +510,93 @@ abstract sealed class MongoCollectionTests { assertEquals(1, collection.find().size) } + @Test + @OptIn(ExperimentalKBsonSerializerApi::class) + open fun insertOne_links() = runBlocking { + // Open a synced realm and verified that the linked entities we upload through the + Realm.open( + SyncConfiguration.Builder(user, setOf(Parent::class, Child::class, EmbeddedChild::class, CollectionDataType::class)) + .initialSubscriptions { + add(it.query()) + add(it.query()) + } + .build() + ).use { realm -> + // We need to upload schema before proceeding + realm.syncSession.uploadAllLocalChanges() + + // We set up listeners to be able to react on when the objects are seen in the synced realm + val childChannel = Channel>(10) + val childListener = + async { realm.query().asFlow().collect { childChannel.send(it) } } + childChannel.receiveOrFail(message = "Didn't receive initial value").let { + assertTrue { it.list.isEmpty() } + } + val parentChannel = Channel>(10) + val parentListener = + async { realm.query().asFlow().collect { parentChannel.send(it) } } + parentChannel.receiveOrFail(message = "Didn't receive initial value").let { + assertTrue { it.list.isEmpty() } + } + + val childCollection = collection() + val unmanagedChild = Child() + assertEquals(unmanagedChild._id, childCollection.insertOne(unmanagedChild)) + // We can't rely on the translator to incorporate the insertOnes in order so we need to + // assure that the child is actually added before verifying the link in the parent. + childChannel.receiveOrFail(message = "Didn't receive initial value").let { + assertEquals(unmanagedChild._id, it.list.first()._id) + } + + val parentCollection = collection() + val unmanagedParent = Parent().apply { + this.child = unmanagedChild + } + val actual = parentCollection.insertOne(unmanagedParent) + assertEquals(unmanagedParent._id, actual) + + // Verifying that the parent include the correct link + parentChannel.receiveOrFail( + timeout = 5.seconds, + message = "Didn't receive update value" + ).let { + val parent = it.list.first() + assertEquals(unmanagedParent._id, parent._id) + parent!!.child!!.let { + assertEquals(unmanagedChild._id, it._id) + assertEquals(unmanagedChild.name, it.name) + } + } + parentListener.cancel() + childListener.cancel() + } + } + + @OptIn(ExperimentalKBsonSerializerApi::class) + @Test + fun insertOne_embeddedObjects() = runBlocking { + // Empty collections + RealmLog.level = RealmLog.level + assertNull(collection.findOne()) + + val parentCollection = collection() + parentCollection.insertOne( + Parent().apply { + embeddedChild = EmbeddedChild().apply { name = "EMBEDDED-NAME" } + } + ) + + parentCollection.find().single().run { + assertEquals("EMBEDDED-NAME", embeddedChild!!.name) + } + } + @Test fun insertOne_explicitTypes() = runBlocking { assertEquals(0, collection.find().size) // Inserting document without _id will use ObjectId as _id - collection.insertOne(BsonDocument("name", "object-1")).let { - assertIs(it) + collection.insertOne(BsonDocument("_id" to BsonInt32(Random.nextInt()), "name" to BsonString("object-1"))).let { + assertIs(it) } // Inserted document will have ObjectId key and cannot be serialized into CollectionDataType // so find must also use BsonDocument @@ -459,9 +618,10 @@ abstract sealed class MongoCollectionTests { } @Test - fun insertOne_throwsOnMissingRequiredFields() = runBlocking { + open fun insertOne_throwsOnMissingRequiredFields() = runBlocking { + RealmLog.level = LogLevel.ALL assertFailsWithMessage("insert not permitted") { - collection.insertOne(BsonDocument()) + collection.insertOne(BsonDocument("_id", ObjectId())) } } @@ -489,15 +649,21 @@ abstract sealed class MongoCollectionTests { fun insertMany_explictTyped() = runBlocking { assertEquals(0, collection.find().size) - collection.insertMany((1..10).map { BsonDocument("name", "object-${it % 5}") }).let { + collection.insertMany( + (1..10).map { + BsonDocument("_id" to BsonInt32(Random.nextInt()), "name" to BsonString("object-${it % 5}")) + } + ).let { assertEquals(10, it.size) it.forEach { - assertIs(it) + assertIs(it) } } assertEquals(10, collection.find().size) } + // InsertMany with links + @Test fun insertMany_throwsOnEmptyList() = runBlocking { assertFailsWithMessage("must provide at least one element") { @@ -518,16 +684,17 @@ abstract sealed class MongoCollectionTests { } @Test - fun insertMany_throwsOnMissingRequiredFields() = runBlocking { + open fun insertMany_throwsOnMissingRequiredFields() = runBlocking { + RealmLog.level = LogLevel.ALL assertFailsWithMessage("insert not permitted") { - collection.insertOne(BsonDocument()) + collection.insertMany(listOf(BsonDocument())) } } @Test fun insertMany_throwsOnTypeMismatch() = runBlocking { assertFailsWithMessage("insert not permitted") { - collection.insertOne(BsonDocument(mapOf("_id" to ObjectId(), "name" to BsonString("object-1")))) + collection.insertMany(listOf(BsonDocument(mapOf("_id" to ObjectId(), "name" to BsonString("object-1"))))) } } @@ -1064,10 +1231,10 @@ abstract sealed class MongoCollectionTests { // Helper method to be able to differentiate collection creation across test classes @OptIn(ExperimentalKBsonSerializerApi::class) -inline fun MongoCollectionTests.collection(clazz: KClass): MongoCollection { +inline fun MongoCollectionTests.collection(eJson: EJson? = null): MongoCollection { return when (this) { - is MongoCollectionFromDatabaseTests -> database.collection(T::class.simpleName!!) - is MongoCollectionFromClientTests -> client.collection() + is MongoCollectionFromDatabaseTests -> database.collection(T::class.simpleName!!, eJson) + is MongoCollectionFromClientTests -> client.collection(eJson) } } @@ -1089,19 +1256,19 @@ class NonSchemaType : RealmObject { } @Serializable -class CollectionDataType(var name: String = "Default", var _id: Int = Random.nextInt()) : RealmObject { +class CollectionDataType(var name: String = "Default", @PrimaryKey var _id: Int = Random.nextInt()) : RealmObject { constructor() : this("Default") } // Distinct data type with same fields as the above CollectionDataType used to showcase injection // of custom serializers. @PersistedName("CollectionDataType") -class CustomDataType(var name: String) : RealmObject { +class CustomDataType(var name: String, var _id: Int = Random.nextInt()) : RealmObject { @Suppress("unused") constructor() : this("Default") } // Custom Id type to showcase that we can use custom serializers for primary key return values. -class CustomIdType(val id: ObjectId) +class CustomIdType(val id: Int) // Custom serializers to showcase that we can inject serializers throughout the MongoClient APIs. class CustomDataTypeSerializer : KSerializer { @@ -1110,25 +1277,30 @@ class CustomDataTypeSerializer : KSerializer { override val descriptor: SerialDescriptor = serializer.descriptor override fun deserialize(decoder: Decoder): CustomDataType { return decoder.decodeSerializableValue(serializer).let { - CustomDataType(it.asDocument()["name"]!!.asString().value) + val _id = it.asDocument()["_id"]!!.asInt32().value + val name = it.asDocument()["name"]!!.asString().value + CustomDataType(name, _id) } } override fun serialize(encoder: Encoder, value: CustomDataType) { - encoder.encodeSerializableValue(serializer, BsonDocument("name", value.name)) + val document = BsonDocument() + document["_id"] = BsonInt32(value._id) + document["name"] = BsonString(value.name) + encoder.encodeSerializableValue(serializer, document) } } class CustomIdSerializer : KSerializer { - val serializer = BsonValue.serializer() + val serializer = BsonInt32.serializer() override val descriptor: SerialDescriptor = serializer.descriptor override fun deserialize(decoder: Decoder): CustomIdType { return decoder.decodeSerializableValue(serializer).let { - CustomIdType(it.asObjectId()) + CustomIdType(it.asInt32().value) } } override fun serialize(encoder: Encoder, value: CustomIdType) { - encoder.encodeSerializableValue(serializer, value.id) + encoder.encodeSerializableValue(serializer, BsonInt32(value.id)) } } @@ -1139,3 +1311,27 @@ val customEjsonSerializer = EJson( contextual(CustomIdType::class, CustomIdSerializer()) } ) + +class ParentSerializer: MongoDBSerializer(Parent::class) +@Serializable(ParentSerializer::class) +class Parent : RealmObject { + @PrimaryKey + var _id: ObjectId = ObjectId() + var name: String = "DEFAULT-PAREN" + var child: Child? = null + var embeddedChild: EmbeddedChild? = null +} +@Serializable +class Child : RealmObject { + @PrimaryKey + var _id: ObjectId = ObjectId() + var name: String = "DEFAULT-CHILD" + +} + +@Serializable +class EmbeddedChild : EmbeddedRealmObject { + var _id: ObjectId = ObjectId() + var name: String = "EMBEDDED-CHILD" +} + From 88750e81bba7684dfbe69261d8e2d8df71c1f7f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Thu, 15 Feb 2024 13:44:45 +0100 Subject: [PATCH 22/36] Updates according to review comments --- .../kotlin/io/realm/kotlin/mongodb/User.kt | 1 + .../kotlin/mongodb/ext/MongoClientExt.kt | 47 + .../kotlin/mongodb/ext/MongoCollectionExt.kt | 495 +++++ .../realm/kotlin/mongodb/internal/UserImpl.kt | 5 +- .../realm/kotlin/mongodb/mongo/MongoClient.kt | 24 - .../kotlin/mongodb/mongo/MongoCollection.kt | 435 +---- .../01_AFTER.ValidateIrBeforeLowering.ir | 1592 +++++++++++------ .../01_AFTER.ValidateIrBeforeLowering.ir | 48 +- .../kotlin/test/mongodb/common/UserTests.kt | 15 +- .../mongodb/common/mongo/MongoClientTests.kt | 11 +- .../common/mongo/MongoCollectionTests.kt | 56 +- .../common/mongo/MongoDatabaseTests.kt | 8 +- 12 files changed, 1680 insertions(+), 1057 deletions(-) create mode 100644 packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/MongoClientExt.kt create mode 100644 packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/MongoCollectionExt.kt diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/User.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/User.kt index c3d85163a0..2a695f2ca9 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/User.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/User.kt @@ -214,6 +214,7 @@ public interface User { * @param eJson the EJson serializer that the [MongoClient] should use to convert objects and * primary keys with. Will default to the apps [EJson] instance configured with * [AppConfiguration.Builder.ejson]. + * throws IllegalStateException if trying to obtain a [MongoClient] from a logged out [User]. */ @ExperimentalKBsonSerializerApi public fun mongoClient(serviceName: String, eJson: EJson? = null): MongoClient diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/MongoClientExt.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/MongoClientExt.kt new file mode 100644 index 0000000000..185c00ff70 --- /dev/null +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/MongoClientExt.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2024 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.realm.kotlin.mongodb.ext + +import io.realm.kotlin.internal.platform.realmObjectCompanionOrThrow +import io.realm.kotlin.mongodb.internal.MongoClientCollection +import io.realm.kotlin.mongodb.internal.MongoClientImpl +import io.realm.kotlin.mongodb.mongo.MongoClient +import io.realm.kotlin.mongodb.mongo.MongoCollection +import io.realm.kotlin.types.BaseRealmObject +import org.mongodb.kbson.ExperimentalKBsonSerializerApi +import org.mongodb.kbson.serialization.EJson + +/** + * Get a [MongoCollection] that exposes methods to retrieve and update data from the remote + * collection of objects of schema type [T]. + * + * Serialization to and from EJSON is performed with [KBSON](https://github.com/mongodb/kbson) + * and requires to opt-in to the experimental [ExperimentalKBsonSerializerApi]-feature. + * + * @param eJson the EJson serializer that the [MongoCollection] should use to convert objects and + * primary keys with. Will default to the databases [EJson] instance. + * @param T the schema type indicating which for which remote entities of the collection will be + * serialized from and to. + * @param K the default type that primary keys will be serialized into. + * @return a [MongoCollection] that will accept and return entities from the remote collection + * as [T] values. + */ +@ExperimentalKBsonSerializerApi +public inline fun MongoClient.collection(eJson: EJson? = null): MongoCollection { + @Suppress("invisible_reference", "invisible_member") + return MongoClientCollection(this as MongoClientImpl, realmObjectCompanionOrThrow(T::class).io_realm_kotlin_className, eJson ?: this.eJson) +} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/MongoCollectionExt.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/MongoCollectionExt.kt new file mode 100644 index 0000000000..1d5a73c220 --- /dev/null +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/MongoCollectionExt.kt @@ -0,0 +1,495 @@ +/* + * Copyright 2024 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.realm.kotlin.mongodb.ext + +import io.realm.kotlin.internal.util.Validation +import io.realm.kotlin.mongodb.internal.MongoCollectionImpl +import io.realm.kotlin.mongodb.internal.decodeFromBsonValue +import io.realm.kotlin.mongodb.internal.decodeFromBsonValueList +import io.realm.kotlin.mongodb.internal.encodeToBsonValue +import io.realm.kotlin.mongodb.mongo.MongoCollection +import org.mongodb.kbson.BsonDocument +import org.mongodb.kbson.BsonValue +import kotlin.jvm.JvmName + +/** + * Returns the number of documents in the collection. + * + * @param filter a filter to select specific documents. If `null` then no filtering will be done. + * @param limit an upper bound of the number of documents to consider. If `null` then no limit is + * applied. + * @throws ServiceException if the underlying App Service HTTP requests fails. + */ +public suspend fun MongoCollection<*, *>.count(filter: BsonDocument? = null, limit: Long? = null): Long { + Validation.isType>(this) + return count(filter, limit) +} + +/** + * Retrieve a single object from the remote collection. + * + * @param filter a filter to select specific documents. If `null` then no filtering will be done. + * @param projection a BsonDocument that describes which fields that are returned from the server. + * If `null` then all fields will be returned. + * @param sort a document describing one or more fields used to sort documents before selecting the + * single document to return. If `null` then no sorting will be applied. + * @return the result of the remote `findOne` invocation deserialized into a [T]-instance. + * @throws ServiceException if the underlying App Service HTTP requests fails. + * @throws SerializationException if App Service response could not be deserialized to [T]. + */ +public suspend inline fun MongoCollection.findOne(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null): T? { + Validation.isType>(this) + val bsonValue: BsonValue = findOne(filter, projection, sort) + return decodeFromBsonValue(bsonValue) +} + +/** + * Retrieve a single object from the remote collection. + * + * @param filter a filter to select specific documents. If `null` then no filtering will be done. + * @param projection a BsonDocument that describes which fields that are returned from the server. + * If `null` then all fields will be returned. + * @param sort a document describing one or more fields used to sort documents before selecting the + * single document to return. If `null` then no sorting will be applied. + * @param T the type that the result of the remote `findOne` invocation should be deserialized into. + * @return the result of the remote `findOne` invocation deserialized into a [T]-instance. + * @throws ServiceException if the underlying App Service HTTP requests fails. + * @throws SerializationException if App Service response could not be deserialized to [T]. + */ +@JvmName("findOneTyped") +public suspend inline fun MongoCollection<*, *>.findOne(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null): T? { + return (this as MongoCollection).findOne(filter, projection, sort) +} + +/** + * Retrieve multiple object from the remote collection. + * + * @param filter a filter to select specific documents. If `null` then no filtering will be done. + * @param projection a BsonDocument that describes which fields that are returned from the server. + * If `null` then all fields will be returned. + * @param sort a document describing one or more fields used to sort documents before selecting the + * single document to return. If `null` then no sorting will be applied. + * @param limit an upper bound of the number of documents to consider. If `null` then no limit is + * applied. + * @return the result of the remote `find` invocation deserialized into a list of [T]-instances. + * @throws ServiceException if the underlying App Service HTTP requests fails. + * @throws SerializationException if App Service response could not be deserialized to `List`. + */ +public suspend inline fun MongoCollection.find(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null, limit: Long? = null): List { + Validation.isType>(this) + return find(filter, projection, sort, limit).asArray().map { decodeFromBsonValue(it) } +} + +/** + * Retrieve multiple object from the remote collection. + * + * @param filter a filter to select specific documents. If `null` then no filtering will be done. + * @param projection a BsonDocument that describes which fields that are returned from the server. + * If `null` then all fields will be returned. + * @param sort a document describing one or more fields used to sort documents before selecting the + * single document to return. If `null` then no sorting will be applied. + * @param limit an upper bound of the number of documents to consider. If `null` then no limit is + * applied. + * @param T the type that the results of the remote `find` invocation should be deserialized into. + * @return the result of the remote `find` invocation deserialized into a list of [T]-instances. + * @throws ServiceException if the underlying App Service HTTP requests fails. + * @throws SerializationException if App Service response could not be deserialized to `List`. + */ +@JvmName("findTyped") +public suspend inline fun MongoCollection<*, *>.find(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null, limit: Long? = null): List { + return (this as MongoCollection).find(filter, projection, sort, limit) +} + +/** + * Execute an aggregate pipeline on the remote collection. + * + * @param pipeline a list of aggregation pipeline stages. + * @return the result of the remote `aggregate` invocation deserialized into a list of [T]-instances. + * @throws ServiceException if the underlying App Service HTTP requests fails. + * @throws SerializationException if App Service response could not be deserialized to `List`. + */ +public suspend inline fun MongoCollection.aggregate(pipeline: List): List { + Validation.isType>(this) + return decodeFromBsonValueList(aggregate(pipeline)) +} + +/** + * Execute an aggregate pipeline on the remote collection. + * + * @param pipeline a list of aggregation pipeline stages. + * @param T the type that the results of the remote `find` invocation should be deserialized into. + * @return the result of the remote `aggregate` invocation deserialized into a list of [T]-instances. + * @throws ServiceException if the underlying App Service HTTP requests fails. + * @throws SerializationException if App Service response could not be deserialized to `List`. + */ +@JvmName("aggregateTyped") +public suspend inline fun MongoCollection<*, *>.aggregate(pipeline: List): List { + return (this as MongoCollection).aggregate(pipeline) +} + +/** + * Insert a single object into the remote collection. + * + * @param document the object to serialize and insert into the remote collection. + * @return the `_id` value of the document insert in the collection deserialized to a [R]-instance. + * @throws ServiceException if the underlying App Service HTTP requests fails. + * @throws SerializationException if [document] could not be serialized into a EJson document or if + * the App Service response could not be deserialized to [R]. + */ +public suspend inline fun MongoCollection.insertOne(document: T): R { + Validation.isType>(this) + return decodeFromBsonValue(insertOne(encodeToBsonValue(document).asDocument())) +} + +/** + * Insert a single object into the remote collection. + * + * @param document the object to serialize and insert into the remote collection. + * @param T the type of object that should be serializer and inserted to the collection. + * @param R the type that the returned `_id` value should be deserialized into. + * @return the `_id` value of the document inserted in the collection deserialized to a [R]-instance. + * @throws ServiceException if the underlying App Service HTTP requests fails. + * @throws SerializationException if [document] could not be serialized into a EJson document or if + * the App Service response could not be deserialized to [R]. + */ +@JvmName("insertOneTyped") +public suspend inline fun MongoCollection<*, *>.insertOne(document: T): R { + return (this as MongoCollection).insertOne(document) +} + +/** + * Insert a list of object into the remote collection. + * + * @param documents the objects to serialize and insert into the remote collection. + * @return the `_id` values of the documents inserted in the collection deserialized to a [R]-instance. + * @throws ServiceException if the underlying App Service HTTP requests fails. + * @throws SerializationException if [documents] could not be serialized into a EJson document or if + * the App Service response could not be deserialized to [R]. + */ +public suspend inline fun MongoCollection.insertMany( + documents: Collection, +): List { + Validation.isType>(this) + return decodeFromBsonValueList(insertMany(documents.map { encodeToBsonValue(it).asDocument() })) +} + +/** + * Insert a list of object into the remote collection. + * + * @param documents the objects to serialize and insert into the remote collection. + * @param T the type of object that should be serializer and inserted to the collection. + * @param R the type that the returned `_id` values should be deserialized into. + * @return the `_id` values of the documents inserted in the collection deserialized to a [R]-instance. + * @throws ServiceException if the underlying App Service HTTP requests fails. + * @throws SerializationException if [documents] could not be serialized into a EJson document or if + * the App Service response could not be deserialized to `List`. + */ +@JvmName("insertManyTyped") +public suspend inline fun MongoCollection<*, *>.insertMany(documents: Collection): List { + return (this as MongoCollection).insertMany(documents) +} + +/** + * Delete a single object from the remote collection. + * + * @param filter a filter to specify the documents to delete. + * @return a boolean indicating if a document was deleted or not. + * @throws ServiceException if the underlying App Service HTTP requests fails. + */ +public suspend fun MongoCollection<*, *>.deleteOne(filter: BsonDocument): Boolean { + Validation.isType>(this) + return deleteOne(filter) +} + +/** + * Delete multiple objects from the remote collection. + * + * @param filter a filter to specify the documents to delete. + * @return the number of documents that have been deleted. + * @throws ServiceException if the underlying App Service HTTP requests fails. + */ +public suspend fun MongoCollection<*, *>.deleteMany(filter: BsonDocument): Long { + Validation.isType>(this) + return deleteMany(filter) +} + +/** + * Wrapper of results of an [updateOne] call. + * + * @param updated boolean indicating that a document was updated. + * @param upsertedId primary key of the new document if created. + */ +public data class UpdateOneResult(val updated: Boolean, val upsertedId: R?) + +/** + * Update or insert a single object in the remote collection. + * + * @param filter a filter to select the document to update. + * @param update a BsonDocument specifying the updates that should be applied to the document. + * @param upsert a boolean indicating if a new document should be inserted if the [filter] does not + * match any existing documents in the collection. + * @return the result of the `updateOne` operation. + * @throws ServiceException if the underlying App Service HTTP requests fails. + * @throws SerializationException if App Service response could not be deserialized to + * [UpdateOneResult]. + */ +public suspend inline fun MongoCollection.updateOne( + filter: BsonDocument, + update: BsonDocument, + upsert: Boolean = false +): UpdateOneResult { + Validation.isType>(this) + return updateOne(filter, update, upsert).let { (updated, upsertedId) -> + UpdateOneResult(updated, upsertedId?.let { decodeFromBsonValue(it) }) + } +} + +/** + * Update or insert a single object in the remote collection. + * + * @param filter a filter to select the document to update. + * @param update a BsonDocument specifying the updates that should be applied to the document. + * @param upsert a boolean indicating if a new document should be inserted if the [filter] does not + * match any existing documents in the collection. + * @param R the type that the returned `_id` of a newly insert document should be deserialized into. + * @return the result of the `updateOne` operation. + * @throws ServiceException if the underlying App Service HTTP requests fails. + * @throws SerializationException if App Service response could not be deserialized to + * [UpdateOneResult]. + */ +@JvmName("updateOneTyped") +public suspend inline fun MongoCollection<*, *>.updateOne( + filter: BsonDocument, + update: BsonDocument, + upsert: Boolean = false +): UpdateOneResult { + return (this as MongoCollection).updateOne(filter, update, upsert) +} + +/** + * Wrapper of results of an [updateMany] call. + * + * @param modifiedCount number of documents that was updated by the operation. + * @param upsertedId primary key of the new document if created. + */ +public data class UpdateManyResult(val modifiedCount: Long, val upsertedId: R?) + +/** + * Update multiple objects or insert a single new object in the remote collection. + * + * @param filter a filter to select the documents to update. + * @param update a BsonDocument specifying the updates that should be applied to the documents. + * @param upsert a boolean indicating if a new document should be inserted if the [filter] does not + * match any existing documents in the collection. + * @return the result of the `updateMany` operation. + * @throws ServiceException if the underlying App Service HTTP requests fails. + * @throws SerializationException if App Service response could not be deserialized to + * [UpdateManyResult]. + */ +public suspend inline fun MongoCollection.updateMany( + filter: BsonDocument, + update: BsonDocument, + upsert: Boolean = false +): UpdateManyResult { + Validation.isType>(this) + return updateMany(filter, update, upsert).let { (updatedCount, upsertedId) -> + UpdateManyResult(updatedCount, upsertedId?.let { decodeFromBsonValue(it) }) + } +} + +/** + * Update multiple objects or insert a single new object in the remote collection. + * + * @param filter a filter to select the documents to update. + * @param update a BsonDocument specifying the updates that should be applied to the documents. + * @param upsert a boolean indicating if a new document should be inserted if the [filter] does not + * match any existing documents in the collection. + * @param R the type that the returned `_id` of a newly insert document should be deserialized into. + * @return the result of the `updateMany` operation. + * @throws ServiceException if the underlying App Service HTTP requests fails. + * @throws SerializationException if App Service response could not be deserialized to + * [UpdateManyResult]. + */ +@JvmName("updateManyTyped") +public suspend inline fun MongoCollection<*, *>.updateMany( + filter: BsonDocument, + update: BsonDocument, + upsert: Boolean = false +): UpdateManyResult { + return (this as MongoCollection).updateMany(filter, update, upsert) +} + +/** + * Find and update or insert a single new object in the remote collection. + * + * @param filter a filter to select the documents to update. + * @param update a BsonDocument specifying the updates that should be applied to the documents. + * @param projection a BsonDocument that describes which fields that are returned from the server. + * If `null` then all fields will be returned. + * @param sort a document describing one or more fields used to sort documents before selecting the + * single document to return. If `null` then no sorting will be applied. + * @param upsert a boolean indicating if a new document should be inserted if the [filter] does not + * match any existing documents in the collection. + * @param returnNewDoc a boolean indicating whether to return the document before or after the update. + * @return the result of the remote `findOneAndUpdate` invocation deserialized into a [T]-instance. + * @throws ServiceException if the underlying App Service HTTP requests fails. + * @throws SerializationException if App Service response could not be deserialized to [T]. + */ +@Suppress("LongParameterList") +public suspend inline fun MongoCollection.findOneAndUpdate( + filter: BsonDocument, + update: BsonDocument, + projection: BsonDocument? = null, + sort: BsonDocument? = null, + upsert: Boolean = false, + returnNewDoc: Boolean = false, +): T? { + Validation.isType>(this) + return decodeFromBsonValue(findOneAndUpdate(filter, update, projection, sort, upsert, returnNewDoc)) +} + +/** + * Find and update or insert a single new object in the remote collection. + * + * @param filter a filter to select the documents to update. + * @param update a BsonDocument specifying the updates that should be applied to the documents. + * @param projection a BsonDocument that describes which fields that are returned from the server. + * If `null` then all fields will be returned. + * @param sort a document describing one or more fields used to sort documents before selecting the + * single document to return. If `null` then no sorting will be applied. + * @param upsert a boolean indicating if a new document should be inserted if the [filter] does not + * match any existing documents in the collection. + * @param returnNewDoc a boolean indicating whether to return the document before or after the update. + * @param T the type that the result of the remote `findOne` invocation should be deserialized into. + * @return the result of the remote `findOneAndUpdate` invocation deserialized into a [T]-instance. + * @throws ServiceException if the underlying App Service HTTP requests fails. + * @throws SerializationException if App Service response could not be deserialized to [T]. + */ +@Suppress("LongParameterList") +@JvmName("findAndUpdateTyped") +public suspend inline fun MongoCollection<*, *>.findOneAndUpdate( + filter: BsonDocument, + update: BsonDocument, + projection: BsonDocument? = null, + sort: BsonDocument? = null, + upsert: Boolean = false, + returnNewDoc: Boolean = false, +): T? { + return (this as MongoCollection).findOneAndUpdate(filter, update, projection, sort, upsert, returnNewDoc) +} + +/** + * Find and replace or insert a single new object in the remote collection. + * + * @param filter a filter to select the documents to update. + * @param document a BsonDocument specifying the updates that should be applied to the documents. + * @param projection a BsonDocument that describes which fields that are returned from the server. + * If `null` then all fields will be returned. + * @param sort a document describing one or more fields used to sort documents before selecting the + * single document to return. If `null` then no sorting will be applied. + * @param upsert a boolean indicating if a new document should be inserted if the [filter] does not + * match any existing documents in the collection. + * @param returnNewDoc a boolean indicating whether to return the document before or after the update. + * @return the result of the remote `findOneAndReplace` invocation deserialized into a [T]-instance. + * @throws ServiceException if the underlying App Service HTTP requests fails. + * @throws SerializationException if App Service response could not be deserialized to [T]. + */ +@Suppress("LongParameterList") +public suspend inline fun MongoCollection.findOneAndReplace( + filter: BsonDocument, + document: BsonDocument, + projection: BsonDocument? = null, + sort: BsonDocument? = null, + upsert: Boolean = false, + returnNewDoc: Boolean = false, +): T? { + Validation.isType>(this) + return decodeFromBsonValue(findOneAndReplace(filter, document, projection, sort, upsert, returnNewDoc)) +} + +/** + * Find and replace or insert a single new object in the remote collection. + * + * @param filter a filter to select the documents to update. + * @param document a BsonDocument specifying the updates that should be applied to the documents. + * @param projection a BsonDocument that describes which fields that are returned from the server. + * If `null` then all fields will be returned. + * @param sort a document describing one or more fields used to sort documents before selecting the + * single document to return. If `null` then no sorting will be applied. + * @param upsert a boolean indicating if a new document should be inserted if the [filter] does not + * match any existing documents in the collection. + * @param returnNewDoc a boolean indicating whether to return the document before or after the update. + * @param T the type that the result of the remote `findOne` invocation should be deserialized into. + * @return the result of the remote `findOneAndReplace` invocation deserialized into a [T]-instance. + * @throws ServiceException if the underlying App Service HTTP requests fails. + * @throws SerializationException if App Service response could not be deserialized to [T]. + */ +@Suppress("LongParameterList") +@JvmName("findAndReplaceTyped") +public suspend inline fun MongoCollection<*, *>.findOneAndReplace( + filter: BsonDocument, + update: BsonDocument, + projection: BsonDocument? = null, + sort: BsonDocument? = null, + upsert: Boolean = false, + returnNewDoc: Boolean = false, +): T? { + return (this as MongoCollection).findOneAndReplace(filter, update, projection, sort, upsert, returnNewDoc) +} + +/** + * Find and delete a single object in the remote collection. + * + * @param filter a filter to select the documents to update. + * @param projection a BsonDocument that describes which fields that are returned from the server. + * If `null` then all fields will be returned. + * @param sort a document describing one or more fields used to sort documents before selecting the + * single document to return. If `null` then no sorting will be applied. + * @return the result of the remote `findOneAndDelete` invocation deserialized into a [T]-instance. + * @throws ServiceException if the underlying App Service HTTP requests fails. + * @throws SerializationException if App Service response could not be deserialized to [T]. + */ +public suspend inline fun MongoCollection.findOneAndDelete( + filter: BsonDocument, + projection: BsonDocument? = null, + sort: BsonDocument? = null, +): T? { + Validation.isType>(this) + return decodeFromBsonValue(findOneAndDelete(filter, projection, sort)) +} + +/** + * Find and delete a single object in the remote collection. + * + * @param filter a filter to select the documents to update. + * @param projection a BsonDocument that describes which fields that are returned from the server. + * If `null` then all fields will be returned. + * @param sort a document describing one or more fields used to sort documents before selecting the + * single document to return. If `null` then no sorting will be applied. + * @param T the type that the result of the remote `findOne` invocation should be deserialized into. + * @return the result of the remote `findOneAndDelete` invocation deserialized into a [T]-instance. + * @throws ServiceException if the underlying App Service HTTP requests fails. + * @throws SerializationException if App Service response could not be deserialized to [T]. + */ +@JvmName("findAndDeleteTyped") +public suspend inline fun MongoCollection<*, *>.findOneAndDelete( + filter: BsonDocument, + projection: BsonDocument? = null, + sort: BsonDocument? = null, +): T? { + return (this as MongoCollection).findOneAndDelete(filter, projection, sort) +} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/UserImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/UserImpl.kt index 5aad6ffe67..aa45c1d7ed 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/UserImpl.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/UserImpl.kt @@ -186,7 +186,10 @@ public class UserImpl( } @ExperimentalKBsonSerializerApi - override fun mongoClient(serviceName: String, eJson: EJson?): MongoClient = MongoClientImpl(this, serviceName, eJson ?: app.configuration.ejson) + override fun mongoClient(serviceName: String, eJson: EJson?): MongoClient { + if (!loggedIn) throw IllegalStateException("Cannot obtain a MongoClient from a logged out user") + return MongoClientImpl(this, serviceName, eJson ?: app.configuration.ejson) + } override fun equals(other: Any?): Boolean { if (this === other) return true diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt index 945388b4c5..61ec271eca 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt @@ -16,9 +16,6 @@ package io.realm.kotlin.mongodb.mongo -import io.realm.kotlin.mongodb.internal.MongoClientCollection -import io.realm.kotlin.mongodb.internal.MongoClientImpl -import io.realm.kotlin.types.BaseRealmObject import org.mongodb.kbson.ExperimentalKBsonSerializerApi import org.mongodb.kbson.serialization.EJson @@ -49,24 +46,3 @@ public interface MongoClient { @ExperimentalKBsonSerializerApi public fun database(databaseName: String, eJson: EJson? = null): MongoDatabase } - -/** - * Get a [MongoCollection] that exposes methods to retrieve and update data from the remote - * collection of objects of schema type [T]. - * - * Serialization to and from EJSON is performed with [KBSON](https://github.com/mongodb/kbson) - * and requires to opt-in to the experimental [ExperimentalKBsonSerializerApi]-feature. - * - * @param eJson the EJson serializer that the [MongoCollection] should use to convert objects and - * primary keys with. Will default to the databases [EJson] instance. - * @param T the schema type indicating which for which remote entities of the collection will be - * serialized from and to. - * @param K the default type that primary keys will be serialized into. - * @return a [MongoCollection] that will accept and return entities from the remote collection - * as [T] values. - */ -@ExperimentalKBsonSerializerApi -public inline fun MongoClient.collection(eJson: EJson? = null): MongoCollection { - @Suppress("invisible_reference", "invisible_member") - return MongoClientCollection(this as MongoClientImpl, io.realm.kotlin.internal.platform.realmObjectCompanionOrThrow(T::class).io_realm_kotlin_className, eJson ?: this.eJson) -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt index 005b848215..ec50d255fe 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt @@ -16,16 +16,8 @@ package io.realm.kotlin.mongodb.mongo -import io.realm.kotlin.internal.util.Validation.isType -import io.realm.kotlin.mongodb.internal.MongoCollectionImpl -import io.realm.kotlin.mongodb.internal.decodeFromBsonValue -import io.realm.kotlin.mongodb.internal.decodeFromBsonValueList -import io.realm.kotlin.mongodb.internal.encodeToBsonValue -import org.mongodb.kbson.BsonDocument -import org.mongodb.kbson.BsonValue import org.mongodb.kbson.ExperimentalKBsonSerializerApi import org.mongodb.kbson.serialization.EJson -import kotlin.jvm.JvmName /** * A __mongo collection__ provides access to retrieve and update data from the database's @@ -35,6 +27,16 @@ import kotlin.jvm.JvmName * [MongoDB API Reference](https://www.mongodb.com/docs/atlas/app-services/functions/mongodb/api/) * for a detailed description of methods and arguments. * + * Input arguments and responses to the App Service HTTP requests will be serialized from and to + * the type [T] and [K] using [Kotlin's Serialization framework](https://kotlinlang.org/docs/serialization.html) + * and can be customized by [Serializable]-annotations or customizing the [EJson]-serializer passed + * to the various [MongoClient], [MongoDatabase] and [MongoCollection]-factory methods. + * + * All operations on a [MongoCollection] will throw an: + * - [ServiceException] if the underlying App Service HTTP requests fails + * - [SerializationException] if input arguments cannot be serialized to a valid EJson document + * or if the App Service response could not be deserialized to the return types. + * * @param T the default type that remote entities of the collection will be serialized from and * to. * @param K the default type that primary keys will be serialized into. @@ -52,420 +54,3 @@ public interface MongoCollection { @ExperimentalKBsonSerializerApi public fun withDocumentClass(eJson: EJson? = null): MongoCollection } - -/** - * Returns the number of documents in the collection. - * - * @param filter a filter to select specific documents. If `null` then no filtering will be done. - * @param limit an upper bound of the number of documents to consider. If `null` then no limit is - * applied. - */ -public suspend fun MongoCollection<*, *>.count(filter: BsonDocument? = null, limit: Long? = null): Long { - isType>(this) - return count(filter, limit) -} - -/** - * Retrieve a single object from the remote collection. - * - * @param filter a filter to select specific documents. If `null` then no filtering will be done. - * @param projection a BsonDocument that describes which fields that are returned from the server. - * If `null` then all fields will be returned. - * @param sort a document describing one or more fields used to sort documents before selecting the - * single document to return. If `null` then no sorting will be applied. - * @return the result of the remote `findOne` invocation deserialized into a [T]-instance. - */ -public suspend inline fun MongoCollection.findOne(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null): T? { - isType>(this) - val bsonValue: BsonValue = findOne(filter, projection, sort) - return decodeFromBsonValue(bsonValue) -} - -/** - * Retrieve a single object from the remote collection. - * - * @param filter a filter to select specific documents. If `null` then no filtering will be done. - * @param projection a BsonDocument that describes which fields that are returned from the server. - * If `null` then all fields will be returned. - * @param sort a document describing one or more fields used to sort documents before selecting the - * single document to return. If `null` then no sorting will be applied. - * @param T the type that the result of the remote `findOne` invocation should be deserialized into. - * @return the result of the remote `findOne` invocation deserialized into a [T]-instance. - */ -@JvmName("findOneTyped") -public suspend inline fun MongoCollection<*, *>.findOne(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null): T? { - return (this as MongoCollection).findOne(filter, projection, sort) -} - -/** - * Retrieve multiple object from the remote collection. - * - * @param filter a filter to select specific documents. If `null` then no filtering will be done. - * @param projection a BsonDocument that describes which fields that are returned from the server. - * If `null` then all fields will be returned. - * @param sort a document describing one or more fields used to sort documents before selecting the - * single document to return. If `null` then no sorting will be applied. - * @param limit an upper bound of the number of documents to consider. If `null` then no limit is - * applied. - * @return the result of the remote `find` invocation deserialized into a list of [T]-instances. - */ -public suspend inline fun MongoCollection.find(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null, limit: Long? = null): List { - isType>(this) - return decodeFromBsonValueList(find(filter, projection, sort, limit).asArray().toList()) -} - -/** - * Retrieve multiple object from the remote collection. - * - * @param filter a filter to select specific documents. If `null` then no filtering will be done. - * @param projection a BsonDocument that describes which fields that are returned from the server. - * If `null` then all fields will be returned. - * @param sort a document describing one or more fields used to sort documents before selecting the - * single document to return. If `null` then no sorting will be applied. - * @param limit an upper bound of the number of documents to consider. If `null` then no limit is - * applied. - * @param T the type that the results of the remote `find` invocation should be deserialized into. - * @return the result of the remote `find` invocation deserialized into a list of [T]-instances. - */ -@JvmName("findTyped") -public suspend inline fun MongoCollection<*, *>.find(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null, limit: Long? = null): List { - return (this as MongoCollection).find(filter, projection, sort, limit) -} - -/** - * Execute an aggregate pipeline on the remote collection. - * - * @param pipeline a list of aggregation pipeline stages. - * @return the result of the remote `aggregate` invocation deserialized into a list of [T]-instances. - */ -public suspend inline fun MongoCollection.aggregate(pipeline: List): List { - isType>(this) - return decodeFromBsonValueList(aggregate(pipeline)) -} - -/** - * Execute an aggregate pipeline on the remote collection. - * - * @param pipeline a list of aggregation pipeline stages. - * @param T the type that the results of the remote `find` invocation should be deserialized into. - * @return the result of the remote `aggregate` invocation deserialized into a list of [T]-instances. - */ -@JvmName("aggregateTyped") -public suspend inline fun MongoCollection<*, *>.aggregate(pipeline: List): List { - return (this as MongoCollection).aggregate(pipeline) -} - -/** - * Insert a single object into the remote collection. - * - * @param document the object to serialize and insert into the remote collection. - * @return the `_id` value of the document insert in the collection deserialized to a [R]-instance. - */ -public suspend inline fun MongoCollection.insertOne(document: T): R { - isType>(this) - return decodeFromBsonValue(insertOne(encodeToBsonValue(document).asDocument())) -} - -/** - * Insert a single object into the remote collection. - * - * @param document the object to serialize and insert into the remote collection. - * @param T the type of object that should be serializer and inserted to the collection. - * @param R the type that the returned `_id` value should be deserialized into. - * @return the `_id` value of the document inserted in the collection deserialized to a [R]-instance. - */ -@JvmName("insertOneTyped") -public suspend inline fun MongoCollection<*, *>.insertOne(document: T): R { - return (this as MongoCollection).insertOne(document) -} - -/** - * Insert a list of object into the remote collection. - * - * @param documents the objects to serialize and insert into the remote collection. - * @return the `_id` values of the documents inserted in the collection deserialized to a [R]-instance. - */ -public suspend inline fun MongoCollection.insertMany( - documents: Collection, -): List { - isType>(this) - return decodeFromBsonValueList(insertMany(documents.map { encodeToBsonValue(it).asDocument() })) -} - -/** - * Insert a list of object into the remote collection. - * - * @param documents the objects to serialize and insert into the remote collection. - * @param T the type of object that should be serializer and inserted to the collection. - * @param R the type that the returned `_id` values should be deserialized into. - * @return the `_id` values of the documents inserted in the collection deserialized to a [R]-instance. - */ -@JvmName("insertManyTyped") -public suspend inline fun MongoCollection<*, *>.insertMany(documents: Collection): List { - return (this as MongoCollection).insertMany(documents) -} - -/** - * Delete a single object from the remote collection. - * - * @param filter a filter to specify the documents to delete. - * @return a boolean indicating if a document was deleted or not. - */ -public suspend fun MongoCollection<*, *>.deleteOne(filter: BsonDocument): Boolean { - isType>(this) - return deleteOne(filter) -} - -/** - * Delete multiple objects from the remote collection. - * - * @param filter a filter to specify the documents to delete. - * @return the number of documents that have been deleted. - */ -public suspend fun MongoCollection<*, *>.deleteMany(filter: BsonDocument): Long { - isType>(this) - return deleteMany(filter) -} - -/** - * Wrapper of results of an [updateOne] call. - * - * @param updated boolean indicating that a document was updated. - * @param upsertedId primary key of the new document if created. - */ -public data class UpdateOneResult(val updated: Boolean, val upsertedId: R?) - -/** - * Update or insert a single object in the remote collection. - * - * @param filter a filter to select the document to update. - * @param update a BsonDocument specifying the updates that should be applied to the document. - * @param upsert a boolean indicating if a new document should be inserted if the [filter] does not - * match any existing documents in the collection. - * @return the result of the `updateOne` operation. - */ -public suspend inline fun MongoCollection.updateOne( - filter: BsonDocument, - update: BsonDocument, - upsert: Boolean = false -): UpdateOneResult { - isType>(this) - return updateOne(filter, update, upsert).let { (updated, upsertedId) -> - UpdateOneResult(updated, upsertedId?.let { decodeFromBsonValue(it) }) - } -} - -/** - * Update or insert a single object in the remote collection. - * - * @param filter a filter to select the document to update. - * @param update a BsonDocument specifying the updates that should be applied to the document. - * @param upsert a boolean indicating if a new document should be inserted if the [filter] does not - * match any existing documents in the collection. - * @param R the type that the returned `_id` of a newly insert document should be deserialized into. - * @return the result of the `updateOne` operation. - */ -@JvmName("updateOneTyped") -public suspend inline fun MongoCollection<*, *>.updateOne( - filter: BsonDocument, - update: BsonDocument, - upsert: Boolean = false -): UpdateOneResult { - return (this as MongoCollection).updateOne(filter, update, upsert) -} - -/** - * Wrapper of results of an [updateMany] call. - * - * @param modifiedCount number of documents that was updated by the operation. - * @param upsertedId primary key of the new document if created. - */ -public data class UpdateManyResult(val modifiedCount: Long, val upsertedId: R?) - -/** - * Update multiple objects or insert a single new object in the remote collection. - * - * @param filter a filter to select the documents to update. - * @param update a BsonDocument specifying the updates that should be applied to the documents. - * @param upsert a boolean indicating if a new document should be inserted if the [filter] does not - * match any existing documents in the collection. - * @return the result of the `updateMany` operation. - */ -public suspend inline fun MongoCollection.updateMany( - filter: BsonDocument, - update: BsonDocument, - upsert: Boolean = false -): UpdateManyResult { - isType>(this) - return updateMany(filter, update, upsert).let { (updatedCount, upsertedId) -> - UpdateManyResult(updatedCount, upsertedId?.let { decodeFromBsonValue(it) }) - } -} - -/** - * Update multiple objects or insert a single new object in the remote collection. - * - * @param filter a filter to select the documents to update. - * @param update a BsonDocument specifying the updates that should be applied to the documents. - * @param upsert a boolean indicating if a new document should be inserted if the [filter] does not - * match any existing documents in the collection. - * @param R the type that the returned `_id` of a newly insert document should be deserialized into. - * @return the result of the `updateMany` operation. - */ -@JvmName("updateManyTyped") -public suspend inline fun MongoCollection<*, *>.updateMany( - filter: BsonDocument, - update: BsonDocument, - upsert: Boolean = false -): UpdateManyResult { - return (this as MongoCollection).updateMany(filter, update, upsert) -} - -/** - * Find and update or insert a single new object in the remote collection. - * - * @param filter a filter to select the documents to update. - * @param update a BsonDocument specifying the updates that should be applied to the documents. - * @param projection a BsonDocument that describes which fields that are returned from the server. - * If `null` then all fields will be returned. - * @param sort a document describing one or more fields used to sort documents before selecting the - * single document to return. If `null` then no sorting will be applied. - * @param upsert a boolean indicating if a new document should be inserted if the [filter] does not - * match any existing documents in the collection. - * @param returnNewDoc a boolean indicating whether to return the document before or after the update. - * @return the result of the remote `findOneAndUpdate` invocation deserialized into a [T]-instance. - */ -@Suppress("LongParameterList") -public suspend inline fun MongoCollection.findOneAndUpdate( - filter: BsonDocument, - update: BsonDocument, - projection: BsonDocument? = null, - sort: BsonDocument? = null, - upsert: Boolean = false, - returnNewDoc: Boolean = false, -): T? { - isType>(this) - return decodeFromBsonValue(findOneAndUpdate(filter, update, projection, sort, upsert, returnNewDoc)) -} - -/** - * Find and update or insert a single new object in the remote collection. - * - * @param filter a filter to select the documents to update. - * @param update a BsonDocument specifying the updates that should be applied to the documents. - * @param projection a BsonDocument that describes which fields that are returned from the server. - * If `null` then all fields will be returned. - * @param sort a document describing one or more fields used to sort documents before selecting the - * single document to return. If `null` then no sorting will be applied. - * @param upsert a boolean indicating if a new document should be inserted if the [filter] does not - * match any existing documents in the collection. - * @param returnNewDoc a boolean indicating whether to return the document before or after the update. - * @param T the type that the result of the remote `findOne` invocation should be deserialized into. - * @return the result of the remote `findOneAndUpdate` invocation deserialized into a [T]-instance. - */ -@Suppress("LongParameterList") -@JvmName("findAndUpdateTyped") -public suspend inline fun MongoCollection<*, *>.findOneAndUpdate( - filter: BsonDocument, - update: BsonDocument, - projection: BsonDocument? = null, - sort: BsonDocument? = null, - upsert: Boolean = false, - returnNewDoc: Boolean = false, -): T? { - return (this as MongoCollection).findOneAndUpdate(filter, update, projection, sort, upsert, returnNewDoc) -} - -/** - * Find and replace or insert a single new object in the remote collection. - * - * @param filter a filter to select the documents to update. - * @param document a BsonDocument specifying the updates that should be applied to the documents. - * @param projection a BsonDocument that describes which fields that are returned from the server. - * If `null` then all fields will be returned. - * @param sort a document describing one or more fields used to sort documents before selecting the - * single document to return. If `null` then no sorting will be applied. - * @param upsert a boolean indicating if a new document should be inserted if the [filter] does not - * match any existing documents in the collection. - * @param returnNewDoc a boolean indicating whether to return the document before or after the update. - * @return the result of the remote `findOneAndReplace` invocation deserialized into a [T]-instance. - */ -@Suppress("LongParameterList") -public suspend inline fun MongoCollection.findOneAndReplace( - filter: BsonDocument, - document: BsonDocument, - projection: BsonDocument? = null, - sort: BsonDocument? = null, - upsert: Boolean = false, - returnNewDoc: Boolean = false, -): T? { - isType>(this) - return decodeFromBsonValue(findOneAndReplace(filter, document, projection, sort, upsert, returnNewDoc)) -} - -/** - * Find and replace or insert a single new object in the remote collection. - * - * @param filter a filter to select the documents to update. - * @param document a BsonDocument specifying the updates that should be applied to the documents. - * @param projection a BsonDocument that describes which fields that are returned from the server. - * If `null` then all fields will be returned. - * @param sort a document describing one or more fields used to sort documents before selecting the - * single document to return. If `null` then no sorting will be applied. - * @param upsert a boolean indicating if a new document should be inserted if the [filter] does not - * match any existing documents in the collection. - * @param returnNewDoc a boolean indicating whether to return the document before or after the update. - * @param T the type that the result of the remote `findOne` invocation should be deserialized into. - * @return the result of the remote `findOneAndReplace` invocation deserialized into a [T]-instance. - */ -@Suppress("LongParameterList") -@JvmName("findAndReplaceTyped") -public suspend inline fun MongoCollection<*, *>.findOneAndReplace( - filter: BsonDocument, - update: BsonDocument, - projection: BsonDocument? = null, - sort: BsonDocument? = null, - upsert: Boolean = false, - returnNewDoc: Boolean = false, -): T? { - return (this as MongoCollection).findOneAndReplace(filter, update, projection, sort, upsert, returnNewDoc) -} - -/** - * Find and delete a single object in the remote collection. - * - * @param filter a filter to select the documents to update. - * @param projection a BsonDocument that describes which fields that are returned from the server. - * If `null` then all fields will be returned. - * @param sort a document describing one or more fields used to sort documents before selecting the - * single document to return. If `null` then no sorting will be applied. - * @return the result of the remote `findOneAndDelete` invocation deserialized into a [T]-instance. - */ -public suspend inline fun MongoCollection.findOneAndDelete( - filter: BsonDocument, - projection: BsonDocument? = null, - sort: BsonDocument? = null, -): T? { - isType>(this) - return decodeFromBsonValue(findOneAndDelete(filter, projection, sort)) -} - -/** - * Find and delete a single object in the remote collection. - * - * @param filter a filter to select the documents to update. - * @param projection a BsonDocument that describes which fields that are returned from the server. - * If `null` then all fields will be returned. - * @param sort a document describing one or more fields used to sort documents before selecting the - * single document to return. If `null` then no sorting will be applied. - * @param T the type that the result of the remote `findOne` invocation should be deserialized into. - * @return the result of the remote `findOneAndDelete` invocation deserialized into a [T]-instance. - */ -@JvmName("findAndDeleteTyped") -public suspend inline fun MongoCollection<*, *>.findOneAndDelete( - filter: BsonDocument, - projection: BsonDocument? = null, - sort: BsonDocument? = null, -): T? { - return (this as MongoCollection).findOneAndDelete(filter, projection, sort) -} diff --git a/packages/plugin-compiler/src/test/resources/sample/expected/01_AFTER.ValidateIrBeforeLowering.ir b/packages/plugin-compiler/src/test/resources/sample/expected/01_AFTER.ValidateIrBeforeLowering.ir index 3ca90d16e5..bc6822fbec 100644 --- a/packages/plugin-compiler/src/test/resources/sample/expected/01_AFTER.ValidateIrBeforeLowering.ir +++ b/packages/plugin-compiler/src/test/resources/sample/expected/01_AFTER.ValidateIrBeforeLowering.ir @@ -7852,635 +7852,1127 @@ MODULE_FRAGMENT name:
GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_className type:kotlin.String visibility:private' type=kotlin.String origin=null receiver: GET_VAR ': sample.input.Sample.Companion declared in sample.input.Sample.Companion.' type=sample.input.Sample.Companion origin=null PROPERTY name:io_realm_kotlin_fields visibility:public modality:FINAL [var] - FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map> visibility:private + FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> visibility:private EXPRESSION_BODY - CALL 'public final fun mapOf (vararg pairs: kotlin.Pair): kotlin.collections.Map declared in kotlin.collections.MapsKt' type=kotlin.collections.Map> origin=null + CALL 'public final fun mapOf (vararg pairs: kotlin.Pair): kotlin.collections.Map declared in kotlin.collections.MapsKt' type=kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> origin=null : kotlin.String - : kotlin.reflect.KProperty1 - pairs: VARARG type=kotlin.Array>> varargElementType=kotlin.collections.Map> - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.Pair, kotlin.reflect.KMutableProperty1> + pairs: VARARG type=kotlin.Array, kotlin.reflect.KMutableProperty1>>> varargElementType=kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String - : kotlin.reflect.KMutableProperty1 + : kotlin.Pair> first: CONST String type=kotlin.String value="id" - second: PROPERTY_REFERENCE 'public final id: kotlin.Long [var]' field=null getter='public final fun (): kotlin.Long declared in sample.input.Sample' setter='public final fun (: kotlin.Long): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Long modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass + second: PROPERTY_REFERENCE 'public final id: kotlin.Long [var]' field=null getter='public final fun (): kotlin.Long declared in sample.input.Sample' setter='public final fun (: kotlin.Long): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="stringField" - second: PROPERTY_REFERENCE 'public final stringField: kotlin.String? [var]' field=null getter='public final fun (): kotlin.String? declared in sample.input.Sample' setter='public final fun (: kotlin.String?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:String modality:FINAL visibility:public superTypes:[kotlin.Comparable; kotlin.CharSequence; java.io.Serializable]' type=kotlin.reflect.KClass + second: PROPERTY_REFERENCE 'public final stringField: kotlin.String? [var]' field=null getter='public final fun (): kotlin.String? declared in sample.input.Sample' setter='public final fun (: kotlin.String?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="byteField" - second: PROPERTY_REFERENCE 'public final byteField: kotlin.Byte? [var]' field=null getter='public final fun (): kotlin.Byte? declared in sample.input.Sample' setter='public final fun (: kotlin.Byte?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Byte modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass + second: PROPERTY_REFERENCE 'public final byteField: kotlin.Byte? [var]' field=null getter='public final fun (): kotlin.Byte? declared in sample.input.Sample' setter='public final fun (: kotlin.Byte?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="charField" - second: PROPERTY_REFERENCE 'public final charField: kotlin.Char? [var]' field=null getter='public final fun (): kotlin.Char? declared in sample.input.Sample' setter='public final fun (: kotlin.Char?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Char modality:FINAL visibility:public superTypes:[kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass + second: PROPERTY_REFERENCE 'public final charField: kotlin.Char? [var]' field=null getter='public final fun (): kotlin.Char? declared in sample.input.Sample' setter='public final fun (: kotlin.Char?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="shortField" - second: PROPERTY_REFERENCE 'public final shortField: kotlin.Short? [var]' field=null getter='public final fun (): kotlin.Short? declared in sample.input.Sample' setter='public final fun (: kotlin.Short?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Short modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass + second: PROPERTY_REFERENCE 'public final shortField: kotlin.Short? [var]' field=null getter='public final fun (): kotlin.Short? declared in sample.input.Sample' setter='public final fun (: kotlin.Short?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="intField" - second: PROPERTY_REFERENCE 'public final intField: kotlin.Int? [var]' field=null getter='public final fun (): kotlin.Int? declared in sample.input.Sample' setter='public final fun (: kotlin.Int?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Int modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass + second: PROPERTY_REFERENCE 'public final intField: kotlin.Int? [var]' field=null getter='public final fun (): kotlin.Int? declared in sample.input.Sample' setter='public final fun (: kotlin.Int?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="longField" - second: PROPERTY_REFERENCE 'public final longField: kotlin.Long? [var]' field=null getter='public final fun (): kotlin.Long? declared in sample.input.Sample' setter='public final fun (: kotlin.Long?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Long modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass + second: PROPERTY_REFERENCE 'public final longField: kotlin.Long? [var]' field=null getter='public final fun (): kotlin.Long? declared in sample.input.Sample' setter='public final fun (: kotlin.Long?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="booleanField" - second: PROPERTY_REFERENCE 'public final booleanField: kotlin.Boolean? [var]' field=null getter='public final fun (): kotlin.Boolean? declared in sample.input.Sample' setter='public final fun (: kotlin.Boolean?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Boolean modality:FINAL visibility:public superTypes:[kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass + second: PROPERTY_REFERENCE 'public final booleanField: kotlin.Boolean? [var]' field=null getter='public final fun (): kotlin.Boolean? declared in sample.input.Sample' setter='public final fun (: kotlin.Boolean?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="floatField" - second: PROPERTY_REFERENCE 'public final floatField: kotlin.Float? [var]' field=null getter='public final fun (): kotlin.Float? declared in sample.input.Sample' setter='public final fun (: kotlin.Float?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Float modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass + second: PROPERTY_REFERENCE 'public final floatField: kotlin.Float? [var]' field=null getter='public final fun (): kotlin.Float? declared in sample.input.Sample' setter='public final fun (: kotlin.Float?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="doubleField" - second: PROPERTY_REFERENCE 'public final doubleField: kotlin.Double? [var]' field=null getter='public final fun (): kotlin.Double? declared in sample.input.Sample' setter='public final fun (: kotlin.Double?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Double modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass + second: PROPERTY_REFERENCE 'public final doubleField: kotlin.Double? [var]' field=null getter='public final fun (): kotlin.Double? declared in sample.input.Sample' setter='public final fun (: kotlin.Double?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="decimal128Field" - second: PROPERTY_REFERENCE 'public final decimal128Field: org.mongodb.kbson.BsonDecimal128?{ org.mongodb.kbson.Decimal128Kt.Decimal128? } [var]' field=null getter='public final fun (): org.mongodb.kbson.BsonDecimal128?{ org.mongodb.kbson.Decimal128Kt.Decimal128? } declared in sample.input.Sample' setter='public final fun (: org.mongodb.kbson.BsonDecimal128?{ org.mongodb.kbson.Decimal128Kt.Decimal128? }): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:BsonDecimal128 modality:FINAL visibility:public superTypes:[org.mongodb.kbson.BsonValue]' type=kotlin.reflect.KClass + second: PROPERTY_REFERENCE 'public final decimal128Field: org.mongodb.kbson.BsonDecimal128?{ org.mongodb.kbson.Decimal128Kt.Decimal128? } [var]' field=null getter='public final fun (): org.mongodb.kbson.BsonDecimal128?{ org.mongodb.kbson.Decimal128Kt.Decimal128? } declared in sample.input.Sample' setter='public final fun (: org.mongodb.kbson.BsonDecimal128?{ org.mongodb.kbson.Decimal128Kt.Decimal128? }): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="timestampField" - second: PROPERTY_REFERENCE 'public final timestampField: io.realm.kotlin.types.RealmInstant? [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmInstant? declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmInstant?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmInstant modality:ABSTRACT visibility:public superTypes:[kotlin.Comparable]' type=kotlin.reflect.KClass + second: PROPERTY_REFERENCE 'public final timestampField: io.realm.kotlin.types.RealmInstant? [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmInstant? declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmInstant?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="objectIdField" - second: PROPERTY_REFERENCE 'public final objectIdField: io.realm.kotlin.types.ObjectId? [var]' field=null getter='public final fun (): io.realm.kotlin.types.ObjectId? declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.ObjectId?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:ObjectId modality:ABSTRACT visibility:public superTypes:[kotlin.Comparable]' type=kotlin.reflect.KClass + second: PROPERTY_REFERENCE 'public final objectIdField: io.realm.kotlin.types.ObjectId? [var]' field=null getter='public final fun (): io.realm.kotlin.types.ObjectId? declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.ObjectId?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="bsonObjectIdField" - second: PROPERTY_REFERENCE 'public final bsonObjectIdField: org.mongodb.kbson.BsonObjectId? [var]' field=null getter='public final fun (): org.mongodb.kbson.BsonObjectId? declared in sample.input.Sample' setter='public final fun (: org.mongodb.kbson.BsonObjectId?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:BsonObjectId modality:FINAL visibility:public superTypes:[org.mongodb.kbson.BsonValue; kotlin.Comparable]' type=kotlin.reflect.KClass + second: PROPERTY_REFERENCE 'public final bsonObjectIdField: org.mongodb.kbson.BsonObjectId? [var]' field=null getter='public final fun (): org.mongodb.kbson.BsonObjectId? declared in sample.input.Sample' setter='public final fun (: org.mongodb.kbson.BsonObjectId?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="uuidField" - second: PROPERTY_REFERENCE 'public final uuidField: io.realm.kotlin.types.RealmUUID? [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmUUID? declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmUUID?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmUUID modality:ABSTRACT visibility:public superTypes:[kotlin.Any]' type=kotlin.reflect.KClass + second: PROPERTY_REFERENCE 'public final uuidField: io.realm.kotlin.types.RealmUUID? [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmUUID? declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmUUID?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="byteArrayField" - second: PROPERTY_REFERENCE 'public final byteArrayField: kotlin.ByteArray? [var]' field=null getter='public final fun (): kotlin.ByteArray? declared in sample.input.Sample' setter='public final fun (: kotlin.ByteArray?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:ByteArray modality:FINAL visibility:public superTypes:[kotlin.Any; kotlin.Cloneable; java.io.Serializable]' type=kotlin.reflect.KClass + second: PROPERTY_REFERENCE 'public final byteArrayField: kotlin.ByteArray? [var]' field=null getter='public final fun (): kotlin.ByteArray? declared in sample.input.Sample' setter='public final fun (: kotlin.ByteArray?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="mutableRealmInt" - second: PROPERTY_REFERENCE 'public final mutableRealmInt: io.realm.kotlin.types.MutableRealmInt? [var]' field=null getter='public final fun (): io.realm.kotlin.types.MutableRealmInt? declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.MutableRealmInt?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:MutableRealmInt modality:ABSTRACT visibility:public superTypes:[kotlin.Comparable; kotlin.Number]' type=kotlin.reflect.KClass + second: PROPERTY_REFERENCE 'public final mutableRealmInt: io.realm.kotlin.types.MutableRealmInt? [var]' field=null getter='public final fun (): io.realm.kotlin.types.MutableRealmInt? declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.MutableRealmInt?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="child" - second: PROPERTY_REFERENCE 'public final child: sample.input.Child? [var]' field=null getter='public final fun (): sample.input.Child? declared in sample.input.Sample' setter='public final fun (: sample.input.Child?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS CLASS name:Child modality:OPEN visibility:public superTypes:[io.realm.kotlin.types.RealmObject; io.realm.kotlin.internal.RealmObjectInternal]' type=kotlin.reflect.KClass + second: PROPERTY_REFERENCE 'public final child: sample.input.Child? [var]' field=null getter='public final fun (): sample.input.Child? declared in sample.input.Sample' setter='public final fun (: sample.input.Child?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableRealmAny" - second: PROPERTY_REFERENCE 'public final nullableRealmAny: io.realm.kotlin.types.RealmAny? [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmAny? declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmAny?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmAny modality:ABSTRACT visibility:public superTypes:[kotlin.Any]' type=kotlin.reflect.KClass + second: PROPERTY_REFERENCE 'public final nullableRealmAny: io.realm.kotlin.types.RealmAny? [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmAny? declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmAny?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="stringListField" - second: PROPERTY_REFERENCE 'public final stringListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmList modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableList; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final stringListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="byteListField" - second: PROPERTY_REFERENCE 'public final byteListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmList modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableList; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final byteListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="charListField" - second: PROPERTY_REFERENCE 'public final charListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmList modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableList; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final charListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="shortListField" - second: PROPERTY_REFERENCE 'public final shortListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmList modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableList; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final shortListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="intListField" - second: PROPERTY_REFERENCE 'public final intListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmList modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableList; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final intListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="longListField" - second: PROPERTY_REFERENCE 'public final longListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmList modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableList; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final longListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="booleanListField" - second: PROPERTY_REFERENCE 'public final booleanListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmList modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableList; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final booleanListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="floatListField" - second: PROPERTY_REFERENCE 'public final floatListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmList modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableList; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final floatListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="doubleListField" - second: PROPERTY_REFERENCE 'public final doubleListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmList modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableList; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final doubleListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="timestampListField" - second: PROPERTY_REFERENCE 'public final timestampListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmList modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableList; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final timestampListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="objectIdListField" - second: PROPERTY_REFERENCE 'public final objectIdListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmList modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableList; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final objectIdListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="bsonObjectIdListField" - second: PROPERTY_REFERENCE 'public final bsonObjectIdListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmList modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableList; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final bsonObjectIdListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="uuidListField" - second: PROPERTY_REFERENCE 'public final uuidListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmList modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableList; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final uuidListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="binaryListField" - second: PROPERTY_REFERENCE 'public final binaryListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmList modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableList; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final binaryListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="decimal128ListField" - second: PROPERTY_REFERENCE 'public final decimal128ListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmList modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableList; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final decimal128ListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="objectListField" - second: PROPERTY_REFERENCE 'public final objectListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmList modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableList; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final objectListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="embeddedRealmObjectListField" - second: PROPERTY_REFERENCE 'public final embeddedRealmObjectListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmList modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableList; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final embeddedRealmObjectListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableStringListField" - second: PROPERTY_REFERENCE 'public final nullableStringListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmList modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableList; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableStringListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableByteListField" - second: PROPERTY_REFERENCE 'public final nullableByteListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmList modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableList; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableByteListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableCharListField" - second: PROPERTY_REFERENCE 'public final nullableCharListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmList modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableList; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableCharListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableShortListField" - second: PROPERTY_REFERENCE 'public final nullableShortListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmList modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableList; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableShortListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableIntListField" - second: PROPERTY_REFERENCE 'public final nullableIntListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmList modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableList; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableIntListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableLongListField" - second: PROPERTY_REFERENCE 'public final nullableLongListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmList modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableList; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableLongListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableBooleanListField" - second: PROPERTY_REFERENCE 'public final nullableBooleanListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmList modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableList; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableBooleanListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableFloatListField" - second: PROPERTY_REFERENCE 'public final nullableFloatListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmList modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableList; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableFloatListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableDoubleListField" - second: PROPERTY_REFERENCE 'public final nullableDoubleListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmList modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableList; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableDoubleListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableTimestampListField" - second: PROPERTY_REFERENCE 'public final nullableTimestampListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmList modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableList; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableTimestampListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableObjectIdListField" - second: PROPERTY_REFERENCE 'public final nullableObjectIdListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmList modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableList; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableObjectIdListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableBsonObjectIdListField" - second: PROPERTY_REFERENCE 'public final nullableBsonObjectIdListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmList modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableList; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableBsonObjectIdListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableUUIDListField" - second: PROPERTY_REFERENCE 'public final nullableUUIDListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmList modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableList; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableUUIDListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableBinaryListField" - second: PROPERTY_REFERENCE 'public final nullableBinaryListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmList modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableList; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableBinaryListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableDecimal128ListField" - second: PROPERTY_REFERENCE 'public final nullableDecimal128ListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmList modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableList; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableDecimal128ListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableRealmAnyListField" - second: PROPERTY_REFERENCE 'public final nullableRealmAnyListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmList modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableList; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableRealmAnyListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="stringSetField" - second: PROPERTY_REFERENCE 'public final stringSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmSet modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableSet; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final stringSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="byteSetField" - second: PROPERTY_REFERENCE 'public final byteSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmSet modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableSet; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final byteSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="charSetField" - second: PROPERTY_REFERENCE 'public final charSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmSet modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableSet; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final charSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="shortSetField" - second: PROPERTY_REFERENCE 'public final shortSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmSet modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableSet; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final shortSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="intSetField" - second: PROPERTY_REFERENCE 'public final intSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmSet modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableSet; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final intSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="longSetField" - second: PROPERTY_REFERENCE 'public final longSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmSet modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableSet; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final longSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="booleanSetField" - second: PROPERTY_REFERENCE 'public final booleanSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmSet modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableSet; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final booleanSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="floatSetField" - second: PROPERTY_REFERENCE 'public final floatSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmSet modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableSet; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final floatSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="doubleSetField" - second: PROPERTY_REFERENCE 'public final doubleSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmSet modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableSet; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final doubleSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="timestampSetField" - second: PROPERTY_REFERENCE 'public final timestampSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmSet modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableSet; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final timestampSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="objectIdSetField" - second: PROPERTY_REFERENCE 'public final objectIdSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmSet modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableSet; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final objectIdSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="bsonObjectIdSetField" - second: PROPERTY_REFERENCE 'public final bsonObjectIdSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmSet modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableSet; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final bsonObjectIdSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="uuidSetField" - second: PROPERTY_REFERENCE 'public final uuidSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmSet modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableSet; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final uuidSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="binarySetField" - second: PROPERTY_REFERENCE 'public final binarySetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmSet modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableSet; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final binarySetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="decimal128SetField" - second: PROPERTY_REFERENCE 'public final decimal128SetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmSet modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableSet; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final decimal128SetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="objectSetField" - second: PROPERTY_REFERENCE 'public final objectSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmSet modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableSet; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final objectSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableStringSetField" - second: PROPERTY_REFERENCE 'public final nullableStringSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmSet modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableSet; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableStringSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableByteSetField" - second: PROPERTY_REFERENCE 'public final nullableByteSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmSet modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableSet; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableByteSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableCharSetField" - second: PROPERTY_REFERENCE 'public final nullableCharSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmSet modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableSet; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableCharSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableShortSetField" - second: PROPERTY_REFERENCE 'public final nullableShortSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmSet modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableSet; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableShortSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableIntSetField" - second: PROPERTY_REFERENCE 'public final nullableIntSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmSet modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableSet; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableIntSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableLongSetField" - second: PROPERTY_REFERENCE 'public final nullableLongSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmSet modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableSet; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableLongSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableBooleanSetField" - second: PROPERTY_REFERENCE 'public final nullableBooleanSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmSet modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableSet; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableBooleanSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableFloatSetField" - second: PROPERTY_REFERENCE 'public final nullableFloatSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmSet modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableSet; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableFloatSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableDoubleSetField" - second: PROPERTY_REFERENCE 'public final nullableDoubleSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmSet modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableSet; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableDoubleSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableTimestampSetField" - second: PROPERTY_REFERENCE 'public final nullableTimestampSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmSet modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableSet; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableTimestampSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableObjectIdSetField" - second: PROPERTY_REFERENCE 'public final nullableObjectIdSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmSet modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableSet; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableObjectIdSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableBsonObjectIdSetField" - second: PROPERTY_REFERENCE 'public final nullableBsonObjectIdSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmSet modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableSet; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableBsonObjectIdSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableUUIDSetField" - second: PROPERTY_REFERENCE 'public final nullableUUIDSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmSet modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableSet; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableUUIDSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableBinarySetField" - second: PROPERTY_REFERENCE 'public final nullableBinarySetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmSet modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableSet; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableBinarySetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableDecimal128SetField" - second: PROPERTY_REFERENCE 'public final nullableDecimal128SetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmSet modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableSet; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableDecimal128SetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableRealmAnySetField" - second: PROPERTY_REFERENCE 'public final nullableRealmAnySetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmSet modality:ABSTRACT visibility:public superTypes:[kotlin.collections.MutableSet; io.realm.kotlin.Deleteable]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableRealmAnySetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="stringDictionaryField" - second: PROPERTY_REFERENCE 'public final stringDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmDictionary modality:ABSTRACT visibility:public superTypes:[io.realm.kotlin.types.RealmMap]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final stringDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="byteDictionaryField" - second: PROPERTY_REFERENCE 'public final byteDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmDictionary modality:ABSTRACT visibility:public superTypes:[io.realm.kotlin.types.RealmMap]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final byteDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="charDictionaryField" - second: PROPERTY_REFERENCE 'public final charDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmDictionary modality:ABSTRACT visibility:public superTypes:[io.realm.kotlin.types.RealmMap]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final charDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="shortDictionaryField" - second: PROPERTY_REFERENCE 'public final shortDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmDictionary modality:ABSTRACT visibility:public superTypes:[io.realm.kotlin.types.RealmMap]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final shortDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="intDictionaryField" - second: PROPERTY_REFERENCE 'public final intDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmDictionary modality:ABSTRACT visibility:public superTypes:[io.realm.kotlin.types.RealmMap]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final intDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="longDictionaryField" - second: PROPERTY_REFERENCE 'public final longDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmDictionary modality:ABSTRACT visibility:public superTypes:[io.realm.kotlin.types.RealmMap]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final longDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="booleanDictionaryField" - second: PROPERTY_REFERENCE 'public final booleanDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmDictionary modality:ABSTRACT visibility:public superTypes:[io.realm.kotlin.types.RealmMap]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final booleanDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="floatDictionaryField" - second: PROPERTY_REFERENCE 'public final floatDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmDictionary modality:ABSTRACT visibility:public superTypes:[io.realm.kotlin.types.RealmMap]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final floatDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="doubleDictionaryField" - second: PROPERTY_REFERENCE 'public final doubleDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmDictionary modality:ABSTRACT visibility:public superTypes:[io.realm.kotlin.types.RealmMap]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final doubleDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="timestampDictionaryField" - second: PROPERTY_REFERENCE 'public final timestampDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmDictionary modality:ABSTRACT visibility:public superTypes:[io.realm.kotlin.types.RealmMap]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final timestampDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="objectIdDictionaryField" - second: PROPERTY_REFERENCE 'public final objectIdDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmDictionary modality:ABSTRACT visibility:public superTypes:[io.realm.kotlin.types.RealmMap]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final objectIdDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="bsonObjectIdDictionaryField" - second: PROPERTY_REFERENCE 'public final bsonObjectIdDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmDictionary modality:ABSTRACT visibility:public superTypes:[io.realm.kotlin.types.RealmMap]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final bsonObjectIdDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="uuidDictionaryField" - second: PROPERTY_REFERENCE 'public final uuidDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmDictionary modality:ABSTRACT visibility:public superTypes:[io.realm.kotlin.types.RealmMap]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final uuidDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="binaryDictionaryField" - second: PROPERTY_REFERENCE 'public final binaryDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmDictionary modality:ABSTRACT visibility:public superTypes:[io.realm.kotlin.types.RealmMap]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final binaryDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="decimal128DictionaryField" - second: PROPERTY_REFERENCE 'public final decimal128DictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmDictionary modality:ABSTRACT visibility:public superTypes:[io.realm.kotlin.types.RealmMap]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final decimal128DictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableStringDictionaryField" - second: PROPERTY_REFERENCE 'public final nullableStringDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmDictionary modality:ABSTRACT visibility:public superTypes:[io.realm.kotlin.types.RealmMap]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableStringDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableByteDictionaryField" - second: PROPERTY_REFERENCE 'public final nullableByteDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmDictionary modality:ABSTRACT visibility:public superTypes:[io.realm.kotlin.types.RealmMap]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableByteDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableCharDictionaryField" - second: PROPERTY_REFERENCE 'public final nullableCharDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmDictionary modality:ABSTRACT visibility:public superTypes:[io.realm.kotlin.types.RealmMap]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableCharDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableShortDictionaryField" - second: PROPERTY_REFERENCE 'public final nullableShortDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmDictionary modality:ABSTRACT visibility:public superTypes:[io.realm.kotlin.types.RealmMap]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableShortDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableIntDictionaryField" - second: PROPERTY_REFERENCE 'public final nullableIntDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmDictionary modality:ABSTRACT visibility:public superTypes:[io.realm.kotlin.types.RealmMap]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableIntDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableLongDictionaryField" - second: PROPERTY_REFERENCE 'public final nullableLongDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmDictionary modality:ABSTRACT visibility:public superTypes:[io.realm.kotlin.types.RealmMap]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableLongDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableBooleanDictionaryField" - second: PROPERTY_REFERENCE 'public final nullableBooleanDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmDictionary modality:ABSTRACT visibility:public superTypes:[io.realm.kotlin.types.RealmMap]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableBooleanDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableFloatDictionaryField" - second: PROPERTY_REFERENCE 'public final nullableFloatDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmDictionary modality:ABSTRACT visibility:public superTypes:[io.realm.kotlin.types.RealmMap]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableFloatDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableDoubleDictionaryField" - second: PROPERTY_REFERENCE 'public final nullableDoubleDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmDictionary modality:ABSTRACT visibility:public superTypes:[io.realm.kotlin.types.RealmMap]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableDoubleDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableTimestampDictionaryField" - second: PROPERTY_REFERENCE 'public final nullableTimestampDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmDictionary modality:ABSTRACT visibility:public superTypes:[io.realm.kotlin.types.RealmMap]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableTimestampDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableObjectIdDictionaryField" - second: PROPERTY_REFERENCE 'public final nullableObjectIdDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmDictionary modality:ABSTRACT visibility:public superTypes:[io.realm.kotlin.types.RealmMap]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableObjectIdDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableBsonObjectIdDictionaryField" - second: PROPERTY_REFERENCE 'public final nullableBsonObjectIdDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmDictionary modality:ABSTRACT visibility:public superTypes:[io.realm.kotlin.types.RealmMap]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableBsonObjectIdDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableUUIDDictionaryField" - second: PROPERTY_REFERENCE 'public final nullableUUIDDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmDictionary modality:ABSTRACT visibility:public superTypes:[io.realm.kotlin.types.RealmMap]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableUUIDDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableBinaryDictionaryField" - second: PROPERTY_REFERENCE 'public final nullableBinaryDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmDictionary modality:ABSTRACT visibility:public superTypes:[io.realm.kotlin.types.RealmMap]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableBinaryDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableDecimal128DictionaryField" - second: PROPERTY_REFERENCE 'public final nullableDecimal128DictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmDictionary modality:ABSTRACT visibility:public superTypes:[io.realm.kotlin.types.RealmMap]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableDecimal128DictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableRealmAnyDictionaryField" - second: PROPERTY_REFERENCE 'public final nullableRealmAnyDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmDictionary modality:ABSTRACT visibility:public superTypes:[io.realm.kotlin.types.RealmMap]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableRealmAnyDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableObjectDictionaryField" - second: PROPERTY_REFERENCE 'public final nullableObjectDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmDictionary modality:ABSTRACT visibility:public superTypes:[io.realm.kotlin.types.RealmMap]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableObjectDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="nullableEmbeddedObjectDictionaryField" - second: PROPERTY_REFERENCE 'public final nullableEmbeddedObjectDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmDictionary modality:ABSTRACT visibility:public superTypes:[io.realm.kotlin.types.RealmMap]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final nullableEmbeddedObjectDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="linkingObjectsByList" - second: PROPERTY_REFERENCE 'public final linkingObjectsByList: io.realm.kotlin.query.RealmResults [delegated,val]' field=null getter='public final fun (): io.realm.kotlin.query.RealmResults declared in sample.input.Sample' setter=null type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:BacklinksDelegate modality:ABSTRACT visibility:public superTypes:[kotlin.Any]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final linkingObjectsByList: io.realm.kotlin.query.RealmResults [delegated,val]' field=null getter='public final fun (): io.realm.kotlin.query.RealmResults declared in sample.input.Sample' setter=null type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="linkingObjectsBySet" - second: PROPERTY_REFERENCE 'public final linkingObjectsBySet: io.realm.kotlin.query.RealmResults [delegated,val]' field=null getter='public final fun (): io.realm.kotlin.query.RealmResults declared in sample.input.Sample' setter=null type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:BacklinksDelegate modality:ABSTRACT visibility:public superTypes:[kotlin.Any]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final linkingObjectsBySet: io.realm.kotlin.query.RealmResults [delegated,val]' field=null getter='public final fun (): io.realm.kotlin.query.RealmResults declared in sample.input.Sample' setter=null type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="linkingObjectsByDictionary" - second: PROPERTY_REFERENCE 'public final linkingObjectsByDictionary: io.realm.kotlin.query.RealmResults [delegated,val]' field=null getter='public final fun (): io.realm.kotlin.query.RealmResults declared in sample.input.Sample' setter=null type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:BacklinksDelegate modality:ABSTRACT visibility:public superTypes:[kotlin.Any]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final linkingObjectsByDictionary: io.realm.kotlin.query.RealmResults [delegated,val]' field=null getter='public final fun (): io.realm.kotlin.query.RealmResults declared in sample.input.Sample' setter=null type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="persistedNameStringField" - second: PROPERTY_REFERENCE 'public final publicNameStringField: kotlin.String? [var]' field=null getter='public final fun (): kotlin.String? declared in sample.input.Sample' setter='public final fun (: kotlin.String?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KMutableProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:String modality:FINAL visibility:public superTypes:[kotlin.Comparable; kotlin.CharSequence; java.io.Serializable]' type=kotlin.reflect.KClass + second: PROPERTY_REFERENCE 'public final publicNameStringField: kotlin.String? [var]' field=null getter='public final fun (): kotlin.String? declared in sample.input.Sample' setter='public final fun (: kotlin.String?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="persistedNameChildField" - second: PROPERTY_REFERENCE 'public final publicNameChildField: sample.input.Child? [var]' field=null getter='public final fun (): sample.input.Child? declared in sample.input.Sample' setter='public final fun (: sample.input.Child?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS CLASS name:Child modality:OPEN visibility:public superTypes:[io.realm.kotlin.types.RealmObject; io.realm.kotlin.internal.RealmObjectInternal]' type=kotlin.reflect.KClass + second: PROPERTY_REFERENCE 'public final publicNameChildField: sample.input.Child? [var]' field=null getter='public final fun (): sample.input.Child? declared in sample.input.Sample' setter='public final fun (: sample.input.Child?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="persistedNameLinkingObjectsField" - second: PROPERTY_REFERENCE 'public final publicNameLinkingObjectsField: io.realm.kotlin.query.RealmResults [delegated,val]' field=null getter='public final fun (): io.realm.kotlin.query.RealmResults declared in sample.input.Sample' setter=null type=kotlin.reflect.KMutableProperty1 origin=null - FUN DEFAULT_PROPERTY_ACCESSOR name: visibility:public modality:FINAL <> ($this:sample.input.Sample.Companion) returnType:kotlin.collections.Map> + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:BacklinksDelegate modality:ABSTRACT visibility:public superTypes:[kotlin.Any]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final publicNameLinkingObjectsField: io.realm.kotlin.query.RealmResults [delegated,val]' field=null getter='public final fun (): io.realm.kotlin.query.RealmResults declared in sample.input.Sample' setter=null type=kotlin.reflect.KMutableProperty1 origin=null + FUN DEFAULT_PROPERTY_ACCESSOR name: visibility:public modality:FINAL <> ($this:sample.input.Sample.Companion) returnType:kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> correspondingProperty: PROPERTY name:io_realm_kotlin_fields visibility:public modality:FINAL [var] overridden: - public abstract fun (): kotlin.collections.Map> declared in io.realm.kotlin.internal.RealmObjectCompanion + public abstract fun (): kotlin.collections.Map, kotlin.reflect.KProperty1>> declared in io.realm.kotlin.internal.RealmObjectCompanion $this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:sample.input.Sample.Companion BLOCK_BODY - RETURN type=kotlin.Nothing from='public final fun (): kotlin.collections.Map> declared in sample.input.Sample.Companion' - GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map> visibility:private' type=kotlin.collections.Map> origin=null + RETURN type=kotlin.Nothing from='public final fun (): kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> declared in sample.input.Sample.Companion' + GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> visibility:private' type=kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> origin=null receiver: GET_VAR ': sample.input.Sample.Companion declared in sample.input.Sample.Companion.' type=sample.input.Sample.Companion origin=null PROPERTY name:io_realm_kotlin_primaryKey visibility:public modality:FINAL [var] FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_primaryKey type:kotlin.reflect.KMutableProperty1 visibility:private @@ -8755,35 +9247,47 @@ MODULE_FRAGMENT name:
GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_className type:kotlin.String visibility:private' type=kotlin.String origin=null receiver: GET_VAR ': sample.input.Child.Companion declared in sample.input.Child.Companion.' type=sample.input.Child.Companion origin=null PROPERTY name:io_realm_kotlin_fields visibility:public modality:FINAL [var] - FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map> visibility:private + FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> visibility:private EXPRESSION_BODY - CALL 'public final fun mapOf (vararg pairs: kotlin.Pair): kotlin.collections.Map declared in kotlin.collections.MapsKt' type=kotlin.collections.Map> origin=null + CALL 'public final fun mapOf (vararg pairs: kotlin.Pair): kotlin.collections.Map declared in kotlin.collections.MapsKt' type=kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> origin=null : kotlin.String - : kotlin.reflect.KProperty1 - pairs: VARARG type=kotlin.Array>> varargElementType=kotlin.collections.Map> - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.Pair, kotlin.reflect.KMutableProperty1> + pairs: VARARG type=kotlin.Array, kotlin.reflect.KMutableProperty1>>> varargElementType=kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String - : kotlin.reflect.KMutableProperty1 + : kotlin.Pair> first: CONST String type=kotlin.String value="name" - second: PROPERTY_REFERENCE 'public final name: kotlin.String? [var]' field=null getter='public final fun (): kotlin.String? declared in sample.input.Child' setter='public final fun (: kotlin.String?): kotlin.Unit declared in sample.input.Child' type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:String modality:FINAL visibility:public superTypes:[kotlin.Comparable; kotlin.CharSequence; java.io.Serializable]' type=kotlin.reflect.KClass + second: PROPERTY_REFERENCE 'public final name: kotlin.String? [var]' field=null getter='public final fun (): kotlin.String? declared in sample.input.Child' setter='public final fun (: kotlin.String?): kotlin.Unit declared in sample.input.Child' type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="linkingObjectsByObject" - second: PROPERTY_REFERENCE 'public final linkingObjectsByObject: io.realm.kotlin.query.RealmResults [delegated,val]' field=null getter='public final fun (): io.realm.kotlin.query.RealmResults declared in sample.input.Child' setter=null type=kotlin.reflect.KMutableProperty1 origin=null - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null - : kotlin.String - : kotlin.reflect.KProperty1 + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:BacklinksDelegate modality:ABSTRACT visibility:public superTypes:[kotlin.Any]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final linkingObjectsByObject: io.realm.kotlin.query.RealmResults [delegated,val]' field=null getter='public final fun (): io.realm.kotlin.query.RealmResults declared in sample.input.Child' setter=null type=kotlin.reflect.KMutableProperty1 origin=null + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.String + : kotlin.Pair> first: CONST String type=kotlin.String value="persistedNameParent" - second: PROPERTY_REFERENCE 'public final publicNameParent: io.realm.kotlin.query.RealmResults [delegated,val]' field=null getter='public final fun (): io.realm.kotlin.query.RealmResults declared in sample.input.Child' setter=null type=kotlin.reflect.KMutableProperty1 origin=null - FUN DEFAULT_PROPERTY_ACCESSOR name: visibility:public modality:FINAL <> ($this:sample.input.Child.Companion) returnType:kotlin.collections.Map> + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:BacklinksDelegate modality:ABSTRACT visibility:public superTypes:[kotlin.Any]' type=kotlin.reflect.KClass> + second: PROPERTY_REFERENCE 'public final publicNameParent: io.realm.kotlin.query.RealmResults [delegated,val]' field=null getter='public final fun (): io.realm.kotlin.query.RealmResults declared in sample.input.Child' setter=null type=kotlin.reflect.KMutableProperty1 origin=null + FUN DEFAULT_PROPERTY_ACCESSOR name: visibility:public modality:FINAL <> ($this:sample.input.Child.Companion) returnType:kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> correspondingProperty: PROPERTY name:io_realm_kotlin_fields visibility:public modality:FINAL [var] overridden: - public abstract fun (): kotlin.collections.Map> declared in io.realm.kotlin.internal.RealmObjectCompanion + public abstract fun (): kotlin.collections.Map, kotlin.reflect.KProperty1>> declared in io.realm.kotlin.internal.RealmObjectCompanion $this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:sample.input.Child.Companion BLOCK_BODY - RETURN type=kotlin.Nothing from='public final fun (): kotlin.collections.Map> declared in sample.input.Child.Companion' - GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map> visibility:private' type=kotlin.collections.Map> origin=null + RETURN type=kotlin.Nothing from='public final fun (): kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> declared in sample.input.Child.Companion' + GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> visibility:private' type=kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> origin=null receiver: GET_VAR ': sample.input.Child.Companion declared in sample.input.Child.Companion.' type=sample.input.Child.Companion origin=null PROPERTY name:io_realm_kotlin_primaryKey visibility:public modality:FINAL [var] FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_primaryKey type:kotlin.reflect.KMutableProperty1 visibility:private @@ -9002,25 +9506,29 @@ MODULE_FRAGMENT name:
GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_className type:kotlin.String visibility:private' type=kotlin.String origin=null receiver: GET_VAR ': sample.input.EmbeddedParent.Companion declared in sample.input.EmbeddedParent.Companion.' type=sample.input.EmbeddedParent.Companion origin=null PROPERTY name:io_realm_kotlin_fields visibility:public modality:FINAL [var] - FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map> visibility:private + FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> visibility:private EXPRESSION_BODY - CALL 'public final fun mapOf (vararg pairs: kotlin.Pair): kotlin.collections.Map declared in kotlin.collections.MapsKt' type=kotlin.collections.Map> origin=null + CALL 'public final fun mapOf (vararg pairs: kotlin.Pair): kotlin.collections.Map declared in kotlin.collections.MapsKt' type=kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> origin=null : kotlin.String - : kotlin.reflect.KProperty1 - pairs: VARARG type=kotlin.Array>> varargElementType=kotlin.collections.Map> - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.Pair, kotlin.reflect.KMutableProperty1> + pairs: VARARG type=kotlin.Array, kotlin.reflect.KMutableProperty1>>> varargElementType=kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String - : kotlin.reflect.KMutableProperty1 + : kotlin.Pair> first: CONST String type=kotlin.String value="child" - second: PROPERTY_REFERENCE 'public final child: sample.input.EmbeddedChild? [var]' field=null getter='public final fun (): sample.input.EmbeddedChild? declared in sample.input.EmbeddedParent' setter='public final fun (: sample.input.EmbeddedChild?): kotlin.Unit declared in sample.input.EmbeddedParent' type=kotlin.reflect.KMutableProperty1 origin=null - FUN DEFAULT_PROPERTY_ACCESSOR name: visibility:public modality:FINAL <> ($this:sample.input.EmbeddedParent.Companion) returnType:kotlin.collections.Map> + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS CLASS name:EmbeddedChild modality:OPEN visibility:public superTypes:[io.realm.kotlin.types.EmbeddedRealmObject; io.realm.kotlin.internal.RealmObjectInternal]' type=kotlin.reflect.KClass + second: PROPERTY_REFERENCE 'public final child: sample.input.EmbeddedChild? [var]' field=null getter='public final fun (): sample.input.EmbeddedChild? declared in sample.input.EmbeddedParent' setter='public final fun (: sample.input.EmbeddedChild?): kotlin.Unit declared in sample.input.EmbeddedParent' type=kotlin.reflect.KMutableProperty1 origin=null + FUN DEFAULT_PROPERTY_ACCESSOR name: visibility:public modality:FINAL <> ($this:sample.input.EmbeddedParent.Companion) returnType:kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> correspondingProperty: PROPERTY name:io_realm_kotlin_fields visibility:public modality:FINAL [var] overridden: - public abstract fun (): kotlin.collections.Map> declared in io.realm.kotlin.internal.RealmObjectCompanion + public abstract fun (): kotlin.collections.Map, kotlin.reflect.KProperty1>> declared in io.realm.kotlin.internal.RealmObjectCompanion $this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:sample.input.EmbeddedParent.Companion BLOCK_BODY - RETURN type=kotlin.Nothing from='public final fun (): kotlin.collections.Map> declared in sample.input.EmbeddedParent.Companion' - GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map> visibility:private' type=kotlin.collections.Map> origin=null + RETURN type=kotlin.Nothing from='public final fun (): kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> declared in sample.input.EmbeddedParent.Companion' + GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> visibility:private' type=kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> origin=null receiver: GET_VAR ': sample.input.EmbeddedParent.Companion declared in sample.input.EmbeddedParent.Companion.' type=sample.input.EmbeddedParent.Companion origin=null PROPERTY name:io_realm_kotlin_primaryKey visibility:public modality:FINAL [var] FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_primaryKey type:kotlin.reflect.KMutableProperty1 visibility:private @@ -9237,25 +9745,29 @@ MODULE_FRAGMENT name:
GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_className type:kotlin.String visibility:private' type=kotlin.String origin=null receiver: GET_VAR ': sample.input.EmbeddedChild.Companion declared in sample.input.EmbeddedChild.Companion.' type=sample.input.EmbeddedChild.Companion origin=null PROPERTY name:io_realm_kotlin_fields visibility:public modality:FINAL [var] - FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map> visibility:private + FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> visibility:private EXPRESSION_BODY - CALL 'public final fun mapOf (vararg pairs: kotlin.Pair): kotlin.collections.Map declared in kotlin.collections.MapsKt' type=kotlin.collections.Map> origin=null + CALL 'public final fun mapOf (vararg pairs: kotlin.Pair): kotlin.collections.Map declared in kotlin.collections.MapsKt' type=kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> origin=null : kotlin.String - : kotlin.reflect.KProperty1 - pairs: VARARG type=kotlin.Array>> varargElementType=kotlin.collections.Map> - CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.Pair, kotlin.reflect.KMutableProperty1> + pairs: VARARG type=kotlin.Array, kotlin.reflect.KMutableProperty1>>> varargElementType=kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> + CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String - : kotlin.reflect.KMutableProperty1 + : kotlin.Pair> first: CONST String type=kotlin.String value="name" - second: PROPERTY_REFERENCE 'public final name: kotlin.String? [var]' field=null getter='public final fun (): kotlin.String? declared in sample.input.EmbeddedChild' setter='public final fun (: kotlin.String?): kotlin.Unit declared in sample.input.EmbeddedChild' type=kotlin.reflect.KMutableProperty1 origin=null - FUN DEFAULT_PROPERTY_ACCESSOR name: visibility:public modality:FINAL <> ($this:sample.input.EmbeddedChild.Companion) returnType:kotlin.collections.Map> + second: CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null + : kotlin.reflect.KClass<*> + : kotlin.reflect.KMutableProperty1 + first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:String modality:FINAL visibility:public superTypes:[kotlin.Comparable; kotlin.CharSequence; java.io.Serializable]' type=kotlin.reflect.KClass + second: PROPERTY_REFERENCE 'public final name: kotlin.String? [var]' field=null getter='public final fun (): kotlin.String? declared in sample.input.EmbeddedChild' setter='public final fun (: kotlin.String?): kotlin.Unit declared in sample.input.EmbeddedChild' type=kotlin.reflect.KMutableProperty1 origin=null + FUN DEFAULT_PROPERTY_ACCESSOR name: visibility:public modality:FINAL <> ($this:sample.input.EmbeddedChild.Companion) returnType:kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> correspondingProperty: PROPERTY name:io_realm_kotlin_fields visibility:public modality:FINAL [var] overridden: - public abstract fun (): kotlin.collections.Map> declared in io.realm.kotlin.internal.RealmObjectCompanion + public abstract fun (): kotlin.collections.Map, kotlin.reflect.KProperty1>> declared in io.realm.kotlin.internal.RealmObjectCompanion $this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:sample.input.EmbeddedChild.Companion BLOCK_BODY - RETURN type=kotlin.Nothing from='public final fun (): kotlin.collections.Map> declared in sample.input.EmbeddedChild.Companion' - GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map> visibility:private' type=kotlin.collections.Map> origin=null + RETURN type=kotlin.Nothing from='public final fun (): kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> declared in sample.input.EmbeddedChild.Companion' + GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> visibility:private' type=kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> origin=null receiver: GET_VAR ': sample.input.EmbeddedChild.Companion declared in sample.input.EmbeddedChild.Companion.' type=sample.input.EmbeddedChild.Companion origin=null PROPERTY name:io_realm_kotlin_primaryKey visibility:public modality:FINAL [var] FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_primaryKey type:kotlin.reflect.KMutableProperty1 visibility:private diff --git a/packages/plugin-compiler/src/test/resources/schema/expected/01_AFTER.ValidateIrBeforeLowering.ir b/packages/plugin-compiler/src/test/resources/schema/expected/01_AFTER.ValidateIrBeforeLowering.ir index 72f689feb3..67ee9bed61 100644 --- a/packages/plugin-compiler/src/test/resources/schema/expected/01_AFTER.ValidateIrBeforeLowering.ir +++ b/packages/plugin-compiler/src/test/resources/schema/expected/01_AFTER.ValidateIrBeforeLowering.ir @@ -77,20 +77,20 @@ MODULE_FRAGMENT name:
GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_className type:kotlin.String visibility:private' type=kotlin.String origin=null receiver: GET_VAR ': schema.input.A.Companion declared in schema.input.A.Companion.' type=schema.input.A.Companion origin=null PROPERTY name:io_realm_kotlin_fields visibility:public modality:FINAL [var] - FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map> visibility:private + FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> visibility:private EXPRESSION_BODY - CALL 'public final fun mapOf (vararg pairs: kotlin.Pair): kotlin.collections.Map declared in kotlin.collections.MapsKt' type=kotlin.collections.Map> origin=null + CALL 'public final fun mapOf (vararg pairs: kotlin.Pair): kotlin.collections.Map declared in kotlin.collections.MapsKt' type=kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> origin=null : kotlin.String - : kotlin.reflect.KProperty1 - pairs: VARARG type=kotlin.Array>> varargElementType=kotlin.collections.Map> - FUN DEFAULT_PROPERTY_ACCESSOR name: visibility:public modality:FINAL <> ($this:schema.input.A.Companion) returnType:kotlin.collections.Map> + : kotlin.Pair, kotlin.reflect.KMutableProperty1> + pairs: VARARG type=kotlin.Array, kotlin.reflect.KMutableProperty1>>> varargElementType=kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> + FUN DEFAULT_PROPERTY_ACCESSOR name: visibility:public modality:FINAL <> ($this:schema.input.A.Companion) returnType:kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> correspondingProperty: PROPERTY name:io_realm_kotlin_fields visibility:public modality:FINAL [var] overridden: - public abstract fun (): kotlin.collections.Map> declared in io.realm.kotlin.internal.RealmObjectCompanion + public abstract fun (): kotlin.collections.Map, kotlin.reflect.KProperty1>> declared in io.realm.kotlin.internal.RealmObjectCompanion $this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:schema.input.A.Companion BLOCK_BODY - RETURN type=kotlin.Nothing from='public final fun (): kotlin.collections.Map> declared in schema.input.A.Companion' - GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map> visibility:private' type=kotlin.collections.Map> origin=null + RETURN type=kotlin.Nothing from='public final fun (): kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> declared in schema.input.A.Companion' + GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> visibility:private' type=kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> origin=null receiver: GET_VAR ': schema.input.A.Companion declared in schema.input.A.Companion.' type=schema.input.A.Companion origin=null PROPERTY name:io_realm_kotlin_primaryKey visibility:public modality:FINAL [var] FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_primaryKey type:kotlin.reflect.KMutableProperty1 visibility:private @@ -246,20 +246,20 @@ MODULE_FRAGMENT name:
GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_className type:kotlin.String visibility:private' type=kotlin.String origin=null receiver: GET_VAR ': schema.input.B.Companion declared in schema.input.B.Companion.' type=schema.input.B.Companion origin=null PROPERTY name:io_realm_kotlin_fields visibility:public modality:FINAL [var] - FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map> visibility:private + FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> visibility:private EXPRESSION_BODY - CALL 'public final fun mapOf (vararg pairs: kotlin.Pair): kotlin.collections.Map declared in kotlin.collections.MapsKt' type=kotlin.collections.Map> origin=null + CALL 'public final fun mapOf (vararg pairs: kotlin.Pair): kotlin.collections.Map declared in kotlin.collections.MapsKt' type=kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> origin=null : kotlin.String - : kotlin.reflect.KProperty1 - pairs: VARARG type=kotlin.Array>> varargElementType=kotlin.collections.Map> - FUN DEFAULT_PROPERTY_ACCESSOR name: visibility:public modality:FINAL <> ($this:schema.input.B.Companion) returnType:kotlin.collections.Map> + : kotlin.Pair, kotlin.reflect.KMutableProperty1> + pairs: VARARG type=kotlin.Array, kotlin.reflect.KMutableProperty1>>> varargElementType=kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> + FUN DEFAULT_PROPERTY_ACCESSOR name: visibility:public modality:FINAL <> ($this:schema.input.B.Companion) returnType:kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> correspondingProperty: PROPERTY name:io_realm_kotlin_fields visibility:public modality:FINAL [var] overridden: - public abstract fun (): kotlin.collections.Map> declared in io.realm.kotlin.internal.RealmObjectCompanion + public abstract fun (): kotlin.collections.Map, kotlin.reflect.KProperty1>> declared in io.realm.kotlin.internal.RealmObjectCompanion $this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:schema.input.B.Companion BLOCK_BODY - RETURN type=kotlin.Nothing from='public final fun (): kotlin.collections.Map> declared in schema.input.B.Companion' - GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map> visibility:private' type=kotlin.collections.Map> origin=null + RETURN type=kotlin.Nothing from='public final fun (): kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> declared in schema.input.B.Companion' + GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> visibility:private' type=kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> origin=null receiver: GET_VAR ': schema.input.B.Companion declared in schema.input.B.Companion.' type=schema.input.B.Companion origin=null PROPERTY name:io_realm_kotlin_primaryKey visibility:public modality:FINAL [var] FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_primaryKey type:kotlin.reflect.KMutableProperty1 visibility:private @@ -415,20 +415,20 @@ MODULE_FRAGMENT name:
GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_className type:kotlin.String visibility:private' type=kotlin.String origin=null receiver: GET_VAR ': schema.input.C.Companion declared in schema.input.C.Companion.' type=schema.input.C.Companion origin=null PROPERTY name:io_realm_kotlin_fields visibility:public modality:FINAL [var] - FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map> visibility:private + FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> visibility:private EXPRESSION_BODY - CALL 'public final fun mapOf (vararg pairs: kotlin.Pair): kotlin.collections.Map declared in kotlin.collections.MapsKt' type=kotlin.collections.Map> origin=null + CALL 'public final fun mapOf (vararg pairs: kotlin.Pair): kotlin.collections.Map declared in kotlin.collections.MapsKt' type=kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> origin=null : kotlin.String - : kotlin.reflect.KProperty1 - pairs: VARARG type=kotlin.Array>> varargElementType=kotlin.collections.Map> - FUN DEFAULT_PROPERTY_ACCESSOR name: visibility:public modality:FINAL <> ($this:schema.input.C.Companion) returnType:kotlin.collections.Map> + : kotlin.Pair, kotlin.reflect.KMutableProperty1> + pairs: VARARG type=kotlin.Array, kotlin.reflect.KMutableProperty1>>> varargElementType=kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> + FUN DEFAULT_PROPERTY_ACCESSOR name: visibility:public modality:FINAL <> ($this:schema.input.C.Companion) returnType:kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> correspondingProperty: PROPERTY name:io_realm_kotlin_fields visibility:public modality:FINAL [var] overridden: - public abstract fun (): kotlin.collections.Map> declared in io.realm.kotlin.internal.RealmObjectCompanion + public abstract fun (): kotlin.collections.Map, kotlin.reflect.KProperty1>> declared in io.realm.kotlin.internal.RealmObjectCompanion $this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:schema.input.C.Companion BLOCK_BODY - RETURN type=kotlin.Nothing from='public final fun (): kotlin.collections.Map> declared in schema.input.C.Companion' - GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map> visibility:private' type=kotlin.collections.Map> origin=null + RETURN type=kotlin.Nothing from='public final fun (): kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> declared in schema.input.C.Companion' + GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> visibility:private' type=kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> origin=null receiver: GET_VAR ': schema.input.C.Companion declared in schema.input.C.Companion.' type=schema.input.C.Companion origin=null PROPERTY name:io_realm_kotlin_primaryKey visibility:public modality:FINAL [var] FIELD PROPERTY_BACKING_FIELD name:io_realm_kotlin_primaryKey type:kotlin.reflect.KMutableProperty1 visibility:private diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserTests.kt index 84acf111bf..a955d372e0 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserTests.kt @@ -31,7 +31,7 @@ import io.realm.kotlin.mongodb.exceptions.ServiceException import io.realm.kotlin.mongodb.ext.customData import io.realm.kotlin.mongodb.ext.customDataAsBsonDocument import io.realm.kotlin.mongodb.mongo.MongoClient -import io.realm.kotlin.mongodb.mongo.insertOne +import io.realm.kotlin.mongodb.ext.insertOne import io.realm.kotlin.mongodb.sync.SyncConfiguration import io.realm.kotlin.test.mongodb.TestApp import io.realm.kotlin.test.mongodb.asTestApp @@ -784,6 +784,19 @@ class UserTests { } } + @Test + @OptIn(ExperimentalKBsonSerializerApi::class) + fun mongoClient_throwsOnLoggedOutUser() = runBlocking { + val (email, password) = randomEmail() to "123456" + val user = runBlocking { + createUserAndLogin(email, password) + } + user.logOut() + assertFailsWithMessage("Cannot obtain a MongoClient from a logged out user") { + user.mongoClient("UNKNOWN_SERVICE") + } + } + private fun updatecustomDataAsBsonDocument(user: User, data: BsonDocument) { // Name of collection and property used for storing custom user data. Must match server config.json val COLLECTION_NAME = "UserData" diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTests.kt index 376624cd98..59024d4c3b 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTests.kt @@ -18,13 +18,10 @@ package io.realm.kotlin.test.mongodb.common.mongo import io.realm.kotlin.entities.sync.CollectionDataType import io.realm.kotlin.internal.platform.runBlocking -import io.realm.kotlin.log.LogLevel -import io.realm.kotlin.log.RealmLog -import io.realm.kotlin.mongodb.AppConfiguration import io.realm.kotlin.mongodb.exceptions.ServiceException import io.realm.kotlin.mongodb.mongo.MongoClient -import io.realm.kotlin.mongodb.mongo.collection -import io.realm.kotlin.mongodb.mongo.insertOne +import io.realm.kotlin.mongodb.ext.collection +import io.realm.kotlin.mongodb.ext.insertOne import io.realm.kotlin.test.mongodb.TEST_APP_FLEX import io.realm.kotlin.test.mongodb.TestApp import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage @@ -51,9 +48,6 @@ class MongoClientTests { app = TestApp( this::class.simpleName, appName = TEST_APP_FLEX, - builder = { builder: AppConfiguration.Builder -> - builder.httpLogObfuscator(null) - } ) val user = app.createUserAndLogin() client = user.mongoClient(TEST_SERVICE_NAME) @@ -61,7 +55,6 @@ class MongoClientTests { @AfterTest fun teadDown() { - RealmLog.level = LogLevel.WARN if (this::app.isInitialized) { app.close() } diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt index 9ed785f425..d481dab693 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt @@ -24,28 +24,25 @@ import io.realm.kotlin.entities.sync.EmbeddedChildCollectionDataType import io.realm.kotlin.entities.sync.ParentCollectionDataType import io.realm.kotlin.ext.query import io.realm.kotlin.internal.platform.runBlocking -import io.realm.kotlin.log.LogLevel -import io.realm.kotlin.log.RealmLog -import io.realm.kotlin.mongodb.AppConfiguration import io.realm.kotlin.mongodb.User import io.realm.kotlin.mongodb.exceptions.ServiceException import io.realm.kotlin.mongodb.mongo.MongoClient import io.realm.kotlin.mongodb.mongo.MongoCollection import io.realm.kotlin.mongodb.mongo.MongoDatabase -import io.realm.kotlin.mongodb.mongo.aggregate -import io.realm.kotlin.mongodb.mongo.collection -import io.realm.kotlin.mongodb.mongo.count -import io.realm.kotlin.mongodb.mongo.deleteMany -import io.realm.kotlin.mongodb.mongo.deleteOne -import io.realm.kotlin.mongodb.mongo.find -import io.realm.kotlin.mongodb.mongo.findOne -import io.realm.kotlin.mongodb.mongo.findOneAndDelete -import io.realm.kotlin.mongodb.mongo.findOneAndReplace -import io.realm.kotlin.mongodb.mongo.findOneAndUpdate -import io.realm.kotlin.mongodb.mongo.insertMany -import io.realm.kotlin.mongodb.mongo.insertOne -import io.realm.kotlin.mongodb.mongo.updateMany -import io.realm.kotlin.mongodb.mongo.updateOne +import io.realm.kotlin.mongodb.ext.aggregate +import io.realm.kotlin.mongodb.ext.collection +import io.realm.kotlin.mongodb.ext.count +import io.realm.kotlin.mongodb.ext.deleteMany +import io.realm.kotlin.mongodb.ext.deleteOne +import io.realm.kotlin.mongodb.ext.find +import io.realm.kotlin.mongodb.ext.findOne +import io.realm.kotlin.mongodb.ext.findOneAndDelete +import io.realm.kotlin.mongodb.ext.findOneAndReplace +import io.realm.kotlin.mongodb.ext.findOneAndUpdate +import io.realm.kotlin.mongodb.ext.insertMany +import io.realm.kotlin.mongodb.ext.insertOne +import io.realm.kotlin.mongodb.ext.updateMany +import io.realm.kotlin.mongodb.ext.updateOne import io.realm.kotlin.mongodb.sync.SyncConfiguration import io.realm.kotlin.mongodb.syncSession import io.realm.kotlin.notifications.ResultsChange @@ -88,6 +85,10 @@ import kotlin.test.assertNull import kotlin.test.assertTrue import kotlin.time.Duration.Companion.seconds +/** + * Test class that verifies MongoDB Client API interactions through the + * `MongoClient.database(...).collection(...)`-API. + */ class MongoCollectionFromDatabaseTests : MongoCollectionTests() { lateinit var database: MongoDatabase @@ -112,6 +113,10 @@ class MongoCollectionFromDatabaseTests : MongoCollectionTests() { } } +/** + * Test class that verifies MongoDB Client API interactions through the + * `MongoClient.collection(...)`-API. + */ class MongoCollectionFromClientTests : MongoCollectionTests() { @BeforeTest @@ -149,9 +154,6 @@ abstract sealed class MongoCollectionTests { app = TestApp( this::class.simpleName, TEST_APP_FLEX, - builder = { builder: AppConfiguration.Builder -> - builder.httpLogObfuscator(null) - } ) app.asTestApp.run { @@ -168,7 +170,6 @@ abstract sealed class MongoCollectionTests { @AfterTest fun teadDown() { - RealmLog.level = LogLevel.WARN app.asTestApp.run { runBlocking { COLLECTION_SCHEMAS.forEach { @@ -314,7 +315,6 @@ abstract sealed class MongoCollectionTests { @Test open fun findOne_links() = runBlocking { - RealmLog.level = LogLevel.ALL Realm.open( SyncConfiguration.Builder(user, COLLECTION_SCHEMAS) .initialSubscriptions { @@ -342,7 +342,6 @@ abstract sealed class MongoCollectionTests { @Test open fun findOne_embeddedObjects() = runBlocking { // Empty collections - RealmLog.level = RealmLog.level assertNull(collection.findOne()) val parentCollection = collection() @@ -578,7 +577,6 @@ abstract sealed class MongoCollectionTests { @Test fun insertOne_embeddedObjects() = runBlocking { // Empty collections - RealmLog.level = RealmLog.level assertNull(collection.findOne()) val parentCollection = collection() @@ -621,7 +619,6 @@ abstract sealed class MongoCollectionTests { @Test open fun insertOne_throwsOnMissingRequiredFields() = runBlocking { - RealmLog.level = LogLevel.ALL assertFailsWithMessage("insert not permitted") { collection.insertOne(BsonDocument("_id", ObjectId())) } @@ -687,7 +684,6 @@ abstract sealed class MongoCollectionTests { @Test open fun insertMany_throwsOnMissingRequiredFields() = runBlocking { - RealmLog.level = LogLevel.ALL assertFailsWithMessage("insert not permitted") { collection.insertMany(listOf(BsonDocument())) } @@ -1229,6 +1225,14 @@ abstract sealed class MongoCollectionTests { collection.findOneAndDelete(BsonDocument("\$who", 1)) } } + + @Test + fun throwsOnLoggedOutUser() = runBlocking { + user.logOut() + assertFailsWithMessage("unauthorized") { + collection.findOne() + } + } } // Helper method to be able to differentiate collection creation across test classes diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoDatabaseTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoDatabaseTests.kt index 0832e4fff0..abbeaf3220 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoDatabaseTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoDatabaseTests.kt @@ -18,12 +18,10 @@ package io.realm.kotlin.test.mongodb.common.mongo import io.realm.kotlin.entities.sync.CollectionDataType import io.realm.kotlin.internal.platform.runBlocking -import io.realm.kotlin.log.LogLevel -import io.realm.kotlin.log.RealmLog import io.realm.kotlin.mongodb.AppConfiguration import io.realm.kotlin.mongodb.mongo.MongoClient import io.realm.kotlin.mongodb.mongo.MongoDatabase -import io.realm.kotlin.mongodb.mongo.insertOne +import io.realm.kotlin.mongodb.ext.insertOne import io.realm.kotlin.test.mongodb.TEST_APP_FLEX import io.realm.kotlin.test.mongodb.TestApp import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage @@ -53,9 +51,6 @@ class MongoDatabaseTests { app = TestApp( this::class.simpleName, appName = TEST_APP_FLEX, - builder = { builder: AppConfiguration.Builder -> - builder.httpLogObfuscator(null) - } ) val user = app.createUserAndLogin() client = user.mongoClient(TEST_SERVICE_NAME) @@ -65,7 +60,6 @@ class MongoDatabaseTests { @AfterTest fun teadDown() { - RealmLog.level = LogLevel.WARN if (this::app.isInitialized) { app.close() } From 63a300e11ef970badfe6b45a49add786fbcaaa29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Fri, 5 Apr 2024 15:31:32 +0200 Subject: [PATCH 23/36] Typed link serialization --- .../realm/kotlin/internal/RealmInstantImpl.kt | 8 +- .../io/realm/kotlin/internal/RealmUUIDImpl.kt | 5 + .../kotlin/serializers/RealmKSerializers.kt | 9 +- .../kotlin/io/realm/kotlin/mongodb/User.kt | 8 +- .../mongodb/internal/MongoDBSerializer.kt | 279 ++++++++++++++++++ .../realm/kotlin/mongodb/mongo/MongoClient.kt | 72 ++++- .../kotlin/mongodb/mongo/MongoCollection.kt | 7 +- .../kotlin/mongodb/mongo/MongoDBSerializer.kt | 208 ------------- .../kotlin/mongodb/mongo/MongoDatabase.kt | 3 +- .../entities/sync/MongoDBClientSchemas.kt | 11 +- .../mongodb/common/mongo/MongoClientTests.kt | 4 +- .../common/mongo/MongoCollectionTests.kt | 162 +++++++++- 12 files changed, 542 insertions(+), 234 deletions(-) create mode 100644 packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDBSerializer.kt delete mode 100644 packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoDBSerializer.kt diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmInstantImpl.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmInstantImpl.kt index 8f0c714fac..99763d73e9 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmInstantImpl.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmInstantImpl.kt @@ -2,6 +2,8 @@ package io.realm.kotlin.internal import io.realm.kotlin.internal.interop.Timestamp import io.realm.kotlin.types.RealmInstant +import org.mongodb.kbson.BsonBinary +import org.mongodb.kbson.BsonBinarySubType import org.mongodb.kbson.BsonDateTime import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds @@ -36,7 +38,7 @@ public fun RealmInstant.toDuration(): Duration { return epochSeconds.seconds + nanosecondsOfSecond.nanoseconds } -internal fun Duration.toRealmInstant(): RealmInstant { +public fun Duration.toRealmInstant(): RealmInstant { val seconds: Long = this.inWholeSeconds val nanos: Duration = (this - seconds.seconds) return RealmInstant.from(seconds, nanos.inWholeNanoseconds.toInt()) @@ -45,4 +47,6 @@ internal fun Duration.toRealmInstant(): RealmInstant { internal fun RealmInstant.restrictToMillisPrecision() = toDuration().inWholeMilliseconds.milliseconds.toRealmInstant() -internal fun RealmInstant.asBsonDateTime() = BsonDateTime(toDuration().inWholeMilliseconds) +public inline fun RealmInstant.asBsonDateTime(): BsonDateTime = BsonDateTime(toDuration().inWholeMilliseconds) +public inline fun BsonDateTime.asRealmInstant(): RealmInstant = value.milliseconds.toRealmInstant() + diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmUUIDImpl.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmUUIDImpl.kt index 6d366fde35..17c630915b 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmUUIDImpl.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmUUIDImpl.kt @@ -21,6 +21,8 @@ import io.realm.kotlin.internal.util.HEX_PATTERN import io.realm.kotlin.internal.util.parseHex import io.realm.kotlin.internal.util.toHexString import io.realm.kotlin.types.RealmUUID +import org.mongodb.kbson.BsonBinary +import org.mongodb.kbson.BsonBinarySubType import kotlin.experimental.and import kotlin.experimental.or @@ -101,3 +103,6 @@ public class RealmUUIDImpl : RealmUUID { } } } + +public inline fun RealmUUID.asBsonBinary(): BsonBinary = BsonBinary(BsonBinarySubType.UUID_STANDARD, bytes) +public inline fun BsonBinary.asRealmUUID(): RealmUUID = RealmUUID.from(data) diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/serializers/RealmKSerializers.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/serializers/RealmKSerializers.kt index 4d0b19277e..a0d1ccb8b5 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/serializers/RealmKSerializers.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/serializers/RealmKSerializers.kt @@ -19,8 +19,8 @@ import io.realm.kotlin.ext.asRealmObject import io.realm.kotlin.ext.toRealmDictionary import io.realm.kotlin.ext.toRealmList import io.realm.kotlin.ext.toRealmSet -import io.realm.kotlin.internal.toDuration -import io.realm.kotlin.internal.toRealmInstant +import io.realm.kotlin.internal.asBsonDateTime +import io.realm.kotlin.internal.asRealmInstant import io.realm.kotlin.types.MutableRealmInt import io.realm.kotlin.types.RealmAny import io.realm.kotlin.types.RealmAny.Type @@ -45,7 +45,6 @@ import org.mongodb.kbson.BsonBinarySubType import org.mongodb.kbson.BsonDateTime import org.mongodb.kbson.BsonObjectId import org.mongodb.kbson.Decimal128 -import kotlin.time.Duration.Companion.milliseconds /** * KSerializer implementation for [RealmList]. Serialization is done as a generic list structure, @@ -249,12 +248,12 @@ public object RealmInstantKSerializer : KSerializer { override val descriptor: SerialDescriptor = serializer.descriptor override fun deserialize(decoder: Decoder): RealmInstant = - decoder.decodeSerializableValue(serializer).value.milliseconds.toRealmInstant() + decoder.decodeSerializableValue(serializer).asRealmInstant() override fun serialize(encoder: Encoder, value: RealmInstant) { encoder.encodeSerializableValue( serializer = serializer, - value = BsonDateTime(value.toDuration().inWholeMilliseconds) + value = value.asBsonDateTime() ) } } diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/User.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/User.kt index 2a695f2ca9..8cb6a1fba6 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/User.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/User.kt @@ -208,12 +208,16 @@ public interface User { * Get a [MongoClient] for accessing documents from App Service's _Data Source_. * * Serialization to and from EJSON is performed with [KBSON](https://github.com/mongodb/kbson) - * and requires to opt-in to the experimental [ExperimentalKBsonSerializerApi]-feature. + * that supports the [Kotlin Serialization framework](https://github.com/Kotlin/kotlinx.serialization) + * and handles serialization to and from classes marked with [Serializable]. Serialization of + * realm objects and links have some caveats and requires special configuration. For full + * details see [MongoClient]. * * @param serviceName the name of the data service. * @param eJson the EJson serializer that the [MongoClient] should use to convert objects and * primary keys with. Will default to the apps [EJson] instance configured with - * [AppConfiguration.Builder.ejson]. + * [AppConfiguration.Builder.ejson]. For details on configuration of serialization see + * [MongoClient]. * throws IllegalStateException if trying to obtain a [MongoClient] from a logged out [User]. */ @ExperimentalKBsonSerializerApi diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDBSerializer.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDBSerializer.kt new file mode 100644 index 0000000000..7ee739137e --- /dev/null +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDBSerializer.kt @@ -0,0 +1,279 @@ +/* + * Copyright 2024 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.realm.kotlin.mongodb.internal + +import io.realm.kotlin.internal.RealmObjectCompanion +import io.realm.kotlin.internal.asBsonBinary +import io.realm.kotlin.internal.asBsonDateTime +import io.realm.kotlin.internal.asRealmInstant +import io.realm.kotlin.internal.asRealmUUID +import io.realm.kotlin.internal.platform.realmObjectCompanionOrThrow +import io.realm.kotlin.internal.realmObjectCompanionOrNull +import io.realm.kotlin.internal.util.Validation +import io.realm.kotlin.schema.RealmProperty +import io.realm.kotlin.schema.RealmStorageType +import io.realm.kotlin.types.BaseRealmObject +import io.realm.kotlin.types.RealmAny +import io.realm.kotlin.types.RealmInstant +import io.realm.kotlin.types.RealmObject +import io.realm.kotlin.types.RealmUUID +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerializationException +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import org.mongodb.kbson.BsonBinary +import org.mongodb.kbson.BsonBoolean +import org.mongodb.kbson.BsonDocument +import org.mongodb.kbson.BsonDouble +import org.mongodb.kbson.BsonInt64 +import org.mongodb.kbson.BsonNull +import org.mongodb.kbson.BsonString +import org.mongodb.kbson.BsonType +import org.mongodb.kbson.BsonValue +import org.mongodb.kbson.Decimal128 +import org.mongodb.kbson.ObjectId +import kotlin.reflect.KClass +import kotlin.reflect.KMutableProperty1 +import kotlin.reflect.KProperty1 + +/** + * Serializer that will encode and decode realm objects to and from EJSON sent and received + * from the MongoClient APIs according to the schema definition of the realm objects. + * + * Serialization of links will only include primary key of the target and deserialization of + * responses from MongoClient will create target link instances where only the primary key is set + * (and all the other properties of the object will have default values). + * + * The target types of links in mixed fields cannot be derived from the schema definition of the + * realm objects. To be able to deserialize and create the correct instance, the serializer needs to + * know of all potential target types. + */ +public open class MongoDBSerializer internal constructor ( + clazz: KClass, + internal val schema: Map = emptyMap() +) : KSerializer { + override val descriptor: SerialDescriptor = BsonDocument.serializer().descriptor + private val companion = realmObjectCompanionOrThrow(clazz) + + override fun deserialize(decoder: Decoder): BaseRealmObject { + return bsonToObject(companion, decoder.decodeSerializableValue(BsonDocument.serializer())) + } + + private fun bsonToObject(companion: RealmObjectCompanion, bsonDocument: BsonDocument): BaseRealmObject { + val instance = companion.io_realm_kotlin_newInstance() as BaseRealmObject + val fields: Map, KProperty1>> = + companion.io_realm_kotlin_fields + val schema = companion.io_realm_kotlin_schema() + bsonDocument.keys.forEach { key -> + val (kClass, accessor) = fields[key] + ?: throw SerializationException("Unknown field '$key' for type ${companion.io_realm_kotlin_className}") + val type = schema[key]?.type + ?: throw SerializationException("Unknown field '$key' for type ${companion.io_realm_kotlin_className}") + val value = bsonValueToStorageType(type.storageType, kClass, bsonDocument[key]) + (accessor as KMutableProperty1).set(instance, value) + } + return instance + } + + override fun serialize(encoder: Encoder, value: BaseRealmObject) { + encoder.encodeSerializableValue(BsonDocument.serializer(), objectToBson(companion, value)) + } + + private fun objectToBson( + companion: RealmObjectCompanion, + realmObject: BaseRealmObject, + ): BsonDocument { + val fields: Map, KProperty1>> = + companion.io_realm_kotlin_fields + val schema = companion.io_realm_kotlin_schema() + val document = BsonDocument() + fields.forEach { (fieldName, fieldDetails) -> + val (kClass, accessor) = fieldDetails + val type = + schema[fieldName]?.type ?: Validation.sdkError("Schema does not contain property $fieldName") + storageTypeToBsonValue(type.storageType, kClass, accessor.get(realmObject))?.let { + document[fieldName] = it + } + } + return document + } + + private fun storageTypeToBsonValue( + storageType: RealmStorageType, + clazz: KClass<*>, + value: Any?, + ): BsonValue? { + if (value == null) return BsonNull + return when (storageType) { + RealmStorageType.BOOL -> BsonBoolean(value as Boolean) + RealmStorageType.INT -> BsonInt64(value as Long) + RealmStorageType.STRING -> BsonString(value as String) + RealmStorageType.BINARY -> BsonBinary(value as ByteArray) + RealmStorageType.OBJECT -> { + @Suppress("UNCHECKED_CAST") + val targetCompanion = realmObjectCompanionOrThrow(clazz as KClass) + @Suppress("UNCHECKED_CAST") + val primaryKeyProperty: KMutableProperty1? = + targetCompanion.io_realm_kotlin_primaryKey as KMutableProperty1? + when (primaryKeyProperty) { + // Embedded objects does not have a primary key, so serialize to full documents + null -> objectToBson(targetCompanion, value as BaseRealmObject) + else -> { + val targetStorageType = + targetCompanion.io_realm_kotlin_schema().primaryKey!!.type.storageType + val primaryKey = primaryKeyProperty.get(value as BaseRealmObject) + storageTypeToBsonValue(targetStorageType, clazz, primaryKey) + } + } + } + + RealmStorageType.FLOAT -> BsonDouble((value as Float).toDouble()) + RealmStorageType.DOUBLE -> BsonDouble(value as Double) + RealmStorageType.DECIMAL128 -> value as Decimal128 + RealmStorageType.TIMESTAMP -> (value as RealmInstant).asBsonDateTime() + RealmStorageType.OBJECT_ID -> (value as ObjectId) + RealmStorageType.UUID -> (value as RealmUUID).asBsonBinary() + RealmStorageType.ANY -> { + val realmAny = value as RealmAny + when (realmAny.type) { + RealmAny.Type.BOOL -> BsonBoolean(realmAny.asBoolean()) + RealmAny.Type.INT -> BsonInt64(realmAny.asLong()) + RealmAny.Type.STRING -> BsonString(realmAny.asString()) + RealmAny.Type.BINARY -> BsonBinary(realmAny.asByteArray()) + RealmAny.Type.TIMESTAMP -> realmAny.asRealmInstant().asBsonDateTime() + RealmAny.Type.FLOAT -> BsonDouble(realmAny.asFloat().toDouble()) + RealmAny.Type.DOUBLE -> BsonDouble(realmAny.asDouble()) + RealmAny.Type.DECIMAL128 -> realmAny.asDecimal128() + RealmAny.Type.OBJECT_ID -> realmAny.asObjectId() + RealmAny.Type.UUID -> realmAny.asRealmUUID().asBsonBinary() + RealmAny.Type.OBJECT -> { + // Objects in RealmAny cannot be EmbeddedObjects + val target = realmAny.asRealmObject(BaseRealmObject::class) + val targetCompanion = realmObjectCompanionOrThrow(target::class) + val primaryKeySchemaProperty: RealmProperty = targetCompanion.io_realm_kotlin_schema().primaryKey ?: throw SerializationException( + "Cannot serialize class without primary key: '${targetCompanion.io_realm_kotlin_className}'" + ) + val (primaryKeyType, primaryKeyAccessor) = targetCompanion.io_realm_kotlin_fields[primaryKeySchemaProperty.name] ?: throw SerializationException( + "Cannot serialize class without primary key: '${targetCompanion.io_realm_kotlin_className}'" + ) + val primaryKey: BsonValue = storageTypeToBsonValue(primaryKeySchemaProperty.type.storageType, primaryKeyType, primaryKeyAccessor.get(target)) ?: BsonNull + BsonDocument( + "\$ref" to BsonString(targetCompanion.io_realm_kotlin_className), + "\$id" to primaryKey + ) + } + } + } + } + } + + internal fun bsonValueToStorageType( + storageType: RealmStorageType, + kClass: KClass<*>, + bsonValue: BsonValue?, + ): Any? { + if (bsonValue == null || bsonValue == BsonNull) return null + return when (storageType) { + RealmStorageType.BOOL -> bsonValue.asBoolean().value + RealmStorageType.INT -> bsonValue.asNumber().longValue() + RealmStorageType.STRING -> bsonValue.asString().value + RealmStorageType.BINARY -> bsonValue.asBinary().data + RealmStorageType.OBJECT -> { + val targetCompanion = kClass.realmObjectCompanionOrNull()!! + @Suppress("UNCHECKED_CAST") + val primaryKeyAccessor = + (targetCompanion.io_realm_kotlin_primaryKey as KMutableProperty1?) + when (primaryKeyAccessor) { + // Embedded objects does not have primary keys + null -> bsonToObject(targetCompanion, bsonValue.asDocument()) + else -> { + val targetInstance = + (targetCompanion.io_realm_kotlin_newInstance() as BaseRealmObject) + primaryKeyAccessor.set( + targetInstance, + bsonValue + ) + targetInstance + } + } + } + RealmStorageType.FLOAT -> bsonValue.asDouble().value.toFloat() + RealmStorageType.DOUBLE -> bsonValue.asDouble().value + RealmStorageType.DECIMAL128 -> bsonValue.asDecimal128() + RealmStorageType.TIMESTAMP -> bsonValue.asDateTime().asRealmInstant() + RealmStorageType.OBJECT_ID -> bsonValue.asObjectId() + RealmStorageType.UUID -> bsonValue.asBinary().asRealmUUID() + RealmStorageType.ANY -> when (bsonValue.bsonType) { + // RealmAny.Type.FLOAT + // RealmAny.Type.DOUBLE + BsonType.DOUBLE -> bsonValue.asDouble().value + // RealmAny.Type.STRING + BsonType.STRING -> bsonValue.asString().value + // RealmAny.Type.INT + BsonType.INT32 -> bsonValue.asInt32().value + BsonType.INT64 -> bsonValue.asInt64().value + // RealmAny.Type.DECIMAL128 + BsonType.DECIMAL128 -> bsonValue.asDecimal128() + // RealmAny.Type.BINARY + // RealmAny.Type.UUID handled as binary, we can't distinguish it + BsonType.BINARY -> bsonValue.asBinary() + // RealmAny.Type.OBJECT_ID + BsonType.OBJECT_ID -> bsonValue.asObjectId() + // RealmAny.Type.BOOL + BsonType.BOOLEAN -> bsonValue.asBoolean().value + // RealmAny.Type.TIMESTAMP + BsonType.DATE_TIME -> bsonValue.asDateTime().asRealmInstant() + BsonType.DOCUMENT -> { + val dbRef = bsonValue.asDocument() + val type = dbRef["\$ref"]?.asString()?.value ?: throw SerializationException("Cannot resolve target class: Missing '${"$"}ref'") + val primaryKey = dbRef["\$id"] ?: throw SerializationException("Cannot resolve target primary key: Missing '${"$"}id'") + val targetCompanion = schema[type] ?: throw SerializationException("Cannot resolve target class in schema: Unknown class '${"$"}ref=$type'") + val primaryKeySchemaProperty = targetCompanion.io_realm_kotlin_schema().primaryKey ?: throw SerializationException( + "Target class does not have a primary key: '${"$"}ref=$type'" + ) + val (primaryKeyType, primaryKeyAccessor) = targetCompanion.io_realm_kotlin_fields[primaryKeySchemaProperty.name] ?: throw SerializationException( + "Target class does not have a primary key: '${"$"}ref=$type'" + ) + val instance: RealmObject = targetCompanion.io_realm_kotlin_newInstance() as RealmObject + (primaryKeyAccessor as KMutableProperty1).set( + instance, + bsonValueToStorageType( + primaryKeySchemaProperty.type.storageType, + primaryKeyType, + primaryKey + ) + ) + RealmAny.create(instance) + } + BsonType.NULL -> null + BsonType.TIMESTAMP, + BsonType.END_OF_DOCUMENT, + BsonType.ARRAY, + BsonType.UNDEFINED, + BsonType.REGULAR_EXPRESSION, + BsonType.DB_POINTER, + BsonType.JAVASCRIPT, + BsonType.SYMBOL, + BsonType.JAVASCRIPT_WITH_SCOPE, + BsonType.MIN_KEY, + BsonType.MAX_KEY + -> throw SerializationException("Deserializer does not support ${bsonValue.bsonType}") + } + } + } +} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt index 61ec271eca..b89b4873b1 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt @@ -16,8 +16,18 @@ package io.realm.kotlin.mongodb.mongo +import io.realm.kotlin.RealmConfiguration +import io.realm.kotlin.internal.RealmObjectCompanion +import io.realm.kotlin.internal.platform.realmObjectCompanionOrThrow +import io.realm.kotlin.mongodb.AppConfiguration +import io.realm.kotlin.mongodb.internal.MongoDBSerializer +import io.realm.kotlin.types.BaseRealmObject +import io.realm.kotlin.types.RealmObject +import kotlinx.serialization.KSerializer +import kotlinx.serialization.modules.SerializersModule import org.mongodb.kbson.ExperimentalKBsonSerializerApi import org.mongodb.kbson.serialization.EJson +import kotlin.reflect.KClass /** * A **Mongo client** is used to access an App Service's Data Source directly without Sync support. @@ -25,6 +35,38 @@ import org.mongodb.kbson.serialization.EJson * This API corresponds to the Atlas App Service "MongoDB API". Please consult the * [MongoDB API Reference](https://www.mongodb.com/docs/atlas/app-services/functions/mongodb/api/) * for a detailed description of methods and arguments. + * + * Serialization to and from EJSON is performed with [KBSON](https://github.com/mongodb/kbson) + * that supports the [Kotlin Serialization framework](https://github.com/Kotlin/kotlinx.serialization) + * and handles serialization to and from classes marked with [Serializable]. Serialization can be + * customized by customizing the [EJson]-serializer passed to the various [MongoClient], + * [MongoDatabase] and [MongoCollection]-factory methods. + * + * Object references (links) are serialized solely by their primary keys, so to serialize the + * MongoDB API requests and responses to and from realm objects ([RealmObject], + * [EmbeddedRealmObject] and [AsymmetricRealmObject]) the serialization framework must be + * configured with special serializers for those. This can be done with + * ``` + * val user = app.currentUser + * val client = user.mongoClient( + * "serviceName", + * EJson( + * serializersModule = realmSerializerModule( + * setOf( + * MongoDBCollectionDataType1::class, + * MongoDBCollectionDataType2::class + * ) + * ) + * ) + * ``` + * + * *NOTE* Since the MongoDB API responses only includes primary key information for links, + * serialization of responses into realm objects ([RealmObject], [EmbeddedRealmObject] and + * [AsymmetricRealmObject]) will create instances of the target objects with only the primary key + * property set. All other properties from the realm objects will have the default values specified + * in the class definition. + * + * *NOTE* The EJSON serializer requires to opt-in to the experimental. */ public interface MongoClient { @@ -41,8 +83,36 @@ public interface MongoClient { * * @param databaseName name of the database from the data source. * @param eJson the EJson serializer that the [MongoDatabase] should use to convert objects and - * primary keys with. Will default to the client's [EJson] instance. + * primary keys with. Will default to the client's [EJson] instance. For details on + * configuration of serialization see + * [MongoClient]. */ @ExperimentalKBsonSerializerApi public fun database(databaseName: String, eJson: EJson? = null): MongoDatabase } + +/** + * Creates [SerializersModule] with MongoDB API compliant serializers for all the realm objects + * ([RealmObject], [EmbeddedRealmObject] and [AsymmetricRealmObject]) in the given [schema]. + * + * The target types of links in mixed fields cannot be derived from the schema definition of the + * realm objects. To be able to deserialize and create the correct instance of links in mixed + * fields, all serializers needs to know of the full set of realm objects. This means that the + * serializer module must be constructed with knowledge of all references classes in the schema. + */ +public fun realmSerializerModule(schema: Set>): SerializersModule { + val companions: Map = + schema.associate { kClass -> realmObjectCompanionOrThrow(kClass).let { it.io_realm_kotlin_className to it } } + RealmConfiguration.Builder( + emptySet()).build().schema + + val serializers: List, KSerializer<*>>> = schema.map { + it to MongoDBSerializer(it, companions) } + + return SerializersModule { + serializers.forEach { + @Suppress("UNCHECKED_CAST") + contextual(it.first as KClass, it.second as KSerializer) + } + } +} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt index ec50d255fe..e1410a184d 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt @@ -30,7 +30,8 @@ import org.mongodb.kbson.serialization.EJson * Input arguments and responses to the App Service HTTP requests will be serialized from and to * the type [T] and [K] using [Kotlin's Serialization framework](https://kotlinlang.org/docs/serialization.html) * and can be customized by [Serializable]-annotations or customizing the [EJson]-serializer passed - * to the various [MongoClient], [MongoDatabase] and [MongoCollection]-factory methods. + * to the various [MongoClient], [MongoDatabase] and [MongoCollection]-factory methods. For details + * on configuring the serialization see [MongoClient]. * * All operations on a [MongoCollection] will throw an: * - [ServiceException] if the underlying App Service HTTP requests fails @@ -50,6 +51,10 @@ public interface MongoCollection { /** * Get an instance of the same collection with a different set of default types serialization. + * + * @param eJson the EJson serializer that the [MongoCollection] should use to convert objects and + * primary keys with. Will default to the databases [EJson] instance. For details on + * configuration of serialization see [MongoClient]. */ @ExperimentalKBsonSerializerApi public fun withDocumentClass(eJson: EJson? = null): MongoCollection diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoDBSerializer.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoDBSerializer.kt deleted file mode 100644 index 6fa549b1d6..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoDBSerializer.kt +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Copyright 2024 Realm Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.realm.kotlin.mongodb.mongo - -import io.realm.kotlin.internal.ObjectIdImpl -import io.realm.kotlin.internal.RealmObjectCompanion -import io.realm.kotlin.internal.platform.realmObjectCompanionOrThrow -import io.realm.kotlin.internal.realmObjectCompanionOrNull -import io.realm.kotlin.internal.util.Validation -import io.realm.kotlin.schema.RealmStorageType -import io.realm.kotlin.types.BaseRealmObject -import io.realm.kotlin.types.MutableRealmInt -import io.realm.kotlin.types.RealmAny -import io.realm.kotlin.types.RealmDictionary -import io.realm.kotlin.types.RealmInstant -import io.realm.kotlin.types.RealmObject -import io.realm.kotlin.types.RealmUUID -import kotlinx.serialization.KSerializer -import kotlinx.serialization.SerializationException -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder -import org.mongodb.kbson.BsonArray -import org.mongodb.kbson.BsonBinary -import org.mongodb.kbson.BsonBoolean -import org.mongodb.kbson.BsonDocument -import org.mongodb.kbson.BsonDouble -import org.mongodb.kbson.BsonInt32 -import org.mongodb.kbson.BsonInt64 -import org.mongodb.kbson.BsonNull -import org.mongodb.kbson.BsonObjectId -import org.mongodb.kbson.BsonString -import org.mongodb.kbson.BsonType -import org.mongodb.kbson.BsonValue -import org.mongodb.kbson.Decimal128 -import org.mongodb.kbson.ObjectId -import kotlin.reflect.KClass -import kotlin.reflect.KMutableProperty1 -import kotlin.reflect.KProperty1 - -public open class MongoDBSerializer(clazz: KClass) : KSerializer { - override val descriptor: SerialDescriptor = BsonDocument.serializer().descriptor - private val companion = realmObjectCompanionOrThrow(clazz as KClass) - override fun deserialize(decoder: Decoder): T { - val x: BsonDocument = decoder.decodeSerializableValue(BsonDocument.serializer()) - return bsonToObject(companion, x) - } - - @Suppress("NestedBlockDepth") - private fun bsonToObject(companion: RealmObjectCompanion, bsonDocument: BsonDocument): T { - val instance = companion.io_realm_kotlin_newInstance() as T - val fields: Map, KProperty1>> = companion.io_realm_kotlin_fields - val schema = companion.io_realm_kotlin_schema() - bsonDocument.keys.forEach { - // FIXME Test exception path - val fieldsDescriptor = fields[it] ?: throw SerializationException("Unknown field $it for type ${companion.io_realm_kotlin_className}") - val type = schema[it]?.type - val value: Any? = if (type?.storageType == RealmStorageType.OBJECT) { - // FIXME Should we rather embed targetCompanion directly and make it nullable for non RealmObjects - val targetCompanion = fieldsDescriptor.first.realmObjectCompanionOrNull()!! - val primaryKeyAccessor = - (targetCompanion.io_realm_kotlin_primaryKey as KMutableProperty1?) - when (primaryKeyAccessor) { - // Embedded objects does not have primary keys - null -> { - val bsonValue: BsonValue? = bsonDocument[it] - if (bsonValue != null && bsonValue != BsonNull) { - bsonToObject(targetCompanion, bsonValue.asDocument()) - } else { - null - } - } - else -> { - val targetInstance = (targetCompanion.io_realm_kotlin_newInstance() as BaseRealmObject) - primaryKeyAccessor.set(targetInstance, bsonDocument[it]?.asPrimitiveValue()) - targetInstance - } - } - } else { - bsonDocument[it]?.asPrimitiveValue() - } - // FIXME Check validity of nullability - (fieldsDescriptor.second as KMutableProperty1).set(instance, value) - } - return instance - } - - // FIXME Serialize null values? - // FIXME @Ignore - override fun serialize(encoder: Encoder, value: T) { - val document = objectToBson(companion, value) - encoder.encodeSerializableValue(BsonDocument.serializer(), document) - } - - @Suppress("NestedBlockDepth") - private fun objectToBson(companion: RealmObjectCompanion, realmObject: BaseRealmObject): BsonDocument { - val fields: Map, KProperty1>> = companion.io_realm_kotlin_fields - val schema = companion.io_realm_kotlin_schema() - val document = BsonDocument() - fields.forEach { (fieldName, fieldDetails) -> - val (_, accessor) = fieldDetails - val type = schema[fieldName]?.type ?: Validation.sdkError("Schema does not contain property $fieldName") - val value = if (type.storageType == RealmStorageType.OBJECT) { - val target = accessor.get(realmObject) - if (target != null && target != BsonNull) { - val targetCompanion = - realmObjectCompanionOrThrow((target as BaseRealmObject)::class) - val primaryKeyProperty: KMutableProperty1? = targetCompanion.io_realm_kotlin_primaryKey as KMutableProperty1? - when (primaryKeyProperty) { - null -> objectToBson(targetCompanion, target) - else -> BsonValue(primaryKeyProperty.get(target)) - } - } else return@forEach - } else { - BsonValue(accessor.get(realmObject)) - } - document[fieldName] = value - } - return document - } -} - -@Suppress("ComplexMethod") -internal operator fun BsonValue.Companion.invoke(any: Any?): BsonValue { - return when (any) { - null -> BsonNull - is String -> BsonString(any) - is Boolean -> BsonBoolean(any) - is Byte -> BsonInt32(any.toInt()) - is Char -> BsonInt32(any.code) - is Short -> BsonInt32(any.toInt()) - is Int -> BsonInt32(any) - is Long -> BsonInt64(any) - is Float -> BsonDouble(any.toDouble()) - is Double -> BsonDouble(any) - is ObjectId -> any - is io.realm.kotlin.types.ObjectId -> BsonObjectId((any as ObjectIdImpl).bytes) - is BsonValue -> any - is Decimal128 -> any - is ByteArray -> BsonBinary(any) - is MutableRealmInt -> BsonInt64(any.get()) - is RealmAny -> TODO() - is RealmObject -> TODO() - is RealmInstant -> TODO() - is RealmUUID -> TODO() - // RealmSet and RealmList ends up here - is Collection<*> -> BsonArray(any.map { BsonValue(it) }) - // RealmDictionaries - is RealmDictionary<*> -> BsonDocument(any.mapValues { BsonValue(it.value) }) - else -> TODO("Serializer does not support object of type $any") -// BsonType.TIMESTAMP -> asTimestamp() -// BsonType.BINARY -> asBinary() -// BsonType.DATE_TIME -> asDateTime() -// BsonType.DOCUMENT, -// BsonType.END_OF_DOCUMENT, -// BsonType.ARRAY, -// BsonType.UNDEFINED, -// BsonType.REGULAR_EXPRESSION, -// BsonType.DB_POINTER, -// BsonType.JAVASCRIPT, -// BsonType.SYMBOL, -// BsonType.JAVASCRIPT_WITH_SCOPE, -// BsonType.MIN_KEY, -// BsonType.MAX_KEY -> TODO() - } -} - -internal fun BsonValue.asPrimitiveValue(): Any? { - - return when (this.bsonType) { - BsonType.DOUBLE -> asDouble().value - BsonType.STRING -> asString().value - BsonType.INT32 -> asInt32().value - BsonType.TIMESTAMP -> asTimestamp() - BsonType.INT64 -> asInt64().value - BsonType.DECIMAL128 -> asDecimal128() - BsonType.BINARY -> asBinary() - BsonType.OBJECT_ID -> asObjectId() - BsonType.BOOLEAN -> asBoolean().value - BsonType.DATE_TIME -> asDateTime() - BsonType.NULL -> null - BsonType.DOCUMENT, - BsonType.END_OF_DOCUMENT, - BsonType.ARRAY, - BsonType.UNDEFINED, - BsonType.REGULAR_EXPRESSION, - BsonType.DB_POINTER, - BsonType.JAVASCRIPT, - BsonType.SYMBOL, - BsonType.JAVASCRIPT_WITH_SCOPE, - BsonType.MIN_KEY, - BsonType.MAX_KEY -> TODO("Deserializer does not support ${this.bsonType}") - } -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoDatabase.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoDatabase.kt index a0d54c5fcb..2843eb9c60 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoDatabase.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoDatabase.kt @@ -55,7 +55,8 @@ public interface MongoDatabase { * @param collectionName the name of the collection name that the [MongoCollection] will * connect to. * @param eJson the EJson serializer that the [MongoCollection] should use to convert objects and - * primary keys with. Will default to the databases [EJson] instance. + * primary keys with. Will default to the databases [EJson] instance. For details on + * configuration of serialization see [MongoClient]. * @param T the default type that remote entities of the collection will be serialized from and * to. * @param K the default type that primary keys will be serialized into. diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/MongoDBClientSchemas.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/MongoDBClientSchemas.kt index b7a2ff1570..66ab53bb32 100644 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/MongoDBClientSchemas.kt +++ b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/MongoDBClientSchemas.kt @@ -16,11 +16,16 @@ package io.realm.kotlin.entities.sync -import io.realm.kotlin.mongodb.mongo.MongoDBSerializer import io.realm.kotlin.types.EmbeddedRealmObject +import io.realm.kotlin.types.RealmAny import io.realm.kotlin.types.RealmObject import io.realm.kotlin.types.annotations.PrimaryKey +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Polymorphic import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder import org.mongodb.kbson.BsonObjectId import org.mongodb.kbson.ObjectId import kotlin.random.Random @@ -32,8 +37,6 @@ class CollectionDataType(var name: String = "Default", @PrimaryKey var _id: Int constructor() : this("Default") } -class ParentSerializer : MongoDBSerializer(ParentCollectionDataType::class) -@Serializable(ParentSerializer::class) class ParentCollectionDataType : RealmObject { @PrimaryKey @Suppress("VariableNaming") @@ -41,7 +44,9 @@ class ParentCollectionDataType : RealmObject { var name: String = "PARENT-DEFAULT" var child: ChildCollectionDataType? = null var embeddedChild: EmbeddedChildCollectionDataType? = null + var any: RealmAny? = null } + @Serializable class ChildCollectionDataType : RealmObject { @PrimaryKey diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTests.kt index 59024d4c3b..327b2c2cee 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTests.kt @@ -19,9 +19,9 @@ package io.realm.kotlin.test.mongodb.common.mongo import io.realm.kotlin.entities.sync.CollectionDataType import io.realm.kotlin.internal.platform.runBlocking import io.realm.kotlin.mongodb.exceptions.ServiceException -import io.realm.kotlin.mongodb.mongo.MongoClient import io.realm.kotlin.mongodb.ext.collection import io.realm.kotlin.mongodb.ext.insertOne +import io.realm.kotlin.mongodb.mongo.MongoClient import io.realm.kotlin.test.mongodb.TEST_APP_FLEX import io.realm.kotlin.test.mongodb.TestApp import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage @@ -71,13 +71,13 @@ class MongoClientTests { } @Test + @OptIn(ExperimentalKBsonSerializerApi::class) fun database_customSerializer() = runBlocking { val collectionWithDefaultSerializer = client.database(app.clientAppId) .collection("CollectionDataType") assertFailsWithMessage("Serializer for class 'CustomDataType' is not found.") { collectionWithDefaultSerializer.insertOne(CustomDataType("object-1")) } - val collectionWithCustomSerializer = client.database(app.clientAppId, customEjsonSerializer) .collection("CollectionDataType") assertIs(collectionWithCustomSerializer.insertOne(CustomDataType("object-1"))) diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt index d481dab693..fbe164ce9d 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt @@ -22,13 +22,13 @@ import io.realm.kotlin.entities.sync.ChildCollectionDataType import io.realm.kotlin.entities.sync.CollectionDataType import io.realm.kotlin.entities.sync.EmbeddedChildCollectionDataType import io.realm.kotlin.entities.sync.ParentCollectionDataType +import io.realm.kotlin.ext.asRealmObject import io.realm.kotlin.ext.query import io.realm.kotlin.internal.platform.runBlocking +import io.realm.kotlin.log.LogLevel +import io.realm.kotlin.log.RealmLog import io.realm.kotlin.mongodb.User import io.realm.kotlin.mongodb.exceptions.ServiceException -import io.realm.kotlin.mongodb.mongo.MongoClient -import io.realm.kotlin.mongodb.mongo.MongoCollection -import io.realm.kotlin.mongodb.mongo.MongoDatabase import io.realm.kotlin.mongodb.ext.aggregate import io.realm.kotlin.mongodb.ext.collection import io.realm.kotlin.mongodb.ext.count @@ -43,16 +43,22 @@ import io.realm.kotlin.mongodb.ext.insertMany import io.realm.kotlin.mongodb.ext.insertOne import io.realm.kotlin.mongodb.ext.updateMany import io.realm.kotlin.mongodb.ext.updateOne +import io.realm.kotlin.mongodb.mongo.MongoClient +import io.realm.kotlin.mongodb.mongo.MongoCollection +import io.realm.kotlin.mongodb.mongo.MongoDatabase +import io.realm.kotlin.mongodb.mongo.realmSerializerModule import io.realm.kotlin.mongodb.sync.SyncConfiguration import io.realm.kotlin.mongodb.syncSession import io.realm.kotlin.notifications.ResultsChange import io.realm.kotlin.test.mongodb.TEST_APP_FLEX import io.realm.kotlin.test.mongodb.TestApp import io.realm.kotlin.test.mongodb.asTestApp +import io.realm.kotlin.test.mongodb.common.FLEXIBLE_SYNC_SCHEMA import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage import io.realm.kotlin.test.util.receiveOrFail import io.realm.kotlin.test.util.use import io.realm.kotlin.types.BaseRealmObject +import io.realm.kotlin.types.RealmAny import io.realm.kotlin.types.RealmObject import io.realm.kotlin.types.annotations.PersistedName import kotlinx.coroutines.async @@ -132,6 +138,7 @@ class MongoCollectionFromClientTests : MongoCollectionTests() { assertEquals("CollectionDataType", client.collection().name) } + @OptIn(ExperimentalKBsonSerializerApi::class) @Test override fun findOne_unknownCollection() = runBlocking { @OptIn(ExperimentalKBsonSerializerApi::class) @@ -142,7 +149,7 @@ class MongoCollectionFromClientTests : MongoCollectionTests() { } } -abstract sealed class MongoCollectionTests { +sealed class MongoCollectionTests { lateinit var app: TestApp lateinit var user: User @@ -165,7 +172,18 @@ abstract sealed class MongoCollectionTests { } user = app.createUserAndLogin() @OptIn(ExperimentalKBsonSerializerApi::class) - client = user.mongoClient(TEST_SERVICE_NAME) + client = user.mongoClient( + TEST_SERVICE_NAME, + EJson( + serializersModule = realmSerializerModule( + setOf( + ParentCollectionDataType::class, + ChildCollectionDataType::class + ) + ) + ) + ) + } @AfterTest @@ -338,6 +356,68 @@ abstract sealed class MongoCollectionTests { } } + @Test + open fun findOne_typedLinks() = runBlocking { + Realm.open( + SyncConfiguration.Builder(user, COLLECTION_SCHEMAS) + .initialSubscriptions { + add(it.query()) + add(it.query()) + } + .build() + ).use { + val syncedParent = it.write { + copyToRealm( + ParentCollectionDataType().apply { + any = RealmAny.create(ChildCollectionDataType()) + } + ) + } + // We need to upload schema before proceeding + it.syncSession.uploadAllLocalChanges(30.seconds) + // The translator should have some time to integrate the synced data + delay(5.seconds) + + @OptIn(ExperimentalKBsonSerializerApi::class) + val mongoDBClientParent = collection().findOne() + assertEquals(syncedParent._id, mongoDBClientParent!!._id) + assertEquals(syncedParent.any!!.asRealmObject()._id, mongoDBClientParent.any!!.asRealmObject()._id) + } + } + + @Test + open fun findOne_typedLinks_throwsOnMissingTargetSchema() = runBlocking { + Realm.open( + SyncConfiguration.Builder(user, FLEXIBLE_SYNC_SCHEMA) + .initialSubscriptions { + add(it.query()) + add(it.query()) + } + .build() + ).use { + val syncedParent = it.write { + copyToRealm( + ParentCollectionDataType().apply { + any = RealmAny.create(ChildCollectionDataType()) + } + ) + } + // We need to upload schema before proceeding + it.syncSession.uploadAllLocalChanges(30.seconds) + // The translator should have some time to integrate the synced data + delay(3.seconds) + + @OptIn(ExperimentalKBsonSerializerApi::class) + val parentCollection = collection(EJson(serializersModule = realmSerializerModule( + setOf(ParentCollectionDataType::class) + ))) + + assertFailsWithMessage("Cannot resolve target class in schema: Unknown class '${"$"} ref=ChildCollectionDataType'") { + parentCollection.findOne() + } + } + } + @OptIn(ExperimentalKBsonSerializerApi::class) @Test open fun findOne_embeddedObjects() = runBlocking { @@ -516,7 +596,7 @@ abstract sealed class MongoCollectionTests { open fun insertOne_links() = runBlocking { // Open a synced realm and verified that the linked entities we upload through the Realm.open( - SyncConfiguration.Builder(user, COLLECTION_SCHEMAS) + SyncConfiguration.Builder(user, FLEXIBLE_SYNC_SCHEMA) .initialSubscriptions { add(it.query()) add(it.query()) @@ -573,13 +653,77 @@ abstract sealed class MongoCollectionTests { } } + @Test + @OptIn(ExperimentalKBsonSerializerApi::class) + open fun insertOne_typedLinks() = runBlocking { + // Open a synced realm and verified that the linked entities we upload through the + Realm.open( + SyncConfiguration.Builder(user, FLEXIBLE_SYNC_SCHEMA) + .initialSubscriptions { + add(it.query()) + add(it.query()) + } + .build() + ).use { realm -> + // We need to upload schema before proceeding + realm.syncSession.uploadAllLocalChanges() + + // We set up listeners to be able to react on when the objects are seen in the synced realm + val childChannel = Channel>(10) + val childListener = + async { realm.query().asFlow().collect { childChannel.send(it) } } + childChannel.receiveOrFail(message = "Didn't receive initial value").let { + assertTrue { it.list.isEmpty() } + } + val parentChannel = Channel>(10) + val parentListener = + async { realm.query().asFlow().collect { parentChannel.send(it) } } + parentChannel.receiveOrFail(message = "Didn't receive initial value").let { + assertTrue { it.list.isEmpty() } + } + + val childCollection = collection() + assertEquals(0, childCollection.find().size) + val unmanagedChild = ChildCollectionDataType() + RealmLog.level = LogLevel.ALL + assertEquals(unmanagedChild._id, childCollection.insertOne(unmanagedChild)) + // We can't rely on the translator to incorporate the insertOnes in order so we need to + // assure that the child is actually added before verifying the link in the parent. + childChannel.receiveOrFail(message = "Didn't receive initial value").let { + assertEquals(unmanagedChild._id, it.list.first()._id) + } + + val parentCollection = collection() + val unmanagedParent = ParentCollectionDataType().apply { + this.any = RealmAny.create(unmanagedChild) + } + + val actual = parentCollection.insertOne(unmanagedParent) + assertEquals(unmanagedParent._id, actual) + + // Verifying that the parent include the correct link + parentChannel.receiveOrFail( + timeout = 5.seconds, + message = "Didn't receive update value" + ).let { + val parent = it.list.first() + assertEquals(unmanagedParent._id, parent._id) + parent.any!!.asRealmObject().let { + assertEquals(unmanagedChild._id, it._id) + assertEquals(unmanagedChild.name, it.name) + } + } + parentListener.cancel() + childListener.cancel() + } + } + @OptIn(ExperimentalKBsonSerializerApi::class) @Test fun insertOne_embeddedObjects() = runBlocking { - // Empty collections - assertNull(collection.findOne()) - val parentCollection = collection() + // Empty collections + assertNull(parentCollection.findOne()) parentCollection.insertOne( ParentCollectionDataType().apply { embeddedChild = EmbeddedChildCollectionDataType().apply { name = "EMBEDDED-NAME" } From 22ab85561ed09ce746338e1e5ae658f366d48ed8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Fri, 5 Apr 2024 15:37:31 +0200 Subject: [PATCH 24/36] Clean up and linting --- CHANGELOG.md | 2 +- .../realm/kotlin/internal/RealmInstantImpl.kt | 3 --- .../types/annotations/MongoDBSerializable.kt | 19 ------------------- .../mongodb/internal/MongoDBSerializer.kt | 4 +++- .../realm/kotlin/mongodb/mongo/MongoClient.kt | 8 ++------ .../entities/sync/MongoDBClientSchemas.kt | 5 ----- .../kotlin/test/mongodb/common/UserTests.kt | 2 +- .../common/mongo/MongoCollectionTests.kt | 10 +++++++--- .../common/mongo/MongoDatabaseTests.kt | 3 +-- 9 files changed, 15 insertions(+), 41 deletions(-) delete mode 100644 packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/annotations/MongoDBSerializable.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index f422169634..a86e9e0352 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ This release will bump the Realm file format from version 23 to 24. Opening a fi * Index on list of strings property now supported (Core issue [realm/realm-core#7142](https://github.com/realm/realm-core/pull/7142)) * Improved performance of RQL (parsed) queries on a non-linked string property using: >, >=, <, <=, operators and fixed behaviour that a null string should be evaulated as less than everything, previously nulls were not matched. (Core issue [realm/realm-core#3939](https://github.com/realm/realm-core/issues/3939). * Updated bundled OpenSSL version to 3.2.0 (Core issue [realm/realm-core#7303](https://github.com/realm/realm-core/pull/7303)) +* [Sync] Add Mongo Client API to access Atlas App Service collections. It can be accessed through `User.mongoClient`. (Issue [#972](https://github.com/realm/realm-kotlin/issues/972)) ### Fixed * Sorting order of strings has changed to use standard unicode codepoint order instead of grouping similar english letters together. A noticeable change will be from "aAbBzZ" to "ABZabz". (Core issue [realm/realm-core#2573](https://github.com/realm/realm-core/issues/2573)) @@ -123,7 +124,6 @@ This release will bump the Realm file format from version 23 to 24. Opening a fi * The Unpacking of JVM native library will use the current library version instead of a calculated hash for the path. (Issue [#1617](https://github.com/realm/realm-kotlin/issues/1617)). * [Sync] Added option to use managed WebSockets via OkHttp instead of Realm's built-in WebSocket client for Sync traffic (Only Android and JVM targets for now). Managed WebSockets offer improved support for proxies and firewalls that require authentication. This feature is currently opt-in and can be enabled by using `AppConfiguration.usePlatformNetworking()`. Managed WebSockets will become the default in a future version. (PR [#1528](https://github.com/realm/realm-kotlin/pull/1528)). * [Sync] `AutoClientResetFailed` exception now reports as the throwable cause any user exceptions that might occur during a client reset. (Issue [#1580](https://github.com/realm/realm-kotlin/issues/1580)) -* [Sync] Add Mongo Client API to access Atlas App Service collections. It can be accessed through `User.mongoClient`. (Issue [#972](https://github.com/realm/realm-kotlin/issues/972)) ### Fixed * Cache notification callback JNI references at startup to ensure that symbols can be resolved in core callbacks. (Issue [#1577](https://github.com/realm/realm-kotlin/issues/1577)) diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmInstantImpl.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmInstantImpl.kt index 99763d73e9..2d6af6ad9e 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmInstantImpl.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmInstantImpl.kt @@ -2,8 +2,6 @@ package io.realm.kotlin.internal import io.realm.kotlin.internal.interop.Timestamp import io.realm.kotlin.types.RealmInstant -import org.mongodb.kbson.BsonBinary -import org.mongodb.kbson.BsonBinarySubType import org.mongodb.kbson.BsonDateTime import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds @@ -49,4 +47,3 @@ internal fun RealmInstant.restrictToMillisPrecision() = public inline fun RealmInstant.asBsonDateTime(): BsonDateTime = BsonDateTime(toDuration().inWholeMilliseconds) public inline fun BsonDateTime.asRealmInstant(): RealmInstant = value.milliseconds.toRealmInstant() - diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/annotations/MongoDBSerializable.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/annotations/MongoDBSerializable.kt deleted file mode 100644 index f10f179440..0000000000 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/annotations/MongoDBSerializable.kt +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2024 Realm Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.realm.kotlin.types.annotations - -public annotation class MongoDBSerializable diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDBSerializer.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDBSerializer.kt index 7ee739137e..e428b35dd8 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDBSerializer.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDBSerializer.kt @@ -63,7 +63,7 @@ import kotlin.reflect.KProperty1 * realm objects. To be able to deserialize and create the correct instance, the serializer needs to * know of all potential target types. */ -public open class MongoDBSerializer internal constructor ( +public open class MongoDBSerializer internal constructor( clazz: KClass, internal val schema: Map = emptyMap() ) : KSerializer { @@ -113,6 +113,7 @@ public open class MongoDBSerializer internal constructor ( return document } + @Suppress("LongMethod", "ComplexMethod") private fun storageTypeToBsonValue( storageType: RealmStorageType, clazz: KClass<*>, @@ -182,6 +183,7 @@ public open class MongoDBSerializer internal constructor ( } } + @Suppress("LongMethod", "ComplexMethod") internal fun bsonValueToStorageType( storageType: RealmStorageType, kClass: KClass<*>, diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt index b89b4873b1..59b4573172 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt @@ -16,10 +16,8 @@ package io.realm.kotlin.mongodb.mongo -import io.realm.kotlin.RealmConfiguration import io.realm.kotlin.internal.RealmObjectCompanion import io.realm.kotlin.internal.platform.realmObjectCompanionOrThrow -import io.realm.kotlin.mongodb.AppConfiguration import io.realm.kotlin.mongodb.internal.MongoDBSerializer import io.realm.kotlin.types.BaseRealmObject import io.realm.kotlin.types.RealmObject @@ -103,11 +101,9 @@ public interface MongoClient { public fun realmSerializerModule(schema: Set>): SerializersModule { val companions: Map = schema.associate { kClass -> realmObjectCompanionOrThrow(kClass).let { it.io_realm_kotlin_className to it } } - RealmConfiguration.Builder( - emptySet()).build().schema - val serializers: List, KSerializer<*>>> = schema.map { - it to MongoDBSerializer(it, companions) } + it to MongoDBSerializer(it, companions) + } return SerializersModule { serializers.forEach { diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/MongoDBClientSchemas.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/MongoDBClientSchemas.kt index 66ab53bb32..666c1ef489 100644 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/MongoDBClientSchemas.kt +++ b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/MongoDBClientSchemas.kt @@ -20,12 +20,7 @@ import io.realm.kotlin.types.EmbeddedRealmObject import io.realm.kotlin.types.RealmAny import io.realm.kotlin.types.RealmObject import io.realm.kotlin.types.annotations.PrimaryKey -import kotlinx.serialization.KSerializer -import kotlinx.serialization.Polymorphic import kotlinx.serialization.Serializable -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder import org.mongodb.kbson.BsonObjectId import org.mongodb.kbson.ObjectId import kotlin.random.Random diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserTests.kt index a955d372e0..9125e5ecbc 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserTests.kt @@ -30,8 +30,8 @@ import io.realm.kotlin.mongodb.exceptions.CredentialsCannotBeLinkedException import io.realm.kotlin.mongodb.exceptions.ServiceException import io.realm.kotlin.mongodb.ext.customData import io.realm.kotlin.mongodb.ext.customDataAsBsonDocument -import io.realm.kotlin.mongodb.mongo.MongoClient import io.realm.kotlin.mongodb.ext.insertOne +import io.realm.kotlin.mongodb.mongo.MongoClient import io.realm.kotlin.mongodb.sync.SyncConfiguration import io.realm.kotlin.test.mongodb.TestApp import io.realm.kotlin.test.mongodb.asTestApp diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt index fbe164ce9d..9dd7f3cf02 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt @@ -408,9 +408,13 @@ sealed class MongoCollectionTests { delay(3.seconds) @OptIn(ExperimentalKBsonSerializerApi::class) - val parentCollection = collection(EJson(serializersModule = realmSerializerModule( - setOf(ParentCollectionDataType::class) - ))) + val parentCollection = collection( + EJson( + serializersModule = realmSerializerModule( + setOf(ParentCollectionDataType::class) + ) + ) + ) assertFailsWithMessage("Cannot resolve target class in schema: Unknown class '${"$"} ref=ChildCollectionDataType'") { parentCollection.findOne() diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoDatabaseTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoDatabaseTests.kt index abbeaf3220..d9e5dfd391 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoDatabaseTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoDatabaseTests.kt @@ -18,10 +18,9 @@ package io.realm.kotlin.test.mongodb.common.mongo import io.realm.kotlin.entities.sync.CollectionDataType import io.realm.kotlin.internal.platform.runBlocking -import io.realm.kotlin.mongodb.AppConfiguration +import io.realm.kotlin.mongodb.ext.insertOne import io.realm.kotlin.mongodb.mongo.MongoClient import io.realm.kotlin.mongodb.mongo.MongoDatabase -import io.realm.kotlin.mongodb.ext.insertOne import io.realm.kotlin.test.mongodb.TEST_APP_FLEX import io.realm.kotlin.test.mongodb.TestApp import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage From 51e3e294ef88a7788e8a7f6ffcd753023affc670 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Wed, 10 Apr 2024 09:50:25 +0200 Subject: [PATCH 25/36] Test upcoming kbson release --- packages/settings.gradle.kts | 3 +++ .../kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/settings.gradle.kts b/packages/settings.gradle.kts index 511d23763d..31220b75a5 100644 --- a/packages/settings.gradle.kts +++ b/packages/settings.gradle.kts @@ -24,6 +24,9 @@ dependencyResolutionManagement { getPropertyValue("testRepository")?.let { maven("file://${rootDir.absolutePath}/$it") } + maven { + url = uri("https://oss.sonatype.org/content/repositories/snapshots") + } } } fun getPropertyValue(propertyName: String): String? { diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt index 9dd7f3cf02..c08385f2f5 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt @@ -416,7 +416,7 @@ sealed class MongoCollectionTests { ) ) - assertFailsWithMessage("Cannot resolve target class in schema: Unknown class '${"$"} ref=ChildCollectionDataType'") { + assertFailsWithMessage("Cannot resolve target class in schema: Unknown class '${"$"}ref=ChildCollectionDataType'") { parentCollection.findOne() } } From 7006b9ebbbba0f1050c0c4c053cd86945203b377 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Wed, 10 Apr 2024 10:25:01 +0200 Subject: [PATCH 26/36] Fix CopyFromRealmTests --- .../kotlin/test/common/CopyFromRealmTests.kt | 25 ++++++++++--------- .../common/mongo/MongoCollectionTests.kt | 1 - 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/CopyFromRealmTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/CopyFromRealmTests.kt index 73064a7c04..a0081715e0 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/CopyFromRealmTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/CopyFromRealmTests.kt @@ -55,6 +55,7 @@ import io.realm.kotlin.types.RealmSet import io.realm.kotlin.types.RealmUUID import org.mongodb.kbson.BsonObjectId import org.mongodb.kbson.Decimal128 +import kotlin.reflect.KClass import kotlin.reflect.KMutableProperty1 import kotlin.reflect.KProperty1 import kotlin.reflect.KType @@ -99,13 +100,13 @@ class CopyFromRealmTests { fun primitiveValues() { // This also checks that any default values set in the class are being overridden correctly. val type = Sample::class val schemaProperties = type.realmObjectCompanionOrThrow().io_realm_kotlin_schema().properties - val fields: Map> = type.realmObjectCompanionOrThrow().io_realm_kotlin_fields + val fields: Map, KProperty1>> = type.realmObjectCompanionOrThrow().io_realm_kotlin_fields // Dynamically set data on the Sample object val originalObject = Sample() schemaProperties.forEach { prop: RealmProperty -> if (prop.type is ValuePropertyType) { - val accessor: KMutableProperty1 = fields[prop.name] as KMutableProperty1 + val accessor: KMutableProperty1 = fields[prop.name]!!.second as KMutableProperty1 val fieldValue: Any? = createPrimitiveValueData(accessor) accessor.set(originalObject, fieldValue) } @@ -122,7 +123,7 @@ class CopyFromRealmTests { // Validate that all primitive list fields were round-tripped correctly. schemaProperties.forEach { prop: RealmProperty -> if (prop.type is ValuePropertyType) { - val accessor: KMutableProperty1 = fields[prop.name] as KMutableProperty1 + val accessor: KMutableProperty1 = fields[prop.name]!!.second as KMutableProperty1 val value: Any? = createPrimitiveValueData(accessor) if (prop.type.storageType == RealmStorageType.BINARY) { @@ -301,13 +302,13 @@ class CopyFromRealmTests { fun primitiveLists() { val type = Sample::class val schemaProperties = type.realmObjectCompanionOrThrow().io_realm_kotlin_schema().properties - val fields: Map> = type.realmObjectCompanionOrThrow().io_realm_kotlin_fields + val fields: Map, KProperty1>> = type.realmObjectCompanionOrThrow().io_realm_kotlin_fields // Dynamically set data on the Sample object val originalObject = Sample() schemaProperties.forEach { prop: RealmProperty -> if (prop.type is ListPropertyType && !(prop.type as ListPropertyType).isComputed) { - val accessor: KMutableProperty1 = fields[prop.name] as KMutableProperty1 + val accessor: KMutableProperty1 = fields[prop.name]!!.second as KMutableProperty1 val list: List = createPrimitiveListData(prop, accessor) accessor.set(originalObject, list) } @@ -324,7 +325,7 @@ class CopyFromRealmTests { // Validate that all primitive list fields were round-tripped correctly. schemaProperties.forEach { prop: RealmProperty -> if (prop.type is ListPropertyType && !(prop.type as ListPropertyType).isComputed) { - val accessor: KMutableProperty1 = fields[prop.name] as KMutableProperty1 + val accessor: KMutableProperty1 = fields[prop.name]!!.second as KMutableProperty1 val list: List = createPrimitiveListData(prop, accessor) if (prop.type.storageType == RealmStorageType.BINARY) { @@ -395,13 +396,13 @@ class CopyFromRealmTests { fun primitiveSets() { val type = Sample::class val schemaProperties = type.realmObjectCompanionOrThrow().io_realm_kotlin_schema().properties - val fields: Map> = type.realmObjectCompanionOrThrow().io_realm_kotlin_fields + val fields: Map, KProperty1>> = type.realmObjectCompanionOrThrow().io_realm_kotlin_fields // Dynamically set data on the Sample object val originalObject = Sample() schemaProperties.forEach { prop: RealmProperty -> if (prop.type is SetPropertyType) { - val accessor: KMutableProperty1 = fields[prop.name] as KMutableProperty1 + val accessor: KMutableProperty1 = fields[prop.name]!!.second as KMutableProperty1 val set: Set = createPrimitiveSetData(prop, accessor) accessor.set(originalObject, set) } @@ -418,7 +419,7 @@ class CopyFromRealmTests { // Validate that all primitive list fields were round-tripped correctly. schemaProperties.forEach { prop: RealmProperty -> if (prop.type is SetPropertyType) { - val accessor: KMutableProperty1 = fields[prop.name] as KMutableProperty1 + val accessor: KMutableProperty1 = fields[prop.name]!!.second as KMutableProperty1 val set: Set = createPrimitiveSetData(prop, accessor) if (prop.type.storageType == RealmStorageType.BINARY) { @@ -487,13 +488,13 @@ class CopyFromRealmTests { fun primitiveDictionaries() { val type = Sample::class val schemaProperties = type.realmObjectCompanionOrThrow().io_realm_kotlin_schema().properties - val fields: Map> = type.realmObjectCompanionOrThrow().io_realm_kotlin_fields + val fields: Map, KProperty1>> = type.realmObjectCompanionOrThrow().io_realm_kotlin_fields // Dynamically set data on the Sample object val originalObject = Sample() schemaProperties.forEach { prop: RealmProperty -> if (prop.type is MapPropertyType) { - val accessor: KMutableProperty1 = fields[prop.name] as KMutableProperty1 + val accessor: KMutableProperty1 = fields[prop.name]!!.second as KMutableProperty1 val dictionary: RealmDictionary = createPrimitiveDictionaryData(prop, accessor) accessor.set(originalObject, dictionary) } @@ -510,7 +511,7 @@ class CopyFromRealmTests { // Validate that all primitive list fields were round-tripped correctly. schemaProperties.forEach { prop: RealmProperty -> if (prop.type is MapPropertyType) { - val accessor: KMutableProperty1 = fields[prop.name] as KMutableProperty1 + val accessor: KMutableProperty1 = fields[prop.name]!!.second as KMutableProperty1 val dictionary: RealmDictionary = createPrimitiveDictionaryData(prop, accessor) if (prop.type.storageType == RealmStorageType.BINARY) { diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt index c08385f2f5..e6c79d7457 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt @@ -183,7 +183,6 @@ sealed class MongoCollectionTests { ) ) ) - } @AfterTest From 81013a20b8f984754cadb931fef3b81c35ba3df7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Wed, 10 Apr 2024 15:40:53 +0200 Subject: [PATCH 27/36] Use public available kbson 0.4.0 --- buildSrc/src/main/kotlin/Config.kt | 2 +- packages/settings.gradle.kts | 3 -- .../test/mongodb/common/UserProfileTests.kt | 4 +- .../common/mongo/MongoCollectionTests.kt | 52 +++++++++++-------- .../kotlin/test/mongodb/common/utils/Utils.kt | 19 +++++++ 5 files changed, 52 insertions(+), 28 deletions(-) diff --git a/buildSrc/src/main/kotlin/Config.kt b/buildSrc/src/main/kotlin/Config.kt index a6adb3b25c..9f647ef0e1 100644 --- a/buildSrc/src/main/kotlin/Config.kt +++ b/buildSrc/src/main/kotlin/Config.kt @@ -121,7 +121,7 @@ object Versions { const val jmh = "1.34" // https://github.com/openjdk/jmh const val jmhPlugin = "0.6.6" // https://github.com/melix/jmh-gradle-plugin const val junit = "4.13.2" // https://mvnrepository.com/artifact/junit/junit - const val kbson = "0.4.0-SNAPSHOT" // https://github.com/mongodb/kbson + const val kbson = "0.4.0" // https://github.com/mongodb/kbson // When updating the Kotlin version, also remember to update /examples/min-android-sample/build.gradle.kts const val kotlin = "1.9.0" // https://github.com/JetBrains/kotlin and https://kotlinlang.org/docs/releases.html#release-details const val kotlinJvmTarget = "1.8" // Which JVM bytecode version is kotlin compiled to. diff --git a/packages/settings.gradle.kts b/packages/settings.gradle.kts index 31220b75a5..511d23763d 100644 --- a/packages/settings.gradle.kts +++ b/packages/settings.gradle.kts @@ -24,9 +24,6 @@ dependencyResolutionManagement { getPropertyValue("testRepository")?.let { maven("file://${rootDir.absolutePath}/$it") } - maven { - url = uri("https://oss.sonatype.org/content/repositories/snapshots") - } } } fun getPropertyValue(propertyName: String): String? { diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserProfileTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserProfileTests.kt index 5ac29f139b..87e056b344 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserProfileTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserProfileTests.kt @@ -231,10 +231,10 @@ class UserProfileTests { setEmptyProfile() val user = app.createUserAndLogin() - assertFailsWithMessage("Fields [name, email, picture_url, first_name, last_name, gender, birthday, min_age, max_age] are required for type with serial name 'io.realm.kotlin.test.mongodb.common.UserProfile', but they were missing") { + assertFailsWithMessage("Could not decode field 'name': Undefined value on a non-optional field") { user.profile() } - assertFailsWithMessage("Fields [name, email, picture_url, first_name, last_name, gender, birthday, min_age, max_age] are required for type with serial name 'io.realm.kotlin.test.mongodb.common.UserProfile', but they were missing") { + assertFailsWithMessage("Could not decode field 'name': Undefined value on a non-optional field") { user.profile(UserProfile.serializer()) } } diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt index e6c79d7457..a267c6577c 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt @@ -55,6 +55,7 @@ import io.realm.kotlin.test.mongodb.TestApp import io.realm.kotlin.test.mongodb.asTestApp import io.realm.kotlin.test.mongodb.common.FLEXIBLE_SYNC_SCHEMA import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage +import io.realm.kotlin.test.mongodb.common.utils.retry import io.realm.kotlin.test.util.receiveOrFail import io.realm.kotlin.test.util.use import io.realm.kotlin.types.BaseRealmObject @@ -63,7 +64,6 @@ import io.realm.kotlin.types.RealmObject import io.realm.kotlin.types.annotations.PersistedName import kotlinx.coroutines.async import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.delay import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.SerializationException @@ -333,23 +333,25 @@ sealed class MongoCollectionTests { @Test open fun findOne_links() = runBlocking { Realm.open( - SyncConfiguration.Builder(user, COLLECTION_SCHEMAS) + SyncConfiguration.Builder(user, FLEXIBLE_SYNC_SCHEMA) .initialSubscriptions { add(it.query()) add(it.query()) } .build() - ).use { - val syncedParent = it.write { + ).use { realm -> + val syncedParent = realm.write { copyToRealm(ParentCollectionDataType().apply { child = ChildCollectionDataType() }) } - // We need to upload schema before proceeding - it.syncSession.uploadAllLocalChanges(30.seconds) - // The translator should have some time to integrate the synced data - delay(5.seconds) + realm.syncSession.uploadAllLocalChanges(30.seconds) @OptIn(ExperimentalKBsonSerializerApi::class) - val mongoDBClientParent = collection().findOne() + val parentCollection = collection() + + val mongoDBClientParent = retry( + { parentCollection.findOne() }, + { response: ParentCollectionDataType? -> response != null }, + ) assertEquals(syncedParent._id, mongoDBClientParent!!._id) assertEquals(syncedParent.child!!._id, mongoDBClientParent!!.child!!._id) } @@ -358,14 +360,14 @@ sealed class MongoCollectionTests { @Test open fun findOne_typedLinks() = runBlocking { Realm.open( - SyncConfiguration.Builder(user, COLLECTION_SCHEMAS) + SyncConfiguration.Builder(user, FLEXIBLE_SYNC_SCHEMA) .initialSubscriptions { add(it.query()) add(it.query()) } .build() - ).use { - val syncedParent = it.write { + ).use { realm -> + val syncedParent = realm.write { copyToRealm( ParentCollectionDataType().apply { any = RealmAny.create(ChildCollectionDataType()) @@ -373,12 +375,15 @@ sealed class MongoCollectionTests { ) } // We need to upload schema before proceeding - it.syncSession.uploadAllLocalChanges(30.seconds) - // The translator should have some time to integrate the synced data - delay(5.seconds) + realm.syncSession.uploadAllLocalChanges(30.seconds) @OptIn(ExperimentalKBsonSerializerApi::class) - val mongoDBClientParent = collection().findOne() + val parentCollection = collection() + + val mongoDBClientParent = retry( + action = { parentCollection.findOne() }, + until = { response -> response != null } + ) assertEquals(syncedParent._id, mongoDBClientParent!!._id) assertEquals(syncedParent.any!!.asRealmObject()._id, mongoDBClientParent.any!!.asRealmObject()._id) } @@ -393,8 +398,8 @@ sealed class MongoCollectionTests { add(it.query()) } .build() - ).use { - val syncedParent = it.write { + ).use { realm -> + val syncedParent = realm.write { copyToRealm( ParentCollectionDataType().apply { any = RealmAny.create(ChildCollectionDataType()) @@ -402,9 +407,7 @@ sealed class MongoCollectionTests { ) } // We need to upload schema before proceeding - it.syncSession.uploadAllLocalChanges(30.seconds) - // The translator should have some time to integrate the synced data - delay(3.seconds) + realm.syncSession.uploadAllLocalChanges(30.seconds) @OptIn(ExperimentalKBsonSerializerApi::class) val parentCollection = collection( @@ -416,7 +419,12 @@ sealed class MongoCollectionTests { ) assertFailsWithMessage("Cannot resolve target class in schema: Unknown class '${"$"}ref=ChildCollectionDataType'") { - parentCollection.findOne() + // We need to await until the response is non-null otherwise there will be nothing + // to deserialize and no exception will be thrown + retry( + action = { parentCollection.findOne() }, + until = { response -> response != null } + ) } } } diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/utils/Utils.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/utils/Utils.kt index 285fcc6d9d..b9ca03ec40 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/utils/Utils.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/utils/Utils.kt @@ -18,11 +18,15 @@ package io.realm.kotlin.test.mongodb.common.utils import io.realm.kotlin.mongodb.sync.SubscriptionSet import io.realm.kotlin.mongodb.sync.SyncSession import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking import kotlin.reflect.KClass import kotlin.test.assertFailsWith import kotlin.test.assertTrue +import kotlin.test.fail +import kotlin.time.Duration import kotlin.time.Duration.Companion.minutes +import kotlin.time.Duration.Companion.seconds // NOTE: Copy from :base:commonTest. It is unclear if there is an easy way to share test code like // this between :base and :sync @@ -70,3 +74,18 @@ suspend inline fun SyncSession.uploadAllLocalChangesOrFail() { val timeout = 5.minutes assertTrue(this.uploadAllLocalChanges(timeout), "Failed to upload local changes in time: $timeout") } + +suspend fun retry(action: suspend () -> R, until: (R) -> Boolean, retries: Int = 5, delay: Duration = 1.seconds): R { + repeat(retries) { + action().let { + println("block: $it") + println("predicate(it): ${until(it)}") + if (until(it)) { + return it + } else { + delay(delay) + } + } + } + fail("Exceeded retries") +} From ab6d33e2975058d54a4c252db3d7c92f48f76514 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Thu, 11 Apr 2024 13:07:56 +0200 Subject: [PATCH 28/36] Increace device farm timeout --- .github/actions/run-android-device-farm-test/action.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/run-android-device-farm-test/action.yml b/.github/actions/run-android-device-farm-test/action.yml index 2d15f48b8f..524aac798c 100644 --- a/.github/actions/run-android-device-farm-test/action.yml +++ b/.github/actions/run-android-device-farm-test/action.yml @@ -41,6 +41,7 @@ runs: test_spec_file: test_spec-${{ inputs.app-id }}.yaml test_spec_type: APPIUM_PYTHON_TEST_SPEC remote_src: true + timeout: 3600 test_spec: | version: 0.1 phases: From 40e01beb8792184611ef7b138ffe7e73ddd8a24d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Mon, 6 May 2024 13:25:04 +0200 Subject: [PATCH 29/36] Remove primary key type generic type paramenter --- .../kotlin/io/realm/kotlin/mongodb/User.kt | 2 +- .../mongodb/exceptions/ServiceExceptions.kt | 2 +- .../kotlin/mongodb/ext/MongoClientExt.kt | 3 +- .../kotlin/mongodb/ext/MongoCollectionExt.kt | 287 +++--------------- .../mongodb/internal/MongoCollectionImpl.kt | 47 ++- .../mongodb/internal/MongoDatabaseImpl.kt | 5 +- .../realm/kotlin/mongodb/mongo/MongoClient.kt | 2 +- .../kotlin/mongodb/mongo/MongoCollection.kt | 7 +- .../kotlin/mongodb/mongo/MongoDatabase.kt | 8 +- .../kotlin/test/mongodb/common/UserTests.kt | 13 +- .../mongodb/common/mongo/MongoClientTests.kt | 26 +- .../common/mongo/MongoCollectionTests.kt | 141 +++++---- .../common/mongo/MongoDatabaseTests.kt | 13 +- .../kotlin/test/mongodb/common/utils/Utils.kt | 4 +- 14 files changed, 184 insertions(+), 376 deletions(-) diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/User.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/User.kt index 8cb6a1fba6..0e4c4a9de7 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/User.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/User.kt @@ -215,7 +215,7 @@ public interface User { * * @param serviceName the name of the data service. * @param eJson the EJson serializer that the [MongoClient] should use to convert objects and - * primary keys with. Will default to the apps [EJson] instance configured with + * primary keys with. Will default to the app's [EJson] instance configured with * [AppConfiguration.Builder.ejson]. For details on configuration of serialization see * [MongoClient]. * throws IllegalStateException if trying to obtain a [MongoClient] from a logged out [User]. diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/exceptions/ServiceExceptions.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/exceptions/ServiceExceptions.kt index 1befe047f5..6d2a4db3c9 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/exceptions/ServiceExceptions.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/exceptions/ServiceExceptions.kt @@ -31,7 +31,7 @@ import io.realm.kotlin.internal.interop.CodeDescription * @see BadRequestException * @see AuthException */ -public open class ServiceException internal constructor( +public open class ServiceException @PublishedApi internal constructor( message: String, internal val errorCode: CodeDescription? = null ) : AppException(message) diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/MongoClientExt.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/MongoClientExt.kt index 185c00ff70..de25ed973f 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/MongoClientExt.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/MongoClientExt.kt @@ -36,12 +36,11 @@ import org.mongodb.kbson.serialization.EJson * primary keys with. Will default to the databases [EJson] instance. * @param T the schema type indicating which for which remote entities of the collection will be * serialized from and to. - * @param K the default type that primary keys will be serialized into. * @return a [MongoCollection] that will accept and return entities from the remote collection * as [T] values. */ @ExperimentalKBsonSerializerApi -public inline fun MongoClient.collection(eJson: EJson? = null): MongoCollection { +public inline fun MongoClient.collection(eJson: EJson? = null): MongoCollection { @Suppress("invisible_reference", "invisible_member") return MongoClientCollection(this as MongoClientImpl, realmObjectCompanionOrThrow(T::class).io_realm_kotlin_className, eJson ?: this.eJson) } diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/MongoCollectionExt.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/MongoCollectionExt.kt index 1d5a73c220..60cba26cf2 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/MongoCollectionExt.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/MongoCollectionExt.kt @@ -17,10 +17,12 @@ package io.realm.kotlin.mongodb.ext import io.realm.kotlin.internal.util.Validation +import io.realm.kotlin.mongodb.exceptions.ServiceException import io.realm.kotlin.mongodb.internal.MongoCollectionImpl import io.realm.kotlin.mongodb.internal.decodeFromBsonValue import io.realm.kotlin.mongodb.internal.decodeFromBsonValueList import io.realm.kotlin.mongodb.internal.encodeToBsonValue +import io.realm.kotlin.mongodb.internal.toAny import io.realm.kotlin.mongodb.mongo.MongoCollection import org.mongodb.kbson.BsonDocument import org.mongodb.kbson.BsonValue @@ -34,8 +36,8 @@ import kotlin.jvm.JvmName * applied. * @throws ServiceException if the underlying App Service HTTP requests fails. */ -public suspend fun MongoCollection<*, *>.count(filter: BsonDocument? = null, limit: Long? = null): Long { - Validation.isType>(this) +public suspend fun MongoCollection<*>.count(filter: BsonDocument? = null, limit: Long? = null): Long { + Validation.isType>(this) return count(filter, limit) } @@ -51,28 +53,11 @@ public suspend fun MongoCollection<*, *>.count(filter: BsonDocument? = null, lim * @throws ServiceException if the underlying App Service HTTP requests fails. * @throws SerializationException if App Service response could not be deserialized to [T]. */ -public suspend inline fun MongoCollection.findOne(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null): T? { - Validation.isType>(this) +public suspend inline fun MongoCollection.findOne(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null): T? { + Validation.isType>(this) val bsonValue: BsonValue = findOne(filter, projection, sort) - return decodeFromBsonValue(bsonValue) -} - -/** - * Retrieve a single object from the remote collection. - * - * @param filter a filter to select specific documents. If `null` then no filtering will be done. - * @param projection a BsonDocument that describes which fields that are returned from the server. - * If `null` then all fields will be returned. - * @param sort a document describing one or more fields used to sort documents before selecting the - * single document to return. If `null` then no sorting will be applied. - * @param T the type that the result of the remote `findOne` invocation should be deserialized into. - * @return the result of the remote `findOne` invocation deserialized into a [T]-instance. - * @throws ServiceException if the underlying App Service HTTP requests fails. - * @throws SerializationException if App Service response could not be deserialized to [T]. - */ -@JvmName("findOneTyped") -public suspend inline fun MongoCollection<*, *>.findOne(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null): T? { - return (this as MongoCollection).findOne(filter, projection, sort) + val decodeFromBsonValue: T? = decodeFromBsonValue(bsonValue) + return decodeFromBsonValue } /** @@ -89,31 +74,11 @@ public suspend inline fun MongoCollection<*, *>.findOne(filter: Bson * @throws ServiceException if the underlying App Service HTTP requests fails. * @throws SerializationException if App Service response could not be deserialized to `List`. */ -public suspend inline fun MongoCollection.find(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null, limit: Long? = null): List { - Validation.isType>(this) +public suspend inline fun MongoCollection.find(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null, limit: Long? = null): List { + Validation.isType>(this) return find(filter, projection, sort, limit).asArray().map { decodeFromBsonValue(it) } } -/** - * Retrieve multiple object from the remote collection. - * - * @param filter a filter to select specific documents. If `null` then no filtering will be done. - * @param projection a BsonDocument that describes which fields that are returned from the server. - * If `null` then all fields will be returned. - * @param sort a document describing one or more fields used to sort documents before selecting the - * single document to return. If `null` then no sorting will be applied. - * @param limit an upper bound of the number of documents to consider. If `null` then no limit is - * applied. - * @param T the type that the results of the remote `find` invocation should be deserialized into. - * @return the result of the remote `find` invocation deserialized into a list of [T]-instances. - * @throws ServiceException if the underlying App Service HTTP requests fails. - * @throws SerializationException if App Service response could not be deserialized to `List`. - */ -@JvmName("findTyped") -public suspend inline fun MongoCollection<*, *>.find(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null, limit: Long? = null): List { - return (this as MongoCollection).find(filter, projection, sort, limit) -} - /** * Execute an aggregate pipeline on the remote collection. * @@ -122,69 +87,23 @@ public suspend inline fun MongoCollection<*, *>.find(filter: BsonDoc * @throws ServiceException if the underlying App Service HTTP requests fails. * @throws SerializationException if App Service response could not be deserialized to `List`. */ -public suspend inline fun MongoCollection.aggregate(pipeline: List): List { - Validation.isType>(this) +public suspend inline fun MongoCollection<*>.aggregate(pipeline: List): List { + Validation.isType>(this) return decodeFromBsonValueList(aggregate(pipeline)) } -/** - * Execute an aggregate pipeline on the remote collection. - * - * @param pipeline a list of aggregation pipeline stages. - * @param T the type that the results of the remote `find` invocation should be deserialized into. - * @return the result of the remote `aggregate` invocation deserialized into a list of [T]-instances. - * @throws ServiceException if the underlying App Service HTTP requests fails. - * @throws SerializationException if App Service response could not be deserialized to `List`. - */ -@JvmName("aggregateTyped") -public suspend inline fun MongoCollection<*, *>.aggregate(pipeline: List): List { - return (this as MongoCollection).aggregate(pipeline) -} - -/** - * Insert a single object into the remote collection. - * - * @param document the object to serialize and insert into the remote collection. - * @return the `_id` value of the document insert in the collection deserialized to a [R]-instance. - * @throws ServiceException if the underlying App Service HTTP requests fails. - * @throws SerializationException if [document] could not be serialized into a EJson document or if - * the App Service response could not be deserialized to [R]. - */ -public suspend inline fun MongoCollection.insertOne(document: T): R { - Validation.isType>(this) - return decodeFromBsonValue(insertOne(encodeToBsonValue(document).asDocument())) -} - /** * Insert a single object into the remote collection. * * @param document the object to serialize and insert into the remote collection. - * @param T the type of object that should be serializer and inserted to the collection. - * @param R the type that the returned `_id` value should be deserialized into. - * @return the `_id` value of the document inserted in the collection deserialized to a [R]-instance. + * @return the `_id` value of the document insert in the collection deserialized to the most appropriate type. * @throws ServiceException if the underlying App Service HTTP requests fails. * @throws SerializationException if [document] could not be serialized into a EJson document or if - * the App Service response could not be deserialized to [R]. + * the App Service response could not be parsed into a reasonable type. */ -@JvmName("insertOneTyped") -public suspend inline fun MongoCollection<*, *>.insertOne(document: T): R { - return (this as MongoCollection).insertOne(document) -} - -/** - * Insert a list of object into the remote collection. - * - * @param documents the objects to serialize and insert into the remote collection. - * @return the `_id` values of the documents inserted in the collection deserialized to a [R]-instance. - * @throws ServiceException if the underlying App Service HTTP requests fails. - * @throws SerializationException if [documents] could not be serialized into a EJson document or if - * the App Service response could not be deserialized to [R]. - */ -public suspend inline fun MongoCollection.insertMany( - documents: Collection, -): List { - Validation.isType>(this) - return decodeFromBsonValueList(insertMany(documents.map { encodeToBsonValue(it).asDocument() })) +public suspend inline fun MongoCollection.insertOne(document: T): Any { + Validation.isType>(this) + return insertOne(encodeToBsonValue(document).asDocument()).toAny() ?: throw ServiceException("No primary key for inserted document") } /** @@ -199,8 +118,10 @@ public suspend inline fun MongoCollection`. */ @JvmName("insertManyTyped") -public suspend inline fun MongoCollection<*, *>.insertMany(documents: Collection): List { - return (this as MongoCollection).insertMany(documents) +public suspend inline fun MongoCollection<*>.insertMany(documents: Collection): List { + Validation.isType>(this) + val bsonValues: List = insertMany(documents.map { encodeToBsonValue(it).asDocument() }) + return bsonValues.map { it.toAny() ?: throw ServiceException("Response should not contain null values: $bsonValues") } } /** @@ -210,8 +131,8 @@ public suspend inline fun MongoCollection<*, * @return a boolean indicating if a document was deleted or not. * @throws ServiceException if the underlying App Service HTTP requests fails. */ -public suspend fun MongoCollection<*, *>.deleteOne(filter: BsonDocument): Boolean { - Validation.isType>(this) +public suspend fun MongoCollection<*>.deleteOne(filter: BsonDocument): Boolean { + Validation.isType>(this) return deleteOne(filter) } @@ -222,8 +143,8 @@ public suspend fun MongoCollection<*, *>.deleteOne(filter: BsonDocument): Boolea * @return the number of documents that have been deleted. * @throws ServiceException if the underlying App Service HTTP requests fails. */ -public suspend fun MongoCollection<*, *>.deleteMany(filter: BsonDocument): Long { - Validation.isType>(this) +public suspend fun MongoCollection<*>.deleteMany(filter: BsonDocument): Long { + Validation.isType>(this) return deleteMany(filter) } @@ -233,7 +154,7 @@ public suspend fun MongoCollection<*, *>.deleteMany(filter: BsonDocument): Long * @param updated boolean indicating that a document was updated. * @param upsertedId primary key of the new document if created. */ -public data class UpdateOneResult(val updated: Boolean, val upsertedId: R?) +public data class UpdateOneResult(val updated: Boolean, val upsertedId: Any?) /** * Update or insert a single object in the remote collection. @@ -247,46 +168,24 @@ public data class UpdateOneResult(val updated: Boolean, val upsertedId: R?) * @throws SerializationException if App Service response could not be deserialized to * [UpdateOneResult]. */ -public suspend inline fun MongoCollection.updateOne( +public suspend inline fun MongoCollection<*>.updateOne( filter: BsonDocument, update: BsonDocument, upsert: Boolean = false -): UpdateOneResult { - Validation.isType>(this) +): UpdateOneResult { + Validation.isType>(this) return updateOne(filter, update, upsert).let { (updated, upsertedId) -> - UpdateOneResult(updated, upsertedId?.let { decodeFromBsonValue(it) }) + UpdateOneResult(updated, upsertedId?.let { it.toAny() }) } } -/** - * Update or insert a single object in the remote collection. - * - * @param filter a filter to select the document to update. - * @param update a BsonDocument specifying the updates that should be applied to the document. - * @param upsert a boolean indicating if a new document should be inserted if the [filter] does not - * match any existing documents in the collection. - * @param R the type that the returned `_id` of a newly insert document should be deserialized into. - * @return the result of the `updateOne` operation. - * @throws ServiceException if the underlying App Service HTTP requests fails. - * @throws SerializationException if App Service response could not be deserialized to - * [UpdateOneResult]. - */ -@JvmName("updateOneTyped") -public suspend inline fun MongoCollection<*, *>.updateOne( - filter: BsonDocument, - update: BsonDocument, - upsert: Boolean = false -): UpdateOneResult { - return (this as MongoCollection).updateOne(filter, update, upsert) -} - /** * Wrapper of results of an [updateMany] call. * * @param modifiedCount number of documents that was updated by the operation. * @param upsertedId primary key of the new document if created. */ -public data class UpdateManyResult(val modifiedCount: Long, val upsertedId: R?) +public data class UpdateManyResult(val modifiedCount: Long, val upsertedId: Any?) /** * Update multiple objects or insert a single new object in the remote collection. @@ -300,39 +199,17 @@ public data class UpdateManyResult(val modifiedCount: Long, val upsertedId: R * @throws SerializationException if App Service response could not be deserialized to * [UpdateManyResult]. */ -public suspend inline fun MongoCollection.updateMany( +public suspend inline fun MongoCollection<*>.updateMany( filter: BsonDocument, update: BsonDocument, upsert: Boolean = false -): UpdateManyResult { - Validation.isType>(this) +): UpdateManyResult { + Validation.isType>(this) return updateMany(filter, update, upsert).let { (updatedCount, upsertedId) -> - UpdateManyResult(updatedCount, upsertedId?.let { decodeFromBsonValue(it) }) + UpdateManyResult(updatedCount, upsertedId?.toAny()) } } -/** - * Update multiple objects or insert a single new object in the remote collection. - * - * @param filter a filter to select the documents to update. - * @param update a BsonDocument specifying the updates that should be applied to the documents. - * @param upsert a boolean indicating if a new document should be inserted if the [filter] does not - * match any existing documents in the collection. - * @param R the type that the returned `_id` of a newly insert document should be deserialized into. - * @return the result of the `updateMany` operation. - * @throws ServiceException if the underlying App Service HTTP requests fails. - * @throws SerializationException if App Service response could not be deserialized to - * [UpdateManyResult]. - */ -@JvmName("updateManyTyped") -public suspend inline fun MongoCollection<*, *>.updateMany( - filter: BsonDocument, - update: BsonDocument, - upsert: Boolean = false -): UpdateManyResult { - return (this as MongoCollection).updateMany(filter, update, upsert) -} - /** * Find and update or insert a single new object in the remote collection. * @@ -350,7 +227,7 @@ public suspend inline fun MongoCollection<*, *>.updateMany( * @throws SerializationException if App Service response could not be deserialized to [T]. */ @Suppress("LongParameterList") -public suspend inline fun MongoCollection.findOneAndUpdate( +public suspend inline fun MongoCollection.findOneAndUpdate( filter: BsonDocument, update: BsonDocument, projection: BsonDocument? = null, @@ -358,40 +235,10 @@ public suspend inline fun MongoCollection.findOneAndU upsert: Boolean = false, returnNewDoc: Boolean = false, ): T? { - Validation.isType>(this) + Validation.isType>(this) return decodeFromBsonValue(findOneAndUpdate(filter, update, projection, sort, upsert, returnNewDoc)) } -/** - * Find and update or insert a single new object in the remote collection. - * - * @param filter a filter to select the documents to update. - * @param update a BsonDocument specifying the updates that should be applied to the documents. - * @param projection a BsonDocument that describes which fields that are returned from the server. - * If `null` then all fields will be returned. - * @param sort a document describing one or more fields used to sort documents before selecting the - * single document to return. If `null` then no sorting will be applied. - * @param upsert a boolean indicating if a new document should be inserted if the [filter] does not - * match any existing documents in the collection. - * @param returnNewDoc a boolean indicating whether to return the document before or after the update. - * @param T the type that the result of the remote `findOne` invocation should be deserialized into. - * @return the result of the remote `findOneAndUpdate` invocation deserialized into a [T]-instance. - * @throws ServiceException if the underlying App Service HTTP requests fails. - * @throws SerializationException if App Service response could not be deserialized to [T]. - */ -@Suppress("LongParameterList") -@JvmName("findAndUpdateTyped") -public suspend inline fun MongoCollection<*, *>.findOneAndUpdate( - filter: BsonDocument, - update: BsonDocument, - projection: BsonDocument? = null, - sort: BsonDocument? = null, - upsert: Boolean = false, - returnNewDoc: Boolean = false, -): T? { - return (this as MongoCollection).findOneAndUpdate(filter, update, projection, sort, upsert, returnNewDoc) -} - /** * Find and replace or insert a single new object in the remote collection. * @@ -409,7 +256,7 @@ public suspend inline fun MongoCollection<*, *>.findOneAndUpdate( * @throws SerializationException if App Service response could not be deserialized to [T]. */ @Suppress("LongParameterList") -public suspend inline fun MongoCollection.findOneAndReplace( +public suspend inline fun MongoCollection.findOneAndReplace( filter: BsonDocument, document: BsonDocument, projection: BsonDocument? = null, @@ -417,40 +264,10 @@ public suspend inline fun MongoCollection.findOneAndR upsert: Boolean = false, returnNewDoc: Boolean = false, ): T? { - Validation.isType>(this) + Validation.isType>(this) return decodeFromBsonValue(findOneAndReplace(filter, document, projection, sort, upsert, returnNewDoc)) } -/** - * Find and replace or insert a single new object in the remote collection. - * - * @param filter a filter to select the documents to update. - * @param document a BsonDocument specifying the updates that should be applied to the documents. - * @param projection a BsonDocument that describes which fields that are returned from the server. - * If `null` then all fields will be returned. - * @param sort a document describing one or more fields used to sort documents before selecting the - * single document to return. If `null` then no sorting will be applied. - * @param upsert a boolean indicating if a new document should be inserted if the [filter] does not - * match any existing documents in the collection. - * @param returnNewDoc a boolean indicating whether to return the document before or after the update. - * @param T the type that the result of the remote `findOne` invocation should be deserialized into. - * @return the result of the remote `findOneAndReplace` invocation deserialized into a [T]-instance. - * @throws ServiceException if the underlying App Service HTTP requests fails. - * @throws SerializationException if App Service response could not be deserialized to [T]. - */ -@Suppress("LongParameterList") -@JvmName("findAndReplaceTyped") -public suspend inline fun MongoCollection<*, *>.findOneAndReplace( - filter: BsonDocument, - update: BsonDocument, - projection: BsonDocument? = null, - sort: BsonDocument? = null, - upsert: Boolean = false, - returnNewDoc: Boolean = false, -): T? { - return (this as MongoCollection).findOneAndReplace(filter, update, projection, sort, upsert, returnNewDoc) -} - /** * Find and delete a single object in the remote collection. * @@ -463,33 +280,11 @@ public suspend inline fun MongoCollection<*, *>.findOneAndReplace( * @throws ServiceException if the underlying App Service HTTP requests fails. * @throws SerializationException if App Service response could not be deserialized to [T]. */ -public suspend inline fun MongoCollection.findOneAndDelete( +public suspend inline fun MongoCollection.findOneAndDelete( filter: BsonDocument, projection: BsonDocument? = null, sort: BsonDocument? = null, ): T? { - Validation.isType>(this) + Validation.isType>(this) return decodeFromBsonValue(findOneAndDelete(filter, projection, sort)) } - -/** - * Find and delete a single object in the remote collection. - * - * @param filter a filter to select the documents to update. - * @param projection a BsonDocument that describes which fields that are returned from the server. - * If `null` then all fields will be returned. - * @param sort a document describing one or more fields used to sort documents before selecting the - * single document to return. If `null` then no sorting will be applied. - * @param T the type that the result of the remote `findOne` invocation should be deserialized into. - * @return the result of the remote `findOneAndDelete` invocation deserialized into a [T]-instance. - * @throws ServiceException if the underlying App Service HTTP requests fails. - * @throws SerializationException if App Service response could not be deserialized to [T]. - */ -@JvmName("findAndDeleteTyped") -public suspend inline fun MongoCollection<*, *>.findOneAndDelete( - filter: BsonDocument, - projection: BsonDocument? = null, - sort: BsonDocument? = null, -): T? { - return (this as MongoCollection).findOneAndDelete(filter, projection, sort) -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt index d90b2040b3..d2ee7e1d0a 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt @@ -24,6 +24,7 @@ import org.mongodb.kbson.BsonDocument import org.mongodb.kbson.BsonInt64 import org.mongodb.kbson.BsonNull import org.mongodb.kbson.BsonString +import org.mongodb.kbson.BsonType import org.mongodb.kbson.BsonValue import org.mongodb.kbson.ExperimentalKBsonSerializerApi import org.mongodb.kbson.serialization.EJson @@ -32,33 +33,33 @@ import org.mongodb.kbson.serialization.encodeToBsonValue @PublishedApi @OptIn(ExperimentalKBsonSerializerApi::class) -internal class MongoDatabaseCollection(@PublishedApi internal val database: MongoDatabaseImpl, override val name: String, eJson: EJson) : MongoCollectionImpl(database.client.functions, eJson) { +internal class MongoDatabaseCollection(@PublishedApi internal val database: MongoDatabaseImpl, override val name: String, eJson: EJson) : MongoCollectionImpl(database.client.functions, eJson) { override val defaults: Map = mapOf( "database" to BsonString(database.name), "collection" to BsonString(name), ) - override fun withDocumentClass(eJson: EJson?): MongoCollection { + override fun withDocumentClass(eJson: EJson?): MongoCollection { return MongoDatabaseCollection(this.database, this.name, eJson ?: this.eJson) } } @PublishedApi @OptIn(ExperimentalKBsonSerializerApi::class) -internal class MongoClientCollection(@PublishedApi internal val clientImpl: MongoClientImpl, override val name: String, eJson: EJson) : MongoCollectionImpl(clientImpl.functions, eJson) { +internal class MongoClientCollection(@PublishedApi internal val clientImpl: MongoClientImpl, override val name: String, eJson: EJson) : MongoCollectionImpl(clientImpl.functions, eJson) { override val defaults: Map = mapOf( "schema_name" to BsonString(name), ) - override fun withDocumentClass(eJson: EJson?): MongoCollection { + override fun withDocumentClass(eJson: EJson?): MongoCollection { return MongoClientCollection(clientImpl, name, eJson ?: this.eJson) } } @PublishedApi @OptIn(ExperimentalKBsonSerializerApi::class) -internal abstract class MongoCollectionImpl constructor( +internal abstract class MongoCollectionImpl constructor( val functions: FunctionsImpl, val eJson: EJson, -) : MongoCollection { +) : MongoCollection { // Default entries for the argument document submitted for the function call. abstract val defaults: Map @@ -214,13 +215,13 @@ internal abstract class MongoCollectionImpl constructor( @OptIn(ExperimentalKBsonSerializerApi::class) @PublishedApi -internal inline fun MongoCollectionImpl<*, *>.encodeToBsonValue(value: R): BsonValue { +internal inline fun MongoCollectionImpl<*>.encodeToBsonValue(value: R): BsonValue { return eJson.encodeToBsonValue(value) } @OptIn(ExperimentalKBsonSerializerApi::class) @PublishedApi -internal inline fun MongoCollectionImpl<*, *>.decodeFromBsonValue(bsonValue: BsonValue): R = +internal inline fun MongoCollectionImpl<*>.decodeFromBsonValue(bsonValue: BsonValue): R = when { bsonValue == BsonNull -> null as R R::class == BsonValue::class -> bsonValue as R @@ -229,10 +230,38 @@ internal inline fun MongoCollectionImpl<*, *>.decodeFromBsonValue(bs @OptIn(ExperimentalKBsonSerializerApi::class) @PublishedApi -internal inline fun MongoCollectionImpl<*, *>.decodeFromBsonValueList(bsonValues: List): List { +internal inline fun MongoCollectionImpl<*>.decodeFromBsonValueList(bsonValues: List): List { return if (R::class == BsonValue::class) { bsonValues as List } else { bsonValues.map { eJson.decodeFromBsonValue(it) } } } + +@Suppress("ComplexMethod") +public fun BsonValue.toAny(): Any? { + return when (this.bsonType) { + BsonType.NULL -> null + BsonType.INT32 -> asInt32().value + BsonType.INT64 -> asInt64().value + BsonType.OBJECT_ID -> this.asObjectId() + BsonType.STRING -> this.asString().value + BsonType.DOUBLE -> this.asDouble().value + BsonType.BINARY -> this.asBinary().data + BsonType.BOOLEAN -> this.asBoolean().value + BsonType.DATE_TIME -> this.asDateTime() + BsonType.ARRAY -> this.asArray().values.map { it.toAny() } + BsonType.DOCUMENT -> this.asDocument().mapValues { (k, v) -> v.toAny() } + BsonType.TIMESTAMP -> asTimestamp() + BsonType.DECIMAL128 -> asDecimal128() + BsonType.DB_POINTER, + BsonType.JAVASCRIPT, + BsonType.SYMBOL, + BsonType.JAVASCRIPT_WITH_SCOPE, + BsonType.REGULAR_EXPRESSION, + BsonType.MIN_KEY, + BsonType.MAX_KEY, + BsonType.END_OF_DOCUMENT, + BsonType.UNDEFINED -> this + } +} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDatabaseImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDatabaseImpl.kt index 85f525cb9b..49fc55b8e5 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDatabaseImpl.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDatabaseImpl.kt @@ -19,7 +19,6 @@ package io.realm.kotlin.mongodb.internal import io.realm.kotlin.mongodb.mongo.MongoCollection import io.realm.kotlin.mongodb.mongo.MongoDatabase import org.mongodb.kbson.BsonDocument -import org.mongodb.kbson.BsonValue import org.mongodb.kbson.ExperimentalKBsonSerializerApi import org.mongodb.kbson.serialization.EJson @@ -32,10 +31,10 @@ internal class MongoDatabaseImpl constructor( val eJson: EJson, ) : MongoDatabase { - override fun collection(collectionName: String): MongoCollection { + override fun collection(collectionName: String): MongoCollection { return MongoDatabaseCollection(this, collectionName, this.eJson) } - override fun collection(collectionName: String, eJson: EJson?): MongoCollection = + override fun collection(collectionName: String, eJson: EJson?): MongoCollection = MongoDatabaseCollection(this, collectionName, eJson ?: this.eJson) } diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt index 59b4573172..c57ff6c61d 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt @@ -64,7 +64,7 @@ import kotlin.reflect.KClass * property set. All other properties from the realm objects will have the default values specified * in the class definition. * - * *NOTE* The EJSON serializer requires to opt-in to the experimental. + * *NOTE* The EJSON serializer requires to opt-in to the experimental [ExperimentalKBsonSerializerApi]. */ public interface MongoClient { diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt index e1410a184d..da07302a66 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt @@ -28,7 +28,7 @@ import org.mongodb.kbson.serialization.EJson * for a detailed description of methods and arguments. * * Input arguments and responses to the App Service HTTP requests will be serialized from and to - * the type [T] and [K] using [Kotlin's Serialization framework](https://kotlinlang.org/docs/serialization.html) + * the type [T] using [Kotlin's Serialization framework](https://kotlinlang.org/docs/serialization.html) * and can be customized by [Serializable]-annotations or customizing the [EJson]-serializer passed * to the various [MongoClient], [MongoDatabase] and [MongoCollection]-factory methods. For details * on configuring the serialization see [MongoClient]. @@ -40,9 +40,8 @@ import org.mongodb.kbson.serialization.EJson * * @param T the default type that remote entities of the collection will be serialized from and * to. - * @param K the default type that primary keys will be serialized into. */ -public interface MongoCollection { +public interface MongoCollection { /** * Name of the remote collection. @@ -57,5 +56,5 @@ public interface MongoCollection { * configuration of serialization see [MongoClient]. */ @ExperimentalKBsonSerializerApi - public fun withDocumentClass(eJson: EJson? = null): MongoCollection + public fun withDocumentClass(eJson: EJson? = null): MongoCollection } diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoDatabase.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoDatabase.kt index 2843eb9c60..f9dc739cde 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoDatabase.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoDatabase.kt @@ -17,7 +17,6 @@ package io.realm.kotlin.mongodb.mongo import org.mongodb.kbson.BsonDocument -import org.mongodb.kbson.BsonValue import org.mongodb.kbson.ExperimentalKBsonSerializerApi import org.mongodb.kbson.serialization.EJson @@ -41,9 +40,9 @@ public interface MongoDatabase { * @param collectionName the name of the collection name that the [MongoCollection] will * connect to. * @return a [MongoCollection] that will accept and return entities from the remote collection - * as [BsonValue] values. + * as [BsonDocument] values. */ - public fun collection(collectionName: String): MongoCollection + public fun collection(collectionName: String): MongoCollection /** * Get a [MongoCollection] that exposed methods to retrieve and update data from the database's @@ -59,10 +58,9 @@ public interface MongoDatabase { * configuration of serialization see [MongoClient]. * @param T the default type that remote entities of the collection will be serialized from and * to. - * @param K the default type that primary keys will be serialized into. * @return a [MongoCollection] that will accept and return entities from the remote collection * as [T] values. */ @ExperimentalKBsonSerializerApi - public fun collection(collectionName: String, eJson: EJson? = null): MongoCollection + public fun collection(collectionName: String, eJson: EJson? = null): MongoCollection } diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserTests.kt index 9125e5ecbc..e85bda5cc2 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserTests.kt @@ -36,7 +36,6 @@ import io.realm.kotlin.mongodb.sync.SyncConfiguration import io.realm.kotlin.test.mongodb.TestApp import io.realm.kotlin.test.mongodb.asTestApp import io.realm.kotlin.test.mongodb.common.mongo.CustomDataType -import io.realm.kotlin.test.mongodb.common.mongo.CustomIdType import io.realm.kotlin.test.mongodb.common.mongo.TEST_SERVICE_NAME import io.realm.kotlin.test.mongodb.common.mongo.customEjsonSerializer import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage @@ -47,9 +46,7 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.SerializationException import org.mongodb.kbson.BsonDocument import org.mongodb.kbson.BsonString -import org.mongodb.kbson.BsonValue import org.mongodb.kbson.ExperimentalKBsonSerializerApi -import org.mongodb.kbson.ObjectId import org.mongodb.kbson.serialization.Bson import kotlin.test.AfterTest import kotlin.test.BeforeTest @@ -745,7 +742,7 @@ class UserTests { } @OptIn(ExperimentalRealmSerializerApi::class) val client: MongoClient = user.mongoClient(TEST_SERVICE_NAME) - assertIs(client.database(app.clientAppId).collection("CollectionDataType").insertOne(CollectionDataType("object-1"))) + assertIs(client.database(app.clientAppId).collection("CollectionDataType").insertOne(CollectionDataType("object-1"))) } @Test @@ -758,15 +755,15 @@ class UserTests { val collectionWithDefaultSerializer = user.mongoClient(TEST_SERVICE_NAME) .database(app.clientAppId) - .collection("CollectionDataType") + .collection("CollectionDataType") assertFailsWithMessage("Serializer for class 'CustomDataType' is not found.") { collectionWithDefaultSerializer.insertOne(CustomDataType("dog-1")) } val collectionWithCustomSerializer = user.mongoClient(TEST_SERVICE_NAME, customEjsonSerializer).database(app.clientAppId) - .collection("CollectionDataType") - assertIs(collectionWithCustomSerializer.insertOne(CustomDataType("dog-1"))) + .collection("CollectionDataType") + assertIs(collectionWithCustomSerializer.insertOne(CustomDataType("dog-1"))) } @Test @@ -778,7 +775,7 @@ class UserTests { } val mongoClient = user.mongoClient("UNKNOWN_SERVICE") val collection = - mongoClient.database(app.clientAppId).collection("CollectionDataType") + mongoClient.database(app.clientAppId).collection("CollectionDataType") assertFailsWithMessage("Cannot access member 'insertOne' of undefined") { collection.insertOne(CollectionDataType("object-1")) } diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTests.kt index 327b2c2cee..10287cdfe1 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTests.kt @@ -26,9 +26,7 @@ import io.realm.kotlin.test.mongodb.TEST_APP_FLEX import io.realm.kotlin.test.mongodb.TestApp import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage import kotlinx.serialization.SerializationException -import org.mongodb.kbson.BsonValue import org.mongodb.kbson.ExperimentalKBsonSerializerApi -import org.mongodb.kbson.ObjectId import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test @@ -67,45 +65,45 @@ class MongoClientTests { @Test fun database_defaultSerializer() = runBlocking { - assertIs(client.database(app.clientAppId).collection("CollectionDataType").insertOne(CollectionDataType("object-1"))) + assertIs(client.database(app.clientAppId).collection("CollectionDataType").insertOne(CollectionDataType("object-1"))) } @Test @OptIn(ExperimentalKBsonSerializerApi::class) fun database_customSerializer() = runBlocking { val collectionWithDefaultSerializer = client.database(app.clientAppId) - .collection("CollectionDataType") + .collection("CollectionDataType") assertFailsWithMessage("Serializer for class 'CustomDataType' is not found.") { collectionWithDefaultSerializer.insertOne(CustomDataType("object-1")) } val collectionWithCustomSerializer = client.database(app.clientAppId, customEjsonSerializer) - .collection("CollectionDataType") - assertIs(collectionWithCustomSerializer.insertOne(CustomDataType("object-1"))) + .collection("CollectionDataType") + assertIs(collectionWithCustomSerializer.insertOne(CustomDataType("object-1"))) } @Test fun database_createsCollectionOnInsertToUnknownDatabase() = runBlocking { val database = client.database("Unknown") - val collection = database.collection("NewCollection") - assertIs(collection.insertOne(CollectionDataType("object-1"))) + val collection = database.collection("NewCollection") + assertIs(collection.insertOne(CollectionDataType("object-1")) as Int) } @Test fun collection_defaultSerializer() = runBlocking { - assertIs(client.collection().insertOne(CollectionDataType("object-1"))) + assertIs(client.collection().insertOne(CollectionDataType("object-1"))) } @Test fun collection_customSerializer() = runBlocking { - val collectionWithDefaultSerializer = client.collection() + val collectionWithDefaultSerializer = client.collection() assertFailsWithMessage("Serializer for class 'CustomDataType' is not found.") { - collectionWithDefaultSerializer.insertOne(CustomDataType("object-1")) + collectionWithDefaultSerializer.withDocumentClass().insertOne(CustomDataType("object-1")) } - val collectionWithCustomSerializer = client.collection( + val collectionWithCustomSerializer = client.collection( customEjsonSerializer ) - assertIs( + assertIs( collectionWithCustomSerializer.insertOne( CustomDataType("object-1") ) @@ -114,7 +112,7 @@ class MongoClientTests { @Test fun collection_unknownSchemaType() = runBlocking { - val collectionWithDefaultSerializer = client.collection() + val collectionWithDefaultSerializer = client.collection() assertFailsWithMessage("no matching collection found that maps to a table with title \"NonSchemaType\".") { collectionWithDefaultSerializer.insertOne(NonSchemaType()) } diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt index a267c6577c..ff05778d8c 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt @@ -114,7 +114,7 @@ class MongoCollectionFromDatabaseTests : MongoCollectionTests() { // Unknown collections will create the collection if inserting document, so only use // NonSchemaType for queries @OptIn(ExperimentalKBsonSerializerApi::class) - val unknownCollection = collection() + val unknownCollection = collection() assertNull(unknownCollection.findOne()) } } @@ -135,14 +135,14 @@ class MongoCollectionFromClientTests : MongoCollectionTests() { @Test fun name_persistedName() { @OptIn(ExperimentalKBsonSerializerApi::class) - assertEquals("CollectionDataType", client.collection().name) + assertEquals("CollectionDataType", client.collection().name) } @OptIn(ExperimentalKBsonSerializerApi::class) @Test override fun findOne_unknownCollection() = runBlocking { @OptIn(ExperimentalKBsonSerializerApi::class) - val unknownCollection = collection() + val unknownCollection = collection() assertFailsWithMessage("no matching collection found that maps to a table with title \"NonSchemaType\"") { unknownCollection.findOne() } @@ -154,7 +154,7 @@ sealed class MongoCollectionTests { lateinit var app: TestApp lateinit var user: User lateinit var client: MongoClient - lateinit var collection: MongoCollection + lateinit var collection: MongoCollection @BeforeTest open fun setUp() { @@ -212,8 +212,8 @@ sealed class MongoCollectionTests { // Reshaped @OptIn(ExperimentalKBsonSerializerApi::class) - val bsonCollection: MongoCollection = collection.withDocumentClass() - assertIs(bsonCollection.insertOne(BsonDocument("_id" to BsonInt32(Random.nextInt()), "name" to BsonString("object-2")))) + val bsonCollection: MongoCollection = collection.withDocumentClass() + assertIs(bsonCollection.insertOne(BsonDocument("_id" to BsonInt32(Random.nextInt()), "name" to BsonString("object-2")))) assertIs(bsonCollection.findOne()) assertEquals(2, bsonCollection.count()) } @@ -221,7 +221,7 @@ sealed class MongoCollectionTests { @Test fun withDocumentClass_withCustomSerialization() = runBlocking { @OptIn(ExperimentalKBsonSerializerApi::class) - val reshapedCollectionWithDefaultSerializer: MongoCollection = + val reshapedCollectionWithDefaultSerializer: MongoCollection = collection.withDocumentClass() assertFailsWithMessage("Serializer for class 'CustomDataType' is not found.") { @@ -229,10 +229,10 @@ sealed class MongoCollectionTests { } @OptIn(ExperimentalKBsonSerializerApi::class) - val reshapedCollectionWithCustomSerializer: MongoCollection = + val reshapedCollectionWithCustomSerializer: MongoCollection = collection.withDocumentClass(customEjsonSerializer) - assertIs(reshapedCollectionWithCustomSerializer.insertOne(CustomDataType("object-2"))) + assertIs(reshapedCollectionWithCustomSerializer.insertOne(CustomDataType("object-2"))) } @Test @@ -316,15 +316,17 @@ sealed class MongoCollectionTests { } } + @OptIn(ExperimentalKBsonSerializerApi::class) @Test fun findOne_explicitTypes() = runBlocking { // Empty collection - assertNull(collection.findOne()) + assertNull(collection.findOne()) collection.insertMany((1..10).map { CollectionDataType("object-${it % 5}") }) // Explicit types - collection.findOne(filter = BsonDocument("name", "object-0")).run { + val findOne: BsonDocument? = collection.withDocumentClass().findOne(filter = BsonDocument("name", "object-0")) + findOne.run { assertIs(this) assertEquals("object-0", this["name"]!!.asString().value) } @@ -346,7 +348,7 @@ sealed class MongoCollectionTests { realm.syncSession.uploadAllLocalChanges(30.seconds) @OptIn(ExperimentalKBsonSerializerApi::class) - val parentCollection = collection() + val parentCollection = collection() val mongoDBClientParent = retry( { parentCollection.findOne() }, @@ -378,7 +380,7 @@ sealed class MongoCollectionTests { realm.syncSession.uploadAllLocalChanges(30.seconds) @OptIn(ExperimentalKBsonSerializerApi::class) - val parentCollection = collection() + val parentCollection = collection() val mongoDBClientParent = retry( action = { parentCollection.findOne() }, @@ -410,7 +412,7 @@ sealed class MongoCollectionTests { realm.syncSession.uploadAllLocalChanges(30.seconds) @OptIn(ExperimentalKBsonSerializerApi::class) - val parentCollection = collection( + val parentCollection = collection( EJson( serializersModule = realmSerializerModule( setOf(ParentCollectionDataType::class) @@ -435,7 +437,7 @@ sealed class MongoCollectionTests { // Empty collections assertNull(collection.findOne()) - val parentCollection = collection() + val parentCollection = collection() parentCollection.insertOne( ParentCollectionDataType().apply { embeddedChild = EmbeddedChildCollectionDataType().apply { name = "EMBEDDED-NAME" } @@ -447,9 +449,11 @@ sealed class MongoCollectionTests { } } + @OptIn(ExperimentalKBsonSerializerApi::class) @Test fun findOne_extraFieldsAreDiscarded() = runBlocking { - collection.insertOne( + val withDocumentClass = collection.withDocumentClass() + withDocumentClass.insertOne( BsonDocument( mapOf( "_id" to BsonInt32(Random.nextInt()), @@ -460,7 +464,7 @@ sealed class MongoCollectionTests { ) // Show that remote method returns extra properties - collection.findOne()!!.let { + withDocumentClass.findOne()!!.let { assertEquals("extra", it["extra"]!!.asString().value) } // But these properties are silently discarded by the serialization framework @@ -493,7 +497,7 @@ sealed class MongoCollectionTests { assertTrue { collection.find().isEmpty() } val names = (1..10).map { "object-${it % 5}" } - val ids: List = collection.insertMany(names.map { CollectionDataType(it) }) + val ids: List = collection.insertMany(names.map { CollectionDataType(it) }) assertEquals(10, collection.find().size) @@ -542,13 +546,16 @@ sealed class MongoCollectionTests { } @Test + @OptIn(ExperimentalKBsonSerializerApi::class) fun find_explicitTypes() = runBlocking { - collection.find().let { assertTrue { it.isEmpty() } } + collection.find().let { assertTrue { it.isEmpty() } } + + val reshapedCollection = collection.withDocumentClass() val names = (1..10).map { "object-${it % 5}" } - collection.insertMany(names.map { BsonDocument("_id" to BsonInt32(Random.nextInt()), "name" to BsonString(it)) }) + reshapedCollection.insertMany(names.map { BsonDocument("_id" to BsonInt32(Random.nextInt()), "name" to BsonString(it)) }) - collection.find().let { results -> + reshapedCollection.find().let { results -> results.forEach { assertIs(it) assertTrue { it.asDocument()["name"]!!.asString().value in names } @@ -559,16 +566,16 @@ sealed class MongoCollectionTests { @Test fun find_fails() = runBlocking { assertFailsWithMessage("unknown top level operator: \$who.") { - collection.find(BsonDocument("\$who", 1)).first() + collection.find(BsonDocument("\$who", 1)).first() } } @Test fun aggregate() = runBlocking { - collection.aggregate(listOf()).let { assertTrue { it.isEmpty() } } + collection.aggregate(listOf()).let { assertTrue { it.isEmpty() } } val names = (1..10).map { "object-${it % 5}" } - val ids: List = collection.insertMany(names.map { CollectionDataType(it) }) + val ids: List = collection.insertMany(names.map { CollectionDataType(it) }) collection.aggregate(listOf()).let { assertEquals(10, it.size) @@ -588,7 +595,7 @@ sealed class MongoCollectionTests { @Test fun aggregate_fails() = runBlocking { assertFailsWithMessage("Unrecognized pipeline stage name: '\$who'.") { - collection.aggregate(pipeline = listOf(BsonDocument("\$who", 1))) + collection.aggregate(pipeline = listOf(BsonDocument("\$who", 1))) } } @@ -631,7 +638,7 @@ sealed class MongoCollectionTests { assertTrue { it.list.isEmpty() } } - val childCollection = collection() + val childCollection = collection() val unmanagedChild = ChildCollectionDataType() assertEquals(unmanagedChild._id, childCollection.insertOne(unmanagedChild)) // We can't rely on the translator to incorporate the insertOnes in order so we need to @@ -640,7 +647,7 @@ sealed class MongoCollectionTests { assertEquals(unmanagedChild._id, it.list.first()._id) } - val parentCollection = collection() + val parentCollection = collection() val unmanagedParent = ParentCollectionDataType().apply { this.child = unmanagedChild } @@ -693,7 +700,7 @@ sealed class MongoCollectionTests { assertTrue { it.list.isEmpty() } } - val childCollection = collection() + val childCollection = collection() assertEquals(0, childCollection.find().size) val unmanagedChild = ChildCollectionDataType() RealmLog.level = LogLevel.ALL @@ -704,7 +711,7 @@ sealed class MongoCollectionTests { assertEquals(unmanagedChild._id, it.list.first()._id) } - val parentCollection = collection() + val parentCollection = collection() val unmanagedParent = ParentCollectionDataType().apply { this.any = RealmAny.create(unmanagedChild) } @@ -732,7 +739,7 @@ sealed class MongoCollectionTests { @OptIn(ExperimentalKBsonSerializerApi::class) @Test fun insertOne_embeddedObjects() = runBlocking { - val parentCollection = collection() + val parentCollection = collection() // Empty collections assertNull(parentCollection.findOne()) parentCollection.insertOne( @@ -746,16 +753,17 @@ sealed class MongoCollectionTests { } } + @OptIn(ExperimentalKBsonSerializerApi::class) @Test fun insertOne_explicitTypes() = runBlocking { assertEquals(0, collection.find().size) // Inserting document without _id will use ObjectId as _id - collection.insertOne(BsonDocument("_id" to BsonInt32(Random.nextInt()), "name" to BsonString("object-1"))).let { - assertIs(it) + collection.withDocumentClass().insertOne(BsonDocument("_id" to BsonInt32(Random.nextInt()), "name" to BsonString("object-1"))).let { + assertIs(it) } // Inserted document will have ObjectId key and cannot be serialized into CollectionDataType // so find must also use BsonDocument - assertEquals(1, collection.find().size) + assertEquals(1, collection.find().size) } @Test @@ -772,17 +780,19 @@ sealed class MongoCollectionTests { assertEquals(1, collection.find().size) } + @OptIn(ExperimentalKBsonSerializerApi::class) @Test open fun insertOne_throwsOnMissingRequiredFields() = runBlocking { assertFailsWithMessage("insert not permitted") { - collection.insertOne(BsonDocument("_id", ObjectId())) + collection.withDocumentClass().insertOne(BsonDocument("_id", ObjectId())) } } + @OptIn(ExperimentalKBsonSerializerApi::class) @Test fun insertOne_throwsOnTypeMismatch() = runBlocking { assertFailsWithMessage("insert not permitted") { - collection.insertOne(BsonDocument(mapOf("_id" to ObjectId(), "name" to BsonString("object-1")))) + collection.withDocumentClass().insertOne(BsonDocument(mapOf("_id" to ObjectId(), "name" to BsonString("object-1")))) } } @Test @@ -803,17 +813,17 @@ sealed class MongoCollectionTests { fun insertMany_explictTyped() = runBlocking { assertEquals(0, collection.find().size) - collection.insertMany( + collection.insertMany( (1..10).map { BsonDocument("_id" to BsonInt32(Random.nextInt()), "name" to BsonString("object-${it % 5}")) } ).let { assertEquals(10, it.size) it.forEach { - assertIs(it) + assertIs(it) } } - assertEquals(10, collection.find().size) + assertEquals(10, collection.find().size) } // InsertMany with links @@ -840,14 +850,14 @@ sealed class MongoCollectionTests { @Test open fun insertMany_throwsOnMissingRequiredFields() = runBlocking { assertFailsWithMessage("insert not permitted") { - collection.insertMany(listOf(BsonDocument())) + collection.insertMany(listOf(BsonDocument())) } } @Test fun insertMany_throwsOnTypeMismatch() = runBlocking { assertFailsWithMessage("insert not permitted") { - collection.insertMany(listOf(BsonDocument(mapOf("_id" to ObjectId(), "name" to BsonString("object-1"))))) + collection.insertMany(listOf(BsonDocument(mapOf("_id" to ObjectId(), "name" to BsonString("object-1"))))) } } @@ -857,7 +867,7 @@ sealed class MongoCollectionTests { assertEquals( 2, - collection.insertMany( + collection.insertMany( listOf( CollectionDataType("object-1"), CollectionDataType("object-1") @@ -904,7 +914,7 @@ sealed class MongoCollectionTests { assertEquals( 4, - collection.insertMany( + collection.insertMany( listOf( CollectionDataType("object-1"), CollectionDataType("object-1"), @@ -941,7 +951,7 @@ sealed class MongoCollectionTests { BsonDocument("""{ "name": "object-3"}"""), BsonDocument(""" { "name": "object-2", "_id" : ${Random.nextInt()}}"""), upsert = true ).let { (updated, upsertedId) -> assertFalse(updated) - assertIs(upsertedId) + assertIs(upsertedId) } assertEquals(5, collection.count()) assertEquals(2, collection.count(filter = BsonDocument("""{"name": "object-2"}"""))) @@ -959,12 +969,12 @@ sealed class MongoCollectionTests { @Test fun updateOne_explicitTypes() = runBlocking { - val upsertWithoutMatch = collection.updateOne( + val upsertWithoutMatch = collection.updateOne( BsonDocument("""{ "name": "object-3"}"""), BsonDocument(""" { "name": "object-2", "_id" : ${Random.nextInt()}}"""), upsert = true ) upsertWithoutMatch.let { (updated, upsertedId) -> assertFalse(updated) - assertIs(upsertedId) + assertIs(upsertedId) } } @@ -980,7 +990,7 @@ sealed class MongoCollectionTests { assertEquals(0, collection.count()) assertEquals( 4, - collection.insertMany( + collection.insertMany( listOf( CollectionDataType("x"), CollectionDataType("x"), @@ -1019,7 +1029,7 @@ sealed class MongoCollectionTests { upsert = true ).let { (modifiedCount, upsertedId) -> assertEquals(0L, modifiedCount) - assertIs(upsertedId) + assertIs(upsertedId) } assertEquals(5, collection.count()) assertEquals(1, collection.count(filter = BsonDocument("""{"name": "UPSERTED"}"""))) @@ -1037,13 +1047,13 @@ sealed class MongoCollectionTests { @Test fun updateMany_explicitTypes() = runBlocking { - collection.updateMany( + collection.updateMany( BsonDocument("""{ "name": "object-3"}"""), BsonDocument(""" { "name": "object-2", "_id" : ${Random.nextInt()}}"""), upsert = true ).let { (modifiedCount, upsertedId) -> assertEquals(0, modifiedCount) - assertIs(upsertedId) + assertIs(upsertedId) } } @@ -1059,7 +1069,7 @@ sealed class MongoCollectionTests { assertNull(collection.findOneAndUpdate(BsonDocument(), BsonDocument())) val names = (1..10).map { "object-${it % 5}" } - val ids: List = collection.insertMany(names.map { CollectionDataType(it) }) + val ids: List = collection.insertMany(names.map { CollectionDataType(it) }) // Update with no match assertNull( @@ -1162,11 +1172,12 @@ sealed class MongoCollectionTests { } } + @OptIn(ExperimentalKBsonSerializerApi::class) @Test fun findOneAndUpdate_explicitTypes() = runBlocking { collection.insertOne(CollectionDataType("object-1")) - collection.findOneAndUpdate( + collection.withDocumentClass().findOneAndUpdate( BsonDocument("""{ "name": "object-1"}"""), BsonDocument("""{ "name": "UPDATED"}"""), )!!.let { @@ -1186,7 +1197,7 @@ sealed class MongoCollectionTests { assertNull(collection.findOneAndReplace(BsonDocument(), BsonDocument())) val names = (1..10).map { "object-${it % 5}" } - val ids: List = collection.insertMany(names.map { CollectionDataType(it) }) + val ids: List = collection.insertMany(names.map { CollectionDataType(it) }) // Replace with no match assertNull( @@ -1290,11 +1301,12 @@ sealed class MongoCollectionTests { } } + @OptIn(ExperimentalKBsonSerializerApi::class) @Test fun findOneAndReplace_explicitTypes() = runBlocking { collection.insertOne(CollectionDataType("object-1")) - collection.findOneAndReplace( + collection.withDocumentClass().findOneAndReplace( BsonDocument("""{ "name": "object-1"}"""), BsonDocument("""{ "name": "REPLACED"}"""), )!!.let { @@ -1314,7 +1326,7 @@ sealed class MongoCollectionTests { assertNull(collection.findOneAndDelete(BsonDocument(), BsonDocument())) val names = (1..10).map { "object-${it % 5}" } - val ids: List = collection.insertMany(names.map { CollectionDataType(it) }) + val ids: List = collection.insertMany(names.map { CollectionDataType(it) }) // Delete with no match assertNull(collection.findOneAndDelete(BsonDocument("""{"name": "NOMATCH"}"""))) @@ -1363,11 +1375,12 @@ sealed class MongoCollectionTests { } } + @OptIn(ExperimentalKBsonSerializerApi::class) @Test fun findOneAndDelete_explicitTypes() = runBlocking { collection.insertOne(CollectionDataType("object-1")) - collection.findOneAndDelete( + collection.withDocumentClass().findOneAndDelete( BsonDocument("""{ "name": "object-1"}"""), )!!.let { assertEquals("object-1", it.asDocument()["name"]!!.asString().value) @@ -1392,7 +1405,7 @@ sealed class MongoCollectionTests { // Helper method to be able to differentiate collection creation across test classes @OptIn(ExperimentalKBsonSerializerApi::class) -inline fun MongoCollectionTests.collection(eJson: EJson? = null): MongoCollection { +inline fun MongoCollectionTests.collection(eJson: EJson? = null): MongoCollection { return when (this) { is MongoCollectionFromDatabaseTests -> database.collection(T::class.simpleName!!, eJson) is MongoCollectionFromClientTests -> client.collection(eJson) @@ -1423,8 +1436,6 @@ class CustomDataType(var name: String, var _id: Int = Random.nextInt()) : RealmO @Suppress("unused") constructor() : this("Default") } -// Custom Id type to showcase that we can use custom serializers for primary key return values. -class CustomIdType(val id: Int) // Custom serializers to showcase that we can inject serializers throughout the MongoClient APIs. class CustomDataTypeSerializer : KSerializer { @@ -1446,24 +1457,10 @@ class CustomDataTypeSerializer : KSerializer { encoder.encodeSerializableValue(serializer, document) } } -class CustomIdSerializer : KSerializer { - val serializer = BsonInt32.serializer() - override val descriptor: SerialDescriptor = serializer.descriptor - override fun deserialize(decoder: Decoder): CustomIdType { - return decoder.decodeSerializableValue(serializer).let { - CustomIdType(it.asInt32().value) - } - } - - override fun serialize(encoder: Encoder, value: CustomIdType) { - encoder.encodeSerializableValue(serializer, BsonInt32(value.id)) - } -} @OptIn(ExperimentalKBsonSerializerApi::class) val customEjsonSerializer = EJson( serializersModule = SerializersModule { contextual(CustomDataType::class, CustomDataTypeSerializer()) - contextual(CustomIdType::class, CustomIdSerializer()) } ) diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoDatabaseTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoDatabaseTests.kt index d9e5dfd391..ed135800ed 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoDatabaseTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoDatabaseTests.kt @@ -28,7 +28,6 @@ import kotlinx.serialization.SerializationException import org.mongodb.kbson.BsonDocument import org.mongodb.kbson.BsonInt32 import org.mongodb.kbson.BsonString -import org.mongodb.kbson.BsonValue import org.mongodb.kbson.ExperimentalKBsonSerializerApi import kotlin.random.Random import kotlin.test.AfterTest @@ -73,29 +72,29 @@ class MongoDatabaseTests { fun collection_defaultTypes() = runBlocking { val collection = database.collection("CollectionDataType") val value = collection.insertOne(BsonDocument("_id" to BsonInt32(Random.nextInt()), "name" to BsonString("object-1"))) - assertIs(value) + assertIs(value) } @Test fun collection_typed() = runBlocking { - val collection = database.collection("CollectionDataType") + val collection = database.collection("CollectionDataType") val value = collection.insertOne(CollectionDataType("object-1", Random.nextInt())) assertIs(value) } @Test fun collection_defaultSerializer() = runBlocking { - assertIs(database.collection("CollectionDataType").insertOne(CollectionDataType("object-1"))) + assertIs(database.collection("CollectionDataType").insertOne(CollectionDataType("object-1"))) } @Test fun collection_customSerializer() = runBlocking { - val collectionWithDefaultSerializer = database.collection("CollectionDataType") + val collectionWithDefaultSerializer = database.collection("CollectionDataType") assertFailsWithMessage("Serializer for class 'CustomDataType' is not found.") { collectionWithDefaultSerializer.insertOne(CustomDataType("object-1")) } - val collectionWithCustomSerializer = database.collection("CollectionDataType", customEjsonSerializer) - assertIs(collectionWithCustomSerializer.insertOne(CustomDataType("object-1"))) + val collectionWithCustomSerializer = database.collection("CollectionDataType", customEjsonSerializer) + assertIs(collectionWithCustomSerializer.insertOne(CustomDataType("object-1"))) } } diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/utils/Utils.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/utils/Utils.kt index b9ca03ec40..8ac26c2ab8 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/utils/Utils.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/utils/Utils.kt @@ -75,11 +75,9 @@ suspend inline fun SyncSession.uploadAllLocalChangesOrFail() { assertTrue(this.uploadAllLocalChanges(timeout), "Failed to upload local changes in time: $timeout") } -suspend fun retry(action: suspend () -> R, until: (R) -> Boolean, retries: Int = 5, delay: Duration = 1.seconds): R { +suspend fun retry(action: suspend () -> R?, until: (R?) -> Boolean, retries: Int = 5, delay: Duration = 1.seconds): R? { repeat(retries) { action().let { - println("block: $it") - println("predicate(it): ${until(it)}") if (until(it)) { return it } else { From b2f8008c998b38c55db07649a2c68b2fad15f059 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Mon, 13 May 2024 15:32:09 +0200 Subject: [PATCH 30/36] Updates according to review comments --- .../realm/kotlin/internal/RealmObjectUtil.kt | 6 +- .../kotlin/internal/platform/RealmObject.kt | 5 +- .../kotlin/internal/platform/RealmObject.kt | 6 +- .../kotlin/internal/platform/RealmObject.kt | 6 +- .../kotlin/mongodb/ext/MongoClientExt.kt | 3 +- .../mongodb/internal/MongoCollectionImpl.kt | 3 +- .../mongodb/internal/MongoDBSerializer.kt | 16 +- .../realm/kotlin/mongodb/mongo/MongoClient.kt | 8 +- .../common/mongo/MongoCollectionTests.kt | 183 +++++++++++------- 9 files changed, 142 insertions(+), 94 deletions(-) diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectUtil.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectUtil.kt index 72c622bd6d..ea4b858a9b 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectUtil.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectUtil.kt @@ -106,14 +106,16 @@ internal fun RealmObjectReference.toRealmObject(): T = /** * Returns the [RealmObjectCompanion] associated with a given [BaseRealmObject]'s [KClass]. */ -public inline fun KClass<*>.realmObjectCompanionOrNull(): RealmObjectCompanion? { +internal inline fun KClass<*>.realmObjectCompanionOrNull(): RealmObjectCompanion? { + @Suppress("invisible_reference", "invisible_member") return realmObjectCompanionOrNull(this) } /** * Returns the [RealmObjectCompanion] associated with a given [BaseRealmObject]'s [KClass]. */ -public inline fun KClass.realmObjectCompanionOrThrow(): RealmObjectCompanion { +internal inline fun KClass.realmObjectCompanionOrThrow(): RealmObjectCompanion { + @Suppress("invisible_reference", "invisible_member") return realmObjectCompanionOrThrow(this) } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/platform/RealmObject.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/platform/RealmObject.kt index 538c14b255..5c17d10bfb 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/platform/RealmObject.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/platform/RealmObject.kt @@ -25,9 +25,10 @@ import kotlin.reflect.KClass * associated [RealmObjectCompanion], in which case the `clazz` wasn't a user defined class * implementing [BaseRealmObject] augmented by our compiler plugin. */ -public expect fun realmObjectCompanionOrNull(clazz: KClass): RealmObjectCompanion? +internal expect fun realmObjectCompanionOrNull(clazz: KClass): RealmObjectCompanion? /** * Returns the [RealmObjectCompanion] associated with a given [BaseRealmObject]'s [KClass]. */ -public expect fun realmObjectCompanionOrThrow(clazz: KClass): RealmObjectCompanion + +internal expect fun realmObjectCompanionOrThrow(clazz: KClass): RealmObjectCompanion diff --git a/packages/library-base/src/jvm/kotlin/io/realm/kotlin/internal/platform/RealmObject.kt b/packages/library-base/src/jvm/kotlin/io/realm/kotlin/internal/platform/RealmObject.kt index 6eda90ff83..97f948eafb 100644 --- a/packages/library-base/src/jvm/kotlin/io/realm/kotlin/internal/platform/RealmObject.kt +++ b/packages/library-base/src/jvm/kotlin/io/realm/kotlin/internal/platform/RealmObject.kt @@ -23,11 +23,13 @@ import kotlin.reflect.full.companionObjectInstance // TODO OPTIMIZE Can we eliminate the reflective approach? Maybe by embedding the information // through the compiler plugin or something similar to the Native findAssociatedObject -public actual fun realmObjectCompanionOrNull(clazz: KClass): RealmObjectCompanion? = +@PublishedApi +internal actual fun realmObjectCompanionOrNull(clazz: KClass): RealmObjectCompanion? = if (clazz.companionObjectInstance is RealmObjectCompanion) { clazz.companionObjectInstance as RealmObjectCompanion } else null -public actual fun realmObjectCompanionOrThrow(clazz: KClass): RealmObjectCompanion = +@PublishedApi +internal actual fun realmObjectCompanionOrThrow(clazz: KClass): RealmObjectCompanion = realmObjectCompanionOrNull(clazz) ?: error("Couldn't find companion object of class '${clazz.simpleName}'.\nA common cause for this is when the `io.realm.kotlin` is not applied to the Gradle module that contains the '${clazz.simpleName}' class.") diff --git a/packages/library-base/src/nativeDarwin/kotlin/io/realm/kotlin/internal/platform/RealmObject.kt b/packages/library-base/src/nativeDarwin/kotlin/io/realm/kotlin/internal/platform/RealmObject.kt index 36a494d843..2d2d81f811 100644 --- a/packages/library-base/src/nativeDarwin/kotlin/io/realm/kotlin/internal/platform/RealmObject.kt +++ b/packages/library-base/src/nativeDarwin/kotlin/io/realm/kotlin/internal/platform/RealmObject.kt @@ -22,13 +22,15 @@ import kotlin.reflect.ExperimentalAssociatedObjects import kotlin.reflect.KClass import kotlin.reflect.findAssociatedObject -public actual fun realmObjectCompanionOrNull(clazz: KClass): RealmObjectCompanion? = +@PublishedApi +internal actual fun realmObjectCompanionOrNull(clazz: KClass): RealmObjectCompanion? = @OptIn(ExperimentalAssociatedObjects::class) when (val associatedObject = clazz.findAssociatedObject()) { is RealmObjectCompanion -> associatedObject else -> null } -public actual fun realmObjectCompanionOrThrow(clazz: KClass): RealmObjectCompanion = +@PublishedApi +internal actual fun realmObjectCompanionOrThrow(clazz: KClass): RealmObjectCompanion = realmObjectCompanionOrNull(clazz) ?: error("Couldn't find companion object of class '${clazz.simpleName}'.\nA common cause for this is when the `io.realm.kotlin` is not applied to the Gradle module that contains the '${clazz.simpleName}' class.") diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/MongoClientExt.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/MongoClientExt.kt index de25ed973f..e64b01c6b1 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/MongoClientExt.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/MongoClientExt.kt @@ -16,7 +16,6 @@ package io.realm.kotlin.mongodb.ext -import io.realm.kotlin.internal.platform.realmObjectCompanionOrThrow import io.realm.kotlin.mongodb.internal.MongoClientCollection import io.realm.kotlin.mongodb.internal.MongoClientImpl import io.realm.kotlin.mongodb.mongo.MongoClient @@ -42,5 +41,5 @@ import org.mongodb.kbson.serialization.EJson @ExperimentalKBsonSerializerApi public inline fun MongoClient.collection(eJson: EJson? = null): MongoCollection { @Suppress("invisible_reference", "invisible_member") - return MongoClientCollection(this as MongoClientImpl, realmObjectCompanionOrThrow(T::class).io_realm_kotlin_className, eJson ?: this.eJson) + return MongoClientCollection(this as MongoClientImpl, io.realm.kotlin.internal.platform.realmObjectCompanionOrThrow(T::class).io_realm_kotlin_className, eJson ?: this.eJson) } diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt index d2ee7e1d0a..a8af3b37a5 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt @@ -239,7 +239,8 @@ internal inline fun MongoCollectionImpl<*>.decodeFromBsonValueList(b } @Suppress("ComplexMethod") -public fun BsonValue.toAny(): Any? { +@PublishedApi +internal fun BsonValue.toAny(): Any? { return when (this.bsonType) { BsonType.NULL -> null BsonType.INT32 -> asInt32().value diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDBSerializer.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDBSerializer.kt index e428b35dd8..91aed60c5d 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDBSerializer.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDBSerializer.kt @@ -21,8 +21,6 @@ import io.realm.kotlin.internal.asBsonBinary import io.realm.kotlin.internal.asBsonDateTime import io.realm.kotlin.internal.asRealmInstant import io.realm.kotlin.internal.asRealmUUID -import io.realm.kotlin.internal.platform.realmObjectCompanionOrThrow -import io.realm.kotlin.internal.realmObjectCompanionOrNull import io.realm.kotlin.internal.util.Validation import io.realm.kotlin.schema.RealmProperty import io.realm.kotlin.schema.RealmStorageType @@ -68,7 +66,8 @@ public open class MongoDBSerializer internal constructor( internal val schema: Map = emptyMap() ) : KSerializer { override val descriptor: SerialDescriptor = BsonDocument.serializer().descriptor - private val companion = realmObjectCompanionOrThrow(clazz) + @Suppress("invisible_reference", "invisible_member") + private val companion = io.realm.kotlin.internal.platform.realmObjectCompanionOrThrow(clazz) override fun deserialize(decoder: Decoder): BaseRealmObject { return bsonToObject(companion, decoder.decodeSerializableValue(BsonDocument.serializer())) @@ -127,7 +126,9 @@ public open class MongoDBSerializer internal constructor( RealmStorageType.BINARY -> BsonBinary(value as ByteArray) RealmStorageType.OBJECT -> { @Suppress("UNCHECKED_CAST") - val targetCompanion = realmObjectCompanionOrThrow(clazz as KClass) + val targetCompanion = + @Suppress("invisible_reference", "invisible_member") + io.realm.kotlin.internal.platform.realmObjectCompanionOrThrow(clazz as KClass) @Suppress("UNCHECKED_CAST") val primaryKeyProperty: KMutableProperty1? = targetCompanion.io_realm_kotlin_primaryKey as KMutableProperty1? @@ -165,7 +166,9 @@ public open class MongoDBSerializer internal constructor( RealmAny.Type.OBJECT -> { // Objects in RealmAny cannot be EmbeddedObjects val target = realmAny.asRealmObject(BaseRealmObject::class) - val targetCompanion = realmObjectCompanionOrThrow(target::class) + val targetCompanion = + @Suppress("invisible_reference", "invisible_member") + io.realm.kotlin.internal.platform.realmObjectCompanionOrThrow(target as KClass) val primaryKeySchemaProperty: RealmProperty = targetCompanion.io_realm_kotlin_schema().primaryKey ?: throw SerializationException( "Cannot serialize class without primary key: '${targetCompanion.io_realm_kotlin_className}'" ) @@ -196,7 +199,8 @@ public open class MongoDBSerializer internal constructor( RealmStorageType.STRING -> bsonValue.asString().value RealmStorageType.BINARY -> bsonValue.asBinary().data RealmStorageType.OBJECT -> { - val targetCompanion = kClass.realmObjectCompanionOrNull()!! + @Suppress("invisible_reference", "invisible_member") + val targetCompanion = io.realm.kotlin.internal.platform.realmObjectCompanionOrNull(kClass)!! @Suppress("UNCHECKED_CAST") val primaryKeyAccessor = (targetCompanion.io_realm_kotlin_primaryKey as KMutableProperty1?) diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt index c57ff6c61d..b54d11b151 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt @@ -17,7 +17,6 @@ package io.realm.kotlin.mongodb.mongo import io.realm.kotlin.internal.RealmObjectCompanion -import io.realm.kotlin.internal.platform.realmObjectCompanionOrThrow import io.realm.kotlin.mongodb.internal.MongoDBSerializer import io.realm.kotlin.types.BaseRealmObject import io.realm.kotlin.types.RealmObject @@ -100,7 +99,12 @@ public interface MongoClient { */ public fun realmSerializerModule(schema: Set>): SerializersModule { val companions: Map = - schema.associate { kClass -> realmObjectCompanionOrThrow(kClass).let { it.io_realm_kotlin_className to it } } + schema.associate { kClass -> + @Suppress("invisible_reference", "invisible_member") + io.realm.kotlin.internal.platform.realmObjectCompanionOrThrow(kClass).let { + it.io_realm_kotlin_className to it + } + } val serializers: List, KSerializer<*>>> = schema.map { it to MongoDBSerializer(it, companions) } diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt index ff05778d8c..4342eac124 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt @@ -159,8 +159,11 @@ sealed class MongoCollectionTests { @BeforeTest open fun setUp() { app = TestApp( - this::class.simpleName, - TEST_APP_FLEX, + testId = this::class.simpleName, + appName = TEST_APP_FLEX, + builder = { + it.httpLogObfuscator(null) + } ) app.asTestApp.run { @@ -173,8 +176,8 @@ sealed class MongoCollectionTests { user = app.createUserAndLogin() @OptIn(ExperimentalKBsonSerializerApi::class) client = user.mongoClient( - TEST_SERVICE_NAME, - EJson( + serviceName = TEST_SERVICE_NAME, + eJson = EJson( serializersModule = realmSerializerModule( setOf( ParentCollectionDataType::class, @@ -186,7 +189,7 @@ sealed class MongoCollectionTests { } @AfterTest - fun teadDown() { + fun tearDown() { app.asTestApp.run { runBlocking { COLLECTION_SCHEMAS.forEach { @@ -205,7 +208,7 @@ sealed class MongoCollectionTests { } @Test - open fun withDocumentClass() = runBlocking { + fun withDocumentClass() = runBlocking { // Original typing assertIs(collection.insertOne(CollectionDataType("object-1", Random.nextInt()))) assertIs(collection.findOne()) @@ -259,7 +262,7 @@ sealed class MongoCollectionTests { } @Test - open fun findOne() = runBlocking { + fun findOne() = runBlocking { // Empty collections assertNull(collection.findOne()) @@ -333,7 +336,7 @@ sealed class MongoCollectionTests { } @Test - open fun findOne_links() = runBlocking { + fun findOne_links() = runBlocking { Realm.open( SyncConfiguration.Builder(user, FLEXIBLE_SYNC_SCHEMA) .initialSubscriptions { @@ -360,7 +363,7 @@ sealed class MongoCollectionTests { } @Test - open fun findOne_typedLinks() = runBlocking { + fun findOne_typedLinks() = runBlocking { Realm.open( SyncConfiguration.Builder(user, FLEXIBLE_SYNC_SCHEMA) .initialSubscriptions { @@ -392,7 +395,7 @@ sealed class MongoCollectionTests { } @Test - open fun findOne_typedLinks_throwsOnMissingTargetSchema() = runBlocking { + fun findOne_typedLinks_throwsOnMissingTargetSchema() = runBlocking { Realm.open( SyncConfiguration.Builder(user, FLEXIBLE_SYNC_SCHEMA) .initialSubscriptions { @@ -433,7 +436,7 @@ sealed class MongoCollectionTests { @OptIn(ExperimentalKBsonSerializerApi::class) @Test - open fun findOne_embeddedObjects() = runBlocking { + fun findOne_embeddedObjects() = runBlocking { // Empty collections assertNull(collection.findOne()) @@ -566,7 +569,7 @@ sealed class MongoCollectionTests { @Test fun find_fails() = runBlocking { assertFailsWithMessage("unknown top level operator: \$who.") { - collection.find(BsonDocument("\$who", 1)).first() + collection.find(filter = BsonDocument("\$who", 1)).first() } } @@ -577,14 +580,18 @@ sealed class MongoCollectionTests { val names = (1..10).map { "object-${it % 5}" } val ids: List = collection.insertMany(names.map { CollectionDataType(it) }) - collection.aggregate(listOf()).let { + collection.aggregate( + pipeline = listOf() + ).let { assertEquals(10, it.size) it.forEach { assertTrue { it.name in names } } } - collection.aggregate(listOf(BsonDocument("\$sort", BsonDocument("name", -1)), BsonDocument("\$limit", 2))).let { + collection.aggregate( + pipeline = listOf(BsonDocument("\$sort", BsonDocument("name", -1)), BsonDocument("\$limit", 2)) + ).let { assertEquals(2, it.size) it.forEach { assertEquals("object-4", it.name) @@ -611,7 +618,7 @@ sealed class MongoCollectionTests { @Test @OptIn(ExperimentalKBsonSerializerApi::class) - open fun insertOne_links() = runBlocking { + fun insertOne_links() = runBlocking { // Open a synced realm and verified that the linked entities we upload through the Realm.open( SyncConfiguration.Builder(user, FLEXIBLE_SYNC_SCHEMA) @@ -673,7 +680,7 @@ sealed class MongoCollectionTests { @Test @OptIn(ExperimentalKBsonSerializerApi::class) - open fun insertOne_typedLinks() = runBlocking { + fun insertOne_typedLinks() = runBlocking { // Open a synced realm and verified that the linked entities we upload through the Realm.open( SyncConfiguration.Builder(user, FLEXIBLE_SYNC_SCHEMA) @@ -758,7 +765,9 @@ sealed class MongoCollectionTests { fun insertOne_explicitTypes() = runBlocking { assertEquals(0, collection.find().size) // Inserting document without _id will use ObjectId as _id - collection.withDocumentClass().insertOne(BsonDocument("_id" to BsonInt32(Random.nextInt()), "name" to BsonString("object-1"))).let { + collection.withDocumentClass().insertOne( + BsonDocument("_id" to BsonInt32(Random.nextInt()), "name" to BsonString("object-1")) + ).let { assertIs(it) } // Inserted document will have ObjectId key and cannot be serialized into CollectionDataType @@ -782,7 +791,7 @@ sealed class MongoCollectionTests { @OptIn(ExperimentalKBsonSerializerApi::class) @Test - open fun insertOne_throwsOnMissingRequiredFields() = runBlocking { + fun insertOne_throwsOnMissingRequiredFields() = runBlocking { assertFailsWithMessage("insert not permitted") { collection.withDocumentClass().insertOne(BsonDocument("_id", ObjectId())) } @@ -792,7 +801,9 @@ sealed class MongoCollectionTests { @Test fun insertOne_throwsOnTypeMismatch() = runBlocking { assertFailsWithMessage("insert not permitted") { - collection.withDocumentClass().insertOne(BsonDocument(mapOf("_id" to ObjectId(), "name" to BsonString("object-1")))) + collection.withDocumentClass().insertOne( + BsonDocument(mapOf("_id" to ObjectId(), "name" to BsonString("object-1"))) + ) } } @Test @@ -815,7 +826,10 @@ sealed class MongoCollectionTests { collection.insertMany( (1..10).map { - BsonDocument("_id" to BsonInt32(Random.nextInt()), "name" to BsonString("object-${it % 5}")) + BsonDocument( + "_id" to BsonInt32(Random.nextInt()), + "name" to BsonString("object-${it % 5}") + ) } ).let { assertEquals(10, it.size) @@ -848,7 +862,7 @@ sealed class MongoCollectionTests { } @Test - open fun insertMany_throwsOnMissingRequiredFields() = runBlocking { + fun insertMany_throwsOnMissingRequiredFields() = runBlocking { assertFailsWithMessage("insert not permitted") { collection.insertMany(listOf(BsonDocument())) } @@ -857,13 +871,22 @@ sealed class MongoCollectionTests { @Test fun insertMany_throwsOnTypeMismatch() = runBlocking { assertFailsWithMessage("insert not permitted") { - collection.insertMany(listOf(BsonDocument(mapOf("_id" to ObjectId(), "name" to BsonString("object-1"))))) + collection.insertMany( + listOf( + BsonDocument( + mapOf( + "_id" to ObjectId(), + "name" to BsonString("object-1") + ) + ) + ) + ) } } @Test fun deleteOne() = runBlocking { - assertFalse { collection.deleteOne(BsonDocument()) } + assertFalse { collection.deleteOne(filter = BsonDocument()) } assertEquals( 2, @@ -874,42 +897,42 @@ sealed class MongoCollectionTests { ) ).size ) - assertEquals(2, collection.count(BsonDocument("""{ "name": "object-1" }"""))) + assertEquals(2, collection.count(filter = BsonDocument("""{ "name": "object-1" }"""))) - assertTrue { collection.deleteOne(BsonDocument("""{ "name": "object-1" }""")) } - assertEquals(1, collection.count(BsonDocument("""{ "name": "object-1" }"""))) + assertTrue { collection.deleteOne(filter = BsonDocument("""{ "name": "object-1" }""")) } + assertEquals(1, collection.count(filter = BsonDocument("""{ "name": "object-1" }"""))) } @Test fun deleteOne_fails() = runBlocking { assertFailsWithMessage("unknown top level operator: \$who.") { - collection.deleteOne(BsonDocument("\$who", 1)) + collection.deleteOne(filter = BsonDocument("\$who", 1)) } } @Test fun deleteMany() = runBlocking { - assertEquals(0, collection.deleteMany(BsonDocument())) + assertEquals(0, collection.deleteMany(filter = BsonDocument())) collection.insertMany((1..10).map { CollectionDataType("object-${it % 5}") }) assertEquals(10, collection.find().size) - assertEquals(2, collection.deleteMany(BsonDocument("""{ "name": "object-1" }"""))) + assertEquals(2, collection.deleteMany(filter = BsonDocument("""{ "name": "object-1" }"""))) assertEquals(8, collection.find().size) - assertEquals(8, collection.deleteMany(BsonDocument())) + assertEquals(8, collection.deleteMany(filter = BsonDocument())) } @Test fun deleteMany_fails() = runBlocking { assertFailsWithMessage("unknown top level operator: \$who.") { - collection.deleteMany(BsonDocument("\$who", 1)) + collection.deleteMany(filter = BsonDocument("\$who", 1)) } } @Test - open fun updateOne() = runBlocking { + fun updateOne() = runBlocking { assertEquals(0, collection.count()) assertEquals( @@ -927,8 +950,8 @@ sealed class MongoCollectionTests { // Update no match collection.updateOne( - BsonDocument("""{ "name": "NOMATCH"}"""), - BsonDocument("\$set", BsonDocument("""{ "name": "UPDATED"}""")), + filter = BsonDocument("""{ "name": "NOMATCH"}"""), + update = BsonDocument("\$set", BsonDocument("""{ "name": "UPDATED"}""")), ).let { (updated, upsertedId) -> assertFalse(updated) assertNull(upsertedId) @@ -936,8 +959,8 @@ sealed class MongoCollectionTests { // Update with match match collection.updateOne( - BsonDocument("""{ "name": "object-1"}"""), - BsonDocument("\$set", BsonDocument("""{ "name": "object-2"}""")), + filter = BsonDocument("""{ "name": "object-1"}"""), + update = BsonDocument("\$set", BsonDocument("""{ "name": "object-2"}""")), ).let { (updated, upsertedId) -> assertTrue(updated) assertNull(upsertedId) @@ -948,7 +971,9 @@ sealed class MongoCollectionTests { // Upsert no match collection.updateOne( - BsonDocument("""{ "name": "object-3"}"""), BsonDocument(""" { "name": "object-2", "_id" : ${Random.nextInt()}}"""), upsert = true + filter = BsonDocument("""{ "name": "object-3"}"""), + update = BsonDocument(""" { "name": "object-2", "_id" : ${Random.nextInt()}}"""), + upsert = true ).let { (updated, upsertedId) -> assertFalse(updated) assertIs(upsertedId) @@ -958,7 +983,9 @@ sealed class MongoCollectionTests { // Upsert with match collection.updateOne( - BsonDocument("""{ "name": "object-2"}"""), BsonDocument(""" { "name": "object-3"}"""), upsert = true + filter = BsonDocument(json = """{ "name": "object-2"}"""), + update = BsonDocument(""" { "name": "object-3"}"""), + upsert = true ).let { (updated, upsertedId) -> assertTrue(updated) assertNull(upsertedId) @@ -970,7 +997,9 @@ sealed class MongoCollectionTests { @Test fun updateOne_explicitTypes() = runBlocking { val upsertWithoutMatch = collection.updateOne( - BsonDocument("""{ "name": "object-3"}"""), BsonDocument(""" { "name": "object-2", "_id" : ${Random.nextInt()}}"""), upsert = true + filter = BsonDocument("""{ "name": "object-3"}"""), + update = BsonDocument(""" { "name": "object-2", "_id" : ${Random.nextInt()}}"""), + upsert = true ) upsertWithoutMatch.let { (updated, upsertedId) -> assertFalse(updated) @@ -981,7 +1010,7 @@ sealed class MongoCollectionTests { @Test fun updateOne_fails() = runBlocking { assertFailsWithMessage("unknown top level operator: \$who.") { - collection.updateOne(BsonDocument("\$who", 1), BsonDocument()) + collection.updateOne(filter = BsonDocument("\$who", 1), update = BsonDocument()) } } @@ -1002,8 +1031,8 @@ sealed class MongoCollectionTests { assertEquals(4, collection.count()) // Update with no match collection.updateMany( - BsonDocument("""{"name": "NOMATCH"}"""), - BsonDocument("""{"name": "UPDATED"}"""), + filter = BsonDocument("""{"name": "NOMATCH"}"""), + update = BsonDocument("""{"name": "UPDATED"}"""), ).let { (modifiedCount, upsertedId) -> assertEquals(0L, modifiedCount) assertNull(upsertedId) @@ -1013,8 +1042,8 @@ sealed class MongoCollectionTests { // Update with match collection.updateMany( - BsonDocument("""{ "name": "x"}"""), - BsonDocument("""{ "name": "UPDATED"}"""), + filter = BsonDocument("""{ "name": "x"}"""), + update = BsonDocument("""{ "name": "UPDATED"}"""), ).let { (modifiedCount, upsertedId) -> assertEquals(2L, modifiedCount) assertNull(upsertedId) @@ -1024,8 +1053,8 @@ sealed class MongoCollectionTests { // Upsert no match collection.updateMany( - BsonDocument("""{ "name": "NOMATCH"}"""), - BsonDocument(""" { "name": "UPSERTED", "_id" : ${Random.nextInt()}}"""), + filter = BsonDocument("""{ "name": "NOMATCH"}"""), + update = BsonDocument(""" { "name": "UPSERTED", "_id" : ${Random.nextInt()}}"""), upsert = true ).let { (modifiedCount, upsertedId) -> assertEquals(0L, modifiedCount) @@ -1036,7 +1065,8 @@ sealed class MongoCollectionTests { // Upsert with match collection.updateMany( - BsonDocument("""{ "name": "y"}"""), BsonDocument(""" { "name": "z"}"""), upsert = true + filter = BsonDocument("""{ "name": "y"}"""), + update = BsonDocument(""" { "name": "z"}"""), upsert = true ).let { (modifiedCount, upsertedId) -> assertEquals(1L, modifiedCount) assertNull(upsertedId) @@ -1048,8 +1078,8 @@ sealed class MongoCollectionTests { @Test fun updateMany_explicitTypes() = runBlocking { collection.updateMany( - BsonDocument("""{ "name": "object-3"}"""), - BsonDocument(""" { "name": "object-2", "_id" : ${Random.nextInt()}}"""), + filter = BsonDocument("""{ "name": "object-3"}"""), + update = BsonDocument(""" { "name": "object-2", "_id" : ${Random.nextInt()}}"""), upsert = true ).let { (modifiedCount, upsertedId) -> assertEquals(0, modifiedCount) @@ -1060,13 +1090,13 @@ sealed class MongoCollectionTests { @Test fun updateMany_fails() = runBlocking { assertFailsWithMessage("Unknown modifier: \$who") { - collection.updateOne(BsonDocument(), BsonDocument("\$who", 1)) + collection.updateOne(filter = BsonDocument(), update = BsonDocument("\$who", 1)) } } @Test fun findOneAndUpdate() = runBlocking { - assertNull(collection.findOneAndUpdate(BsonDocument(), BsonDocument())) + assertNull(collection.findOneAndUpdate(filter = BsonDocument(), update = BsonDocument())) val names = (1..10).map { "object-${it % 5}" } val ids: List = collection.insertMany(names.map { CollectionDataType(it) }) @@ -1074,24 +1104,24 @@ sealed class MongoCollectionTests { // Update with no match assertNull( collection.findOneAndUpdate( - BsonDocument("""{"name": "NOMATCH"}"""), - BsonDocument("""{"name": "UPDATED"}"""), + filter = BsonDocument("""{"name": "NOMATCH"}"""), + update = BsonDocument("""{"name": "UPDATED"}"""), ) ) assertEquals(0, collection.count(filter = BsonDocument("""{"name": "UPDATED"}"""))) // Update with match - return old collection.findOneAndUpdate( - BsonDocument("""{ "name": "object-1"}"""), - BsonDocument("""{ "name": "UPDATED"}"""), + filter = BsonDocument("""{ "name": "object-1"}"""), + update = BsonDocument("""{ "name": "UPDATED"}"""), )!!.let { assertEquals("object-1", it.name) } assertEquals(1, collection.count(filter = BsonDocument("""{"name": "UPDATED"}"""))) // Update with match - return new collection.findOneAndUpdate( - BsonDocument("""{ "name": "object-1"}"""), - BsonDocument("""{ "name": "UPDATED"}"""), + filter = BsonDocument("""{ "name": "object-1"}"""), + update = BsonDocument("""{ "name": "UPDATED"}"""), returnNewDoc = true )!!.let { assertEquals("UPDATED", it.name) @@ -1178,8 +1208,8 @@ sealed class MongoCollectionTests { collection.insertOne(CollectionDataType("object-1")) collection.withDocumentClass().findOneAndUpdate( - BsonDocument("""{ "name": "object-1"}"""), - BsonDocument("""{ "name": "UPDATED"}"""), + filter = BsonDocument("""{ "name": "object-1"}"""), + update = BsonDocument("""{ "name": "UPDATED"}"""), )!!.let { assertEquals("object-1", it.asDocument()["name"]!!.asString().value) } @@ -1188,13 +1218,13 @@ sealed class MongoCollectionTests { @Test fun findOneAndUpdate_fails() = runBlocking { assertFailsWithMessage("Unknown modifier: \$who") { - collection.findOneAndUpdate(BsonDocument(), BsonDocument("\$who", 1)) + collection.findOneAndUpdate(filter = BsonDocument(), update = BsonDocument("\$who", 1)) } } @Test fun findOneAndReplace() = runBlocking { - assertNull(collection.findOneAndReplace(BsonDocument(), BsonDocument())) + assertNull(collection.findOneAndReplace(filter = BsonDocument(), document = BsonDocument())) val names = (1..10).map { "object-${it % 5}" } val ids: List = collection.insertMany(names.map { CollectionDataType(it) }) @@ -1202,16 +1232,16 @@ sealed class MongoCollectionTests { // Replace with no match assertNull( collection.findOneAndReplace( - BsonDocument("""{"name": "NOMATCH"}"""), - BsonDocument(""" { "name": "REPLACED", "_id" : ${Random.nextInt()}}"""), + filter = BsonDocument("""{"name": "NOMATCH"}"""), + document = BsonDocument(""" { "name": "REPLACED", "_id" : ${Random.nextInt()}}"""), ) ) assertEquals(0, collection.count(filter = BsonDocument("""{"name": "REPLACED"}"""))) // Replace with match - return old collection.findOneAndReplace( - BsonDocument("""{ "name": "object-1"}"""), - BsonDocument(""" { "name": "REPLACED"}"""), + filter = BsonDocument("""{ "name": "object-1"}"""), + document = BsonDocument(""" { "name": "REPLACED"}"""), )!!.let { assertEquals("object-1", it.name) } @@ -1219,8 +1249,8 @@ sealed class MongoCollectionTests { // Replace with match - return new collection.findOneAndReplace( - BsonDocument("""{ "name": "object-1"}"""), - BsonDocument("""{ "name": "REPLACED"}"""), + filter = BsonDocument("""{ "name": "object-1"}"""), + document = BsonDocument("""{ "name": "REPLACED"}"""), returnNewDoc = true )!!.let { assertEquals("REPLACED", it.name) @@ -1307,8 +1337,8 @@ sealed class MongoCollectionTests { collection.insertOne(CollectionDataType("object-1")) collection.withDocumentClass().findOneAndReplace( - BsonDocument("""{ "name": "object-1"}"""), - BsonDocument("""{ "name": "REPLACED"}"""), + filter = BsonDocument("""{ "name": "object-1"}"""), + document = BsonDocument("""{ "name": "REPLACED"}"""), )!!.let { assertEquals("object-1", it.asDocument()["name"]!!.asString().value) } @@ -1317,24 +1347,27 @@ sealed class MongoCollectionTests { @Test fun findOneAndReplace_fails() = runBlocking { assertFailsWithMessage("the replace operation document must not contain atomic operators") { - collection.findOneAndReplace(BsonDocument(), BsonDocument("\$who", 1)) + collection.findOneAndReplace( + filter = BsonDocument(), + document = BsonDocument("\$who", 1) + ) } } @Test fun findOneAndDelete() = runBlocking { - assertNull(collection.findOneAndDelete(BsonDocument(), BsonDocument())) + assertNull(collection.findOneAndDelete(filter = BsonDocument(), projection = BsonDocument())) val names = (1..10).map { "object-${it % 5}" } val ids: List = collection.insertMany(names.map { CollectionDataType(it) }) // Delete with no match - assertNull(collection.findOneAndDelete(BsonDocument("""{"name": "NOMATCH"}"""))) + assertNull(collection.findOneAndDelete(filter = BsonDocument("""{"name": "NOMATCH"}"""))) assertEquals(10, collection.count(filter = BsonDocument())) // Delete with match collection.findOneAndDelete( - BsonDocument("""{ "name": "object-1"}"""), + filter = BsonDocument("""{ "name": "object-1"}"""), )!!.let { assertEquals("object-1", it.name) } @@ -1381,7 +1414,7 @@ sealed class MongoCollectionTests { collection.insertOne(CollectionDataType("object-1")) collection.withDocumentClass().findOneAndDelete( - BsonDocument("""{ "name": "object-1"}"""), + filter = BsonDocument("""{ "name": "object-1"}"""), )!!.let { assertEquals("object-1", it.asDocument()["name"]!!.asString().value) } @@ -1390,7 +1423,7 @@ sealed class MongoCollectionTests { @Test fun findOneAndDelete_fails() = runBlocking { assertFailsWithMessage("unknown top level operator: \$who.") { - collection.findOneAndDelete(BsonDocument("\$who", 1)) + collection.findOneAndDelete(filter = BsonDocument("\$who", 1)) } } From c40fce49b8c8d519ffd16f27335c992280ff9789 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Mon, 13 May 2024 22:07:50 +0200 Subject: [PATCH 31/36] Add support for serialization of collections in mixed --- .../mongodb/internal/MongoDBSerializer.kt | 219 +++++++++--------- 1 file changed, 114 insertions(+), 105 deletions(-) diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDBSerializer.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDBSerializer.kt index 48bd94c3f7..33961d9b8e 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDBSerializer.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDBSerializer.kt @@ -16,6 +16,8 @@ package io.realm.kotlin.mongodb.internal +import io.realm.kotlin.ext.toRealmDictionary +import io.realm.kotlin.ext.toRealmList import io.realm.kotlin.internal.RealmObjectCompanion import io.realm.kotlin.internal.asBsonBinary import io.realm.kotlin.internal.asBsonDateTime @@ -151,58 +153,45 @@ public open class MongoDBSerializer internal constructor( RealmStorageType.TIMESTAMP -> (value as RealmInstant).asBsonDateTime() RealmStorageType.OBJECT_ID -> (value as ObjectId) RealmStorageType.UUID -> (value as RealmUUID).asBsonBinary() - RealmStorageType.ANY -> { - val realmAny = value as RealmAny - when (realmAny.type) { - RealmAny.Type.BOOL -> BsonBoolean(realmAny.asBoolean()) - RealmAny.Type.INT -> BsonInt64(realmAny.asLong()) - RealmAny.Type.STRING -> BsonString(realmAny.asString()) - RealmAny.Type.BINARY -> BsonBinary(realmAny.asByteArray()) - RealmAny.Type.TIMESTAMP -> realmAny.asRealmInstant().asBsonDateTime() - RealmAny.Type.FLOAT -> BsonDouble(realmAny.asFloat().toDouble()) - RealmAny.Type.DOUBLE -> BsonDouble(realmAny.asDouble()) - RealmAny.Type.DECIMAL128 -> realmAny.asDecimal128() - RealmAny.Type.OBJECT_ID -> realmAny.asObjectId() - RealmAny.Type.UUID -> realmAny.asRealmUUID().asBsonBinary() - RealmAny.Type.OBJECT -> { - // Objects in RealmAny cannot be EmbeddedObjects - val target = realmAny.asRealmObject(BaseRealmObject::class) - val targetCompanion = - @Suppress("invisible_reference", "invisible_member") - io.realm.kotlin.internal.platform.realmObjectCompanionOrThrow(target as KClass) - val primaryKeySchemaProperty: RealmProperty = targetCompanion.io_realm_kotlin_schema().primaryKey ?: throw SerializationException( - "Cannot serialize class without primary key: '${targetCompanion.io_realm_kotlin_className}'" - ) - val (primaryKeyType, primaryKeyAccessor) = targetCompanion.io_realm_kotlin_fields[primaryKeySchemaProperty.name] ?: throw SerializationException( - "Cannot serialize class without primary key: '${targetCompanion.io_realm_kotlin_className}'" - ) - val primaryKey: BsonValue = storageTypeToBsonValue(primaryKeySchemaProperty.type.storageType, primaryKeyType, primaryKeyAccessor.get(target)) ?: BsonNull - BsonDocument( - "\$ref" to BsonString(targetCompanion.io_realm_kotlin_className), - "\$id" to primaryKey - ) - } - RealmAny.Type.LIST -> { - val map: List = realmAny.asList().map { - storageTypeToBsonValue( - RealmStorageType.ANY, - RealmAny::class/* Argument not used for RealmAny conversion, so just pass in arbitrary class */, - it - ) ?: BsonNull - } - BsonArray(map) - } - RealmAny.Type.DICTIONARY -> BsonDocument( - realmAny.asDictionary().mapValues { (_, v) -> - storageTypeToBsonValue( - RealmStorageType.ANY, - RealmAny::class, - v - ) ?: BsonNull - } - ) - } - } + RealmStorageType.ANY -> { realmAnyToBsonValue(value as RealmAny) } + } + } + + @Suppress("ComplexMethod") + private fun realmAnyToBsonValue(realmAny: RealmAny?): BsonValue = when (realmAny?.type) { + null -> BsonNull + RealmAny.Type.BOOL -> BsonBoolean(realmAny.asBoolean()) + RealmAny.Type.INT -> BsonInt64(realmAny.asLong()) + RealmAny.Type.STRING -> BsonString(realmAny.asString()) + RealmAny.Type.BINARY -> BsonBinary(realmAny.asByteArray()) + RealmAny.Type.TIMESTAMP -> realmAny.asRealmInstant().asBsonDateTime() + RealmAny.Type.FLOAT -> BsonDouble(realmAny.asFloat().toDouble()) + RealmAny.Type.DOUBLE -> BsonDouble(realmAny.asDouble()) + RealmAny.Type.DECIMAL128 -> realmAny.asDecimal128() + RealmAny.Type.OBJECT_ID -> realmAny.asObjectId() + RealmAny.Type.UUID -> realmAny.asRealmUUID().asBsonBinary() + RealmAny.Type.OBJECT -> { + // Objects in RealmAny cannot be EmbeddedObjects + val target = realmAny.asRealmObject(BaseRealmObject::class) + @Suppress("invisible_reference", "invisible_member") + val targetCompanion = io.realm.kotlin.internal.platform.realmObjectCompanionOrThrow(target::class) + val primaryKeySchemaProperty: RealmProperty = targetCompanion.io_realm_kotlin_schema().primaryKey ?: throw SerializationException( + "Cannot serialize class without primary key: '${targetCompanion.io_realm_kotlin_className}'" + ) + val (primaryKeyType, primaryKeyAccessor) = targetCompanion.io_realm_kotlin_fields[primaryKeySchemaProperty.name] ?: throw SerializationException( + "Cannot serialize class without primary key: '${targetCompanion.io_realm_kotlin_className}'" + ) + val primaryKey: BsonValue = storageTypeToBsonValue(primaryKeySchemaProperty.type.storageType, primaryKeyType, primaryKeyAccessor.get(target)) ?: BsonNull + BsonDocument( + "\$ref" to BsonString(targetCompanion.io_realm_kotlin_className), + "\$id" to primaryKey + ) + } + RealmAny.Type.LIST -> { + BsonArray(realmAny.asList().map { realmAnyToBsonValue(realmAny) }) + } + RealmAny.Type.DICTIONARY -> { + BsonDocument(realmAny.asDictionary().mapValues { (_, v) -> realmAnyToBsonValue(v) }) } } @@ -244,62 +233,82 @@ public open class MongoDBSerializer internal constructor( RealmStorageType.TIMESTAMP -> bsonValue.asDateTime().asRealmInstant() RealmStorageType.OBJECT_ID -> bsonValue.asObjectId() RealmStorageType.UUID -> bsonValue.asBinary().asRealmUUID() - RealmStorageType.ANY -> when (bsonValue.bsonType) { - // RealmAny.Type.FLOAT - // RealmAny.Type.DOUBLE - BsonType.DOUBLE -> bsonValue.asDouble().value - // RealmAny.Type.STRING - BsonType.STRING -> bsonValue.asString().value - // RealmAny.Type.INT - BsonType.INT32 -> bsonValue.asInt32().value - BsonType.INT64 -> bsonValue.asInt64().value - // RealmAny.Type.DECIMAL128 - BsonType.DECIMAL128 -> bsonValue.asDecimal128() - // RealmAny.Type.BINARY - // RealmAny.Type.UUID handled as binary, we can't distinguish it - BsonType.BINARY -> bsonValue.asBinary() - // RealmAny.Type.OBJECT_ID - BsonType.OBJECT_ID -> bsonValue.asObjectId() - // RealmAny.Type.BOOL - BsonType.BOOLEAN -> bsonValue.asBoolean().value - // RealmAny.Type.TIMESTAMP - BsonType.DATE_TIME -> bsonValue.asDateTime().asRealmInstant() - BsonType.DOCUMENT -> { - val dbRef = bsonValue.asDocument() - val type = dbRef["\$ref"]?.asString()?.value ?: throw SerializationException("Cannot resolve target class: Missing '${"$"}ref'") - val primaryKey = dbRef["\$id"] ?: throw SerializationException("Cannot resolve target primary key: Missing '${"$"}id'") - val targetCompanion = schema[type] ?: throw SerializationException("Cannot resolve target class in schema: Unknown class '${"$"}ref=$type'") - val primaryKeySchemaProperty = targetCompanion.io_realm_kotlin_schema().primaryKey ?: throw SerializationException( - "Target class does not have a primary key: '${"$"}ref=$type'" - ) - val (primaryKeyType, primaryKeyAccessor) = targetCompanion.io_realm_kotlin_fields[primaryKeySchemaProperty.name] ?: throw SerializationException( - "Target class does not have a primary key: '${"$"}ref=$type'" - ) - val instance: RealmObject = targetCompanion.io_realm_kotlin_newInstance() as RealmObject - (primaryKeyAccessor as KMutableProperty1).set( - instance, - bsonValueToStorageType( - primaryKeySchemaProperty.type.storageType, - primaryKeyType, - primaryKey + RealmStorageType.ANY -> bsonValueToRealmAny(bsonValue) + } + } + + @Suppress("ComplexMethod") + private fun bsonValueToRealmAny( + bsonValue: BsonValue?, + ): RealmAny? { + return when (bsonValue?.bsonType) { + null, + BsonType.NULL -> null + // RealmAny.Type.FLOAT + // RealmAny.Type.DOUBLE + BsonType.DOUBLE -> RealmAny.create(bsonValue.asDouble().value) + // RealmAny.Type.STRING + BsonType.STRING -> RealmAny.create(bsonValue.asString().value) + // RealmAny.Type.INT + BsonType.INT32 -> RealmAny.create(bsonValue.asInt32().value) + BsonType.INT64 -> RealmAny.create(bsonValue.asInt64().value) + // RealmAny.Type.DECIMAL128 + BsonType.DECIMAL128 -> RealmAny.create(bsonValue.asDecimal128()) + // RealmAny.Type.BINARY + // RealmAny.Type.UUID handled as binary, we can't distinguish it + BsonType.BINARY -> RealmAny.create(bsonValue.asBinary().data) + // RealmAny.Type.OBJECT_ID + BsonType.OBJECT_ID -> RealmAny.Companion.create(bsonValue.asObjectId()) + // RealmAny.Type.BOOL + BsonType.BOOLEAN -> RealmAny.create(bsonValue.asBoolean().value) + // RealmAny.Type.TIMESTAMP + BsonType.DATE_TIME -> RealmAny.Companion.create(bsonValue.asDateTime().asRealmInstant()) + BsonType.DOCUMENT -> { + val document = bsonValue.asDocument() + val type: String? = document["\$ref"]?.asString()?.value + when { + type != null -> { + val primaryKey = document["\$id"] ?: throw SerializationException("Cannot resolve target primary key: Missing '${"$"}id'") + val targetCompanion = schema[type] ?: throw SerializationException("Cannot resolve target class in schema: Unknown class '${"$"}ref=$type'") + val primaryKeySchemaProperty = targetCompanion.io_realm_kotlin_schema().primaryKey ?: throw SerializationException( + "Target class does not have a primary key: '${"$"}ref=$type'" + ) + val (primaryKeyType, primaryKeyAccessor) = targetCompanion.io_realm_kotlin_fields[primaryKeySchemaProperty.name] ?: throw SerializationException( + "Target class does not have a primary key: '${"$"}ref=$type'" ) - ) - RealmAny.create(instance) + val instance: RealmObject = targetCompanion.io_realm_kotlin_newInstance() as RealmObject + (primaryKeyAccessor as KMutableProperty1).set( + instance, + bsonValueToStorageType( + primaryKeySchemaProperty.type.storageType, + primaryKeyType, + primaryKey + ) + ) + RealmAny.create(instance) + } + else -> { + RealmAny.create( + document.mapValues { (_, v) -> bsonValueToRealmAny(v) } + .toRealmDictionary() + ) + } } - BsonType.NULL -> null - BsonType.TIMESTAMP, - BsonType.END_OF_DOCUMENT, - BsonType.ARRAY, - BsonType.UNDEFINED, - BsonType.REGULAR_EXPRESSION, - BsonType.DB_POINTER, - BsonType.JAVASCRIPT, - BsonType.SYMBOL, - BsonType.JAVASCRIPT_WITH_SCOPE, - BsonType.MIN_KEY, - BsonType.MAX_KEY - -> throw SerializationException("Deserializer does not support ${bsonValue.bsonType}") } + BsonType.ARRAY -> { + RealmAny.create(bsonValue.asArray().map { bsonValueToRealmAny(it) }.toRealmList()) + } + BsonType.TIMESTAMP, + BsonType.END_OF_DOCUMENT, + BsonType.UNDEFINED, + BsonType.REGULAR_EXPRESSION, + BsonType.DB_POINTER, + BsonType.JAVASCRIPT, + BsonType.SYMBOL, + BsonType.JAVASCRIPT_WITH_SCOPE, + BsonType.MIN_KEY, + BsonType.MAX_KEY + -> throw SerializationException("Deserializer does not support ${bsonValue.bsonType}") } } } From 2be641153393a7ff28962860ace059425ca01bc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Wed, 15 May 2024 13:40:43 +0200 Subject: [PATCH 32/36] Add tests for MongoDBSerializer --- .../realm/kotlin/internal/RealmInstantImpl.kt | 5 +- .../internal/schema/RealmPropertyTypeImpl.kt | 34 ++ .../io/realm/kotlin/types/RealmInstant.kt | 2 +- .../mongodb/internal/MongoDBSerializer.kt | 320 +++++++--- ...RealmModelSyntheticPropertiesGeneration.kt | 25 +- .../entities/sync/SyncObjectWithAllTypes.kt | 4 +- .../common/mongo/MongoCollectionTests.kt | 26 +- .../common/mongo/MongoDBSerializerTests.kt | 569 ++++++++++++++++++ 8 files changed, 864 insertions(+), 121 deletions(-) create mode 100644 packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/schema/RealmPropertyTypeImpl.kt create mode 100644 packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoDBSerializerTests.kt diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmInstantImpl.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmInstantImpl.kt index 2d6af6ad9e..cdc7d7fabe 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmInstantImpl.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmInstantImpl.kt @@ -38,8 +38,9 @@ public fun RealmInstant.toDuration(): Duration { public fun Duration.toRealmInstant(): RealmInstant { val seconds: Long = this.inWholeSeconds - val nanos: Duration = (this - seconds.seconds) - return RealmInstant.from(seconds, nanos.inWholeNanoseconds.toInt()) + // We cannot do duration arithmetic as some operations on INFINITE and NEG_INFINITE will overflow + val nanos: Int = (this.inWholeNanoseconds - (seconds * RealmInstant.SEC_AS_NANOSECOND)).toInt() + return RealmInstant.from(seconds, nanos) } internal fun RealmInstant.restrictToMillisPrecision() = diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/schema/RealmPropertyTypeImpl.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/schema/RealmPropertyTypeImpl.kt new file mode 100644 index 0000000000..62e8ca704a --- /dev/null +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/schema/RealmPropertyTypeImpl.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2024 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.realm.kotlin.internal.schema + +import io.realm.kotlin.internal.interop.CollectionType +import io.realm.kotlin.schema.ListPropertyType +import io.realm.kotlin.schema.MapPropertyType +import io.realm.kotlin.schema.RealmPropertyType +import io.realm.kotlin.schema.SetPropertyType +import io.realm.kotlin.schema.ValuePropertyType + +public val RealmPropertyType.collectionType: CollectionType + get() { + return when (this) { + is ListPropertyType -> CollectionType.RLM_COLLECTION_TYPE_LIST + is MapPropertyType -> CollectionType.RLM_COLLECTION_TYPE_DICTIONARY + is SetPropertyType -> CollectionType.RLM_COLLECTION_TYPE_SET + is ValuePropertyType -> CollectionType.RLM_COLLECTION_TYPE_NONE + } + } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmInstant.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmInstant.kt index 280bbe9e7f..17c66526dc 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmInstant.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/RealmInstant.kt @@ -37,7 +37,7 @@ import io.realm.kotlin.internal.platform.currentTime public interface RealmInstant : Comparable { public companion object { - private const val SEC_AS_NANOSECOND: Int = 1_000_000_000 + internal const val SEC_AS_NANOSECOND: Int = 1_000_000_000 /** * Minimum timestamp that can be stored in Realm. diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDBSerializer.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDBSerializer.kt index 33961d9b8e..d4d33f61cd 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDBSerializer.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDBSerializer.kt @@ -16,17 +16,23 @@ package io.realm.kotlin.mongodb.internal +import io.realm.kotlin.ext.asBsonObjectId import io.realm.kotlin.ext.toRealmDictionary import io.realm.kotlin.ext.toRealmList +import io.realm.kotlin.ext.toRealmSet import io.realm.kotlin.internal.RealmObjectCompanion import io.realm.kotlin.internal.asBsonBinary import io.realm.kotlin.internal.asBsonDateTime import io.realm.kotlin.internal.asRealmInstant import io.realm.kotlin.internal.asRealmUUID -import io.realm.kotlin.internal.util.Validation +import io.realm.kotlin.internal.interop.CollectionType +import io.realm.kotlin.internal.schema.collectionType +import io.realm.kotlin.internal.util.Validation.sdkError import io.realm.kotlin.schema.RealmProperty +import io.realm.kotlin.schema.RealmPropertyType import io.realm.kotlin.schema.RealmStorageType import io.realm.kotlin.types.BaseRealmObject +import io.realm.kotlin.types.MutableRealmInt import io.realm.kotlin.types.RealmAny import io.realm.kotlin.types.RealmInstant import io.realm.kotlin.types.RealmObject @@ -41,6 +47,7 @@ import org.mongodb.kbson.BsonBinary import org.mongodb.kbson.BsonBoolean import org.mongodb.kbson.BsonDocument import org.mongodb.kbson.BsonDouble +import org.mongodb.kbson.BsonInt32 import org.mongodb.kbson.BsonInt64 import org.mongodb.kbson.BsonNull import org.mongodb.kbson.BsonString @@ -64,7 +71,7 @@ import kotlin.reflect.KProperty1 * realm objects. To be able to deserialize and create the correct instance, the serializer needs to * know of all potential target types. */ -public open class MongoDBSerializer internal constructor( +public class MongoDBSerializer internal constructor( clazz: KClass, internal val schema: Map = emptyMap() ) : KSerializer { @@ -86,7 +93,7 @@ public open class MongoDBSerializer internal constructor( ?: throw SerializationException("Unknown field '$key' for type ${companion.io_realm_kotlin_className}") val type = schema[key]?.type ?: throw SerializationException("Unknown field '$key' for type ${companion.io_realm_kotlin_className}") - val value = bsonValueToStorageType(type.storageType, kClass, bsonDocument[key]) + val value = bsonValueToStorageType(type.collectionType, type.storageType, kClass, bsonDocument[key]) (accessor as KMutableProperty1).set(instance, value) } return instance @@ -105,10 +112,10 @@ public open class MongoDBSerializer internal constructor( val schema = companion.io_realm_kotlin_schema() val document = BsonDocument() fields.forEach { (fieldName, fieldDetails) -> - val (kClass, accessor) = fieldDetails - val type = - schema[fieldName]?.type ?: Validation.sdkError("Schema does not contain property $fieldName") - storageTypeToBsonValue(type.storageType, kClass, accessor.get(realmObject))?.let { + val (_, accessor) = fieldDetails + val type: RealmPropertyType = + schema[fieldName]?.type ?: sdkError("Schema does not contain property $fieldName") + storageTypeToBsonValue(type.collectionType, type.storageType, accessor.get(realmObject)).let { document[fieldName] = it } } @@ -117,43 +124,98 @@ public open class MongoDBSerializer internal constructor( @Suppress("LongMethod", "ComplexMethod") private fun storageTypeToBsonValue( - storageType: RealmStorageType, - clazz: KClass<*>, + collectionType: CollectionType, + elementType: RealmStorageType, value: Any?, - ): BsonValue? { + ): BsonValue { if (value == null) return BsonNull - return when (storageType) { - RealmStorageType.BOOL -> BsonBoolean(value as Boolean) - RealmStorageType.INT -> BsonInt64(value as Long) - RealmStorageType.STRING -> BsonString(value as String) - RealmStorageType.BINARY -> BsonBinary(value as ByteArray) - RealmStorageType.OBJECT -> { - @Suppress("UNCHECKED_CAST") - val targetCompanion = - @Suppress("invisible_reference", "invisible_member") - io.realm.kotlin.internal.platform.realmObjectCompanionOrThrow(clazz as KClass) - @Suppress("UNCHECKED_CAST") - val primaryKeyProperty: KMutableProperty1? = - targetCompanion.io_realm_kotlin_primaryKey as KMutableProperty1? - when (primaryKeyProperty) { - // Embedded objects does not have a primary key, so serialize to full documents - null -> objectToBson(targetCompanion, value as BaseRealmObject) - else -> { - val targetStorageType = - targetCompanion.io_realm_kotlin_schema().primaryKey!!.type.storageType - val primaryKey = primaryKeyProperty.get(value as BaseRealmObject) - storageTypeToBsonValue(targetStorageType, clazz, primaryKey) + return when (collectionType) { + CollectionType.RLM_COLLECTION_TYPE_NONE -> { + when (elementType) { + RealmStorageType.BOOL -> BsonBoolean(value as Boolean) + RealmStorageType.INT -> when (value) { + is Byte -> BsonInt32(value.toInt()) + is Char -> BsonInt32(value.code) + is Short -> BsonInt32(value.toInt()) + is Int -> BsonInt32(value) + is Long -> BsonInt64(value) + is MutableRealmInt -> BsonInt64(value.toLong()) + else -> sdkError("Unexpected value of type ${value::class.simpleName} for field with storage type $elementType") + } + RealmStorageType.STRING -> BsonString(value as String) + RealmStorageType.BINARY -> BsonBinary(value as ByteArray) + RealmStorageType.OBJECT -> { + @Suppress("UNCHECKED_CAST") + val targetCompanion = + @Suppress("invisible_reference", "invisible_member") + io.realm.kotlin.internal.platform.realmObjectCompanionOrThrow(value::class as KClass) + @Suppress("UNCHECKED_CAST") + val primaryKeyProperty: KMutableProperty1? = + targetCompanion.io_realm_kotlin_primaryKey as KMutableProperty1? + when (primaryKeyProperty) { + // Embedded objects does not have a primary key, so serialize to full documents + null -> objectToBson(targetCompanion, value as BaseRealmObject) + else -> { + val targetStorageType = + targetCompanion.io_realm_kotlin_schema().primaryKey!!.type.storageType + val primaryKey = primaryKeyProperty.get(value as BaseRealmObject) + storageTypeToBsonValue(CollectionType.RLM_COLLECTION_TYPE_NONE, targetStorageType, primaryKey) + } + } + } + + RealmStorageType.FLOAT -> BsonDouble((value as Float).toDouble()) + RealmStorageType.DOUBLE -> BsonDouble(value as Double) + RealmStorageType.DECIMAL128 -> value as Decimal128 + RealmStorageType.TIMESTAMP -> (value as RealmInstant).asBsonDateTime() + RealmStorageType.OBJECT_ID -> { + @Suppress("DEPRECATION") + when (value) { + is ObjectId -> value + is io.realm.kotlin.types.ObjectId -> value.asBsonObjectId() + else -> sdkError("Unexpected value of type ${value::class.simpleName} for field with storage type $elementType") + } } + RealmStorageType.UUID -> (value as RealmUUID).asBsonBinary() + RealmStorageType.ANY -> { realmAnyToBsonValue(value as RealmAny) } } } - - RealmStorageType.FLOAT -> BsonDouble((value as Float).toDouble()) - RealmStorageType.DOUBLE -> BsonDouble(value as Double) - RealmStorageType.DECIMAL128 -> value as Decimal128 - RealmStorageType.TIMESTAMP -> (value as RealmInstant).asBsonDateTime() - RealmStorageType.OBJECT_ID -> (value as ObjectId) - RealmStorageType.UUID -> (value as RealmUUID).asBsonBinary() - RealmStorageType.ANY -> { realmAnyToBsonValue(value as RealmAny) } + CollectionType.RLM_COLLECTION_TYPE_LIST -> { + BsonArray( + (value as List<*>).map { + storageTypeToBsonValue( + CollectionType.RLM_COLLECTION_TYPE_NONE, + elementType, + it + ) + } + ) + } + CollectionType.RLM_COLLECTION_TYPE_SET -> { + BsonArray( + (value as Set<*>).map { + storageTypeToBsonValue( + CollectionType.RLM_COLLECTION_TYPE_NONE, + elementType, + it + ) + } + ) + } + CollectionType.RLM_COLLECTION_TYPE_DICTIONARY -> { + @Suppress("UNCHECKED_CAST") + val map = value as Map + BsonDocument( + map.mapValues { (_, v) -> + storageTypeToBsonValue( + CollectionType.RLM_COLLECTION_TYPE_NONE, + elementType, + v + ) + } + ) + } + else -> sdkError("Unknown collection type: $collectionType") } } @@ -178,17 +240,21 @@ public open class MongoDBSerializer internal constructor( val primaryKeySchemaProperty: RealmProperty = targetCompanion.io_realm_kotlin_schema().primaryKey ?: throw SerializationException( "Cannot serialize class without primary key: '${targetCompanion.io_realm_kotlin_className}'" ) - val (primaryKeyType, primaryKeyAccessor) = targetCompanion.io_realm_kotlin_fields[primaryKeySchemaProperty.name] ?: throw SerializationException( + val (_, primaryKeyAccessor) = targetCompanion.io_realm_kotlin_fields[primaryKeySchemaProperty.name] ?: throw SerializationException( "Cannot serialize class without primary key: '${targetCompanion.io_realm_kotlin_className}'" ) - val primaryKey: BsonValue = storageTypeToBsonValue(primaryKeySchemaProperty.type.storageType, primaryKeyType, primaryKeyAccessor.get(target)) ?: BsonNull + val primaryKey: BsonValue = storageTypeToBsonValue( + CollectionType.RLM_COLLECTION_TYPE_NONE, + primaryKeySchemaProperty.type.storageType, + primaryKeyAccessor.get(target) + ) BsonDocument( "\$ref" to BsonString(targetCompanion.io_realm_kotlin_className), "\$id" to primaryKey ) } RealmAny.Type.LIST -> { - BsonArray(realmAny.asList().map { realmAnyToBsonValue(realmAny) }) + BsonArray(realmAny.asList().map { realmAnyToBsonValue(it) }) } RealmAny.Type.DICTIONARY -> { BsonDocument(realmAny.asDictionary().mapValues { (_, v) -> realmAnyToBsonValue(v) }) @@ -197,47 +263,107 @@ public open class MongoDBSerializer internal constructor( @Suppress("LongMethod", "ComplexMethod") internal fun bsonValueToStorageType( - storageType: RealmStorageType, + collectionType: CollectionType, + elementType: RealmStorageType, kClass: KClass<*>, bsonValue: BsonValue?, ): Any? { if (bsonValue == null || bsonValue == BsonNull) return null - return when (storageType) { - RealmStorageType.BOOL -> bsonValue.asBoolean().value - RealmStorageType.INT -> bsonValue.asNumber().longValue() - RealmStorageType.STRING -> bsonValue.asString().value - RealmStorageType.BINARY -> bsonValue.asBinary().data - RealmStorageType.OBJECT -> { - @Suppress("invisible_reference", "invisible_member") - val targetCompanion = io.realm.kotlin.internal.platform.realmObjectCompanionOrNull(kClass)!! - @Suppress("UNCHECKED_CAST") - val primaryKeyAccessor = - (targetCompanion.io_realm_kotlin_primaryKey as KMutableProperty1?) - when (primaryKeyAccessor) { - // Embedded objects does not have primary keys - null -> bsonToObject(targetCompanion, bsonValue.asDocument()) - else -> { - val targetInstance = - (targetCompanion.io_realm_kotlin_newInstance() as BaseRealmObject) - primaryKeyAccessor.set( - targetInstance, - bsonValue + return when (collectionType) { + CollectionType.RLM_COLLECTION_TYPE_NONE -> when (elementType) { + RealmStorageType.BOOL -> bsonValue.asBoolean().value + RealmStorageType.INT -> when (kClass) { + Byte::class -> bsonValue.asNumber().longValue().toByte() + Char::class -> bsonValue.asNumber().intValue().toChar() + Short::class -> bsonValue.asNumber().intValue().toShort() + Int::class -> bsonValue.asNumber().intValue() + Long::class -> bsonValue.asNumber().longValue() + MutableRealmInt::class -> MutableRealmInt.create( + bsonValue.asNumber().longValue() + ) + else -> sdkError("Unexpected KClass ('${kClass.simpleName}') for element with storage type '$elementType'") + } + RealmStorageType.STRING -> bsonValue.asString().value + RealmStorageType.BINARY -> bsonValue.asBinary().data + RealmStorageType.OBJECT -> { + @Suppress("invisible_reference", "invisible_member") + val targetCompanion = + io.realm.kotlin.internal.platform.realmObjectCompanionOrNull(kClass) + ?: sdkError("Unexpected kClass ('${kClass::simpleName}') without realm companion") + when (val primaryKeySchemaProperty = targetCompanion.io_realm_kotlin_schema().primaryKey) { + // Embedded objects does not have primary keys + null -> bsonToObject(targetCompanion, bsonValue.asDocument()) + else -> { + val (targetKClass, primaryKeyAccessor) = targetCompanion.io_realm_kotlin_fields[primaryKeySchemaProperty.name] + ?: throw SerializationException( + "Target class does not have a primary key: '${targetCompanion.io_realm_kotlin_className}'" + ) + val targetInstance = + (targetCompanion.io_realm_kotlin_newInstance() as BaseRealmObject) + (primaryKeyAccessor as KMutableProperty1).set( + targetInstance, + bsonValueToStorageType( + CollectionType.RLM_COLLECTION_TYPE_NONE, + primaryKeySchemaProperty.type.storageType, + targetKClass, + bsonValue + ) + ) + targetInstance + } + } + } + + RealmStorageType.FLOAT -> bsonValue.asDouble().value.toFloat() + RealmStorageType.DOUBLE -> bsonValue.asDouble().value + RealmStorageType.DECIMAL128 -> bsonValue.asDecimal128() + RealmStorageType.TIMESTAMP -> bsonValue.asDateTime().asRealmInstant() + RealmStorageType.OBJECT_ID -> { + @Suppress("DEPRECATION") + when (kClass) { + ObjectId::class -> bsonValue.asObjectId() + io.realm.kotlin.types.ObjectId::class -> io.realm.kotlin.types.ObjectId.from( + bsonValue.asObjectId().toByteArray() ) - targetInstance + else -> sdkError("Unexpected KClass ('${kClass.simpleName}') for element with storage type '$elementType'") } } + RealmStorageType.UUID -> bsonValue.asBinary().asRealmUUID() + RealmStorageType.ANY -> bsonValueToRealmAny(bsonValue) + } + CollectionType.RLM_COLLECTION_TYPE_LIST -> { + ( + bsonValue.asArray().map { + bsonValueToStorageType( + CollectionType.RLM_COLLECTION_TYPE_NONE, elementType, kClass, it + ) + } + ).toRealmList() } - RealmStorageType.FLOAT -> bsonValue.asDouble().value.toFloat() - RealmStorageType.DOUBLE -> bsonValue.asDouble().value - RealmStorageType.DECIMAL128 -> bsonValue.asDecimal128() - RealmStorageType.TIMESTAMP -> bsonValue.asDateTime().asRealmInstant() - RealmStorageType.OBJECT_ID -> bsonValue.asObjectId() - RealmStorageType.UUID -> bsonValue.asBinary().asRealmUUID() - RealmStorageType.ANY -> bsonValueToRealmAny(bsonValue) + CollectionType.RLM_COLLECTION_TYPE_DICTIONARY -> { + ( + bsonValue.asDocument().mapValues { + bsonValueToStorageType( + CollectionType.RLM_COLLECTION_TYPE_NONE, elementType, kClass, it.value + ) + } + ).toRealmDictionary() + } + + CollectionType.RLM_COLLECTION_TYPE_SET -> { + ( + bsonValue.asArray().map { + bsonValueToStorageType( + CollectionType.RLM_COLLECTION_TYPE_NONE, elementType, kClass, it + ) + } + ).toRealmSet() + } + else -> sdkError("Unknown collection type: $collectionType") } } - @Suppress("ComplexMethod") + @Suppress("ComplexMethod", "LongMethod") private fun bsonValueToRealmAny( bsonValue: BsonValue?, ): RealmAny? { @@ -266,33 +392,35 @@ public open class MongoDBSerializer internal constructor( BsonType.DOCUMENT -> { val document = bsonValue.asDocument() val type: String? = document["\$ref"]?.asString()?.value - when { - type != null -> { - val primaryKey = document["\$id"] ?: throw SerializationException("Cannot resolve target primary key: Missing '${"$"}id'") - val targetCompanion = schema[type] ?: throw SerializationException("Cannot resolve target class in schema: Unknown class '${"$"}ref=$type'") - val primaryKeySchemaProperty = targetCompanion.io_realm_kotlin_schema().primaryKey ?: throw SerializationException( - "Target class does not have a primary key: '${"$"}ref=$type'" - ) - val (primaryKeyType, primaryKeyAccessor) = targetCompanion.io_realm_kotlin_fields[primaryKeySchemaProperty.name] ?: throw SerializationException( + val targetCompanion = schema[type] + val primaryKey = document["\$id"] + if (targetCompanion != null && primaryKey != null) { + val primaryKeySchemaProperty = + targetCompanion.io_realm_kotlin_schema().primaryKey + ?: throw SerializationException( + "Target class does not have a primary key: '${"$"}ref=$type'" + ) + val (primaryKeyType, primaryKeyAccessor) = targetCompanion.io_realm_kotlin_fields[primaryKeySchemaProperty.name] + ?: throw SerializationException( "Target class does not have a primary key: '${"$"}ref=$type'" ) - val instance: RealmObject = targetCompanion.io_realm_kotlin_newInstance() as RealmObject - (primaryKeyAccessor as KMutableProperty1).set( - instance, - bsonValueToStorageType( - primaryKeySchemaProperty.type.storageType, - primaryKeyType, - primaryKey - ) + val instance: RealmObject = + targetCompanion.io_realm_kotlin_newInstance() as RealmObject + (primaryKeyAccessor as KMutableProperty1).set( + instance, + bsonValueToStorageType( + CollectionType.RLM_COLLECTION_TYPE_NONE, + primaryKeySchemaProperty.type.storageType, + primaryKeyType, + primaryKey ) - RealmAny.create(instance) - } - else -> { - RealmAny.create( - document.mapValues { (_, v) -> bsonValueToRealmAny(v) } - .toRealmDictionary() - ) - } + ) + RealmAny.create(instance) + } else { + RealmAny.create( + document.mapValues { (_, v) -> bsonValueToRealmAny(v) } + .toRealmDictionary() + ) } } BsonType.ARRAY -> { diff --git a/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/RealmModelSyntheticPropertiesGeneration.kt b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/RealmModelSyntheticPropertiesGeneration.kt index 1412d16e02..ebe9068580 100644 --- a/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/RealmModelSyntheticPropertiesGeneration.kt +++ b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/RealmModelSyntheticPropertiesGeneration.kt @@ -91,12 +91,16 @@ import org.jetbrains.kotlin.ir.expressions.impl.IrConstructorCallImpl import org.jetbrains.kotlin.ir.expressions.impl.IrGetEnumValueImpl import org.jetbrains.kotlin.ir.expressions.impl.IrPropertyReferenceImpl import org.jetbrains.kotlin.ir.expressions.impl.IrVarargImpl +import org.jetbrains.kotlin.ir.types.IrSimpleType import org.jetbrains.kotlin.ir.types.IrType import org.jetbrains.kotlin.ir.types.classFqName +import org.jetbrains.kotlin.ir.types.classOrNull +import org.jetbrains.kotlin.ir.types.defaultType import org.jetbrains.kotlin.ir.types.getClass import org.jetbrains.kotlin.ir.types.isNullable import org.jetbrains.kotlin.ir.types.makeNullable import org.jetbrains.kotlin.ir.types.starProjectedType +import org.jetbrains.kotlin.ir.types.typeOrNull import org.jetbrains.kotlin.ir.types.typeWith import org.jetbrains.kotlin.ir.util.companionObject import org.jetbrains.kotlin.ir.util.constructors @@ -250,7 +254,7 @@ class RealmModelSyntheticPropertiesGeneration(private val pluginContext: IrPlugi * - `public fun `io_realm_kotlin_schema`(): RealmClassImpl` is added by calling [addSchemaMethodBody]. * - `public fun `io_realm_kotlin_newInstance`(): Any` is added by calling [addNewInstanceMethodBody]. */ - @Suppress("LongMethod") + @Suppress("LongMethod", "ComplexMethod") fun addCompanionFields( clazz: IrClass, companion: IrClass, @@ -316,14 +320,19 @@ class RealmModelSyntheticPropertiesGeneration(private val pluginContext: IrPlugi // Generate list of properties: List, KMutableProperty1<*, *>>>> properties!!.entries.map { val property = it.value.declaration - val targetType = property.backingField!!.type - val targetIrClass = pluginContext.lookupClassOrThrow(targetType.classId!!) - val targetKClassRef = IrClassReferenceImpl( + val targetType: IrType = property.backingField!!.type + val propertyElementType: IrType = when (it.value.collectionType) { + CollectionType.NONE -> targetType + CollectionType.LIST -> (targetType as IrSimpleType).arguments[0].typeOrNull!! + CollectionType.SET -> (targetType as IrSimpleType).arguments[0].typeOrNull!! + CollectionType.DICTIONARY -> (targetType as IrSimpleType).arguments[0].typeOrNull!! + } + val elementKClassRef = IrClassReferenceImpl( startOffset = startOffset, endOffset = endOffset, - type = pluginContext.irBuiltIns.kClassClass.typeWith(targetType), - symbol = targetIrClass.symbol, - classType = targetIrClass.defaultType, + type = pluginContext.irBuiltIns.kClassClass.typeWith(propertyElementType), + symbol = propertyElementType.classOrNull!!, + classType = propertyElementType.classOrNull!!.defaultType, ) val objectPropertyType = if (it.value.isComputed) realmObjectPropertyType else realmObjectMutablePropertyType @@ -361,7 +370,7 @@ class RealmModelSyntheticPropertiesGeneration(private val pluginContext: IrPlugi putTypeArgument(1, objectPropertyType) putValueArgument( 0, - targetKClassRef + elementKClassRef ) putValueArgument( 1, diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/SyncObjectWithAllTypes.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/SyncObjectWithAllTypes.kt index 63c074fe4e..f1b61f906f 100644 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/SyncObjectWithAllTypes.kt +++ b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/SyncObjectWithAllTypes.kt @@ -32,6 +32,7 @@ import io.realm.kotlin.types.RealmSet import io.realm.kotlin.types.RealmUUID import io.realm.kotlin.types.annotations.PrimaryKey import org.mongodb.kbson.Decimal128 +import kotlin.math.absoluteValue import kotlin.random.Random private typealias FieldDataFactory = (SyncObjectWithAllTypes) -> Unit @@ -40,8 +41,9 @@ private typealias FieldValidator = (SyncObjectWithAllTypes) -> Unit @Suppress("MagicNumber") class SyncObjectWithAllTypes : RealmObject { @PrimaryKey + @Suppress("VariableNaming") - var _id: String = "id-${Random.nextLong()}" + var _id: String = "id-${Random.nextLong().absoluteValue}" // Non-nullable types var stringField: String = "hello world" diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt index 4342eac124..65b9d2e2d9 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt @@ -358,7 +358,7 @@ sealed class MongoCollectionTests { { response: ParentCollectionDataType? -> response != null }, ) assertEquals(syncedParent._id, mongoDBClientParent!!._id) - assertEquals(syncedParent.child!!._id, mongoDBClientParent!!.child!!._id) + assertEquals(syncedParent.child!!._id, mongoDBClientParent.child!!._id) } } @@ -395,7 +395,7 @@ sealed class MongoCollectionTests { } @Test - fun findOne_typedLinks_throwsOnMissingTargetSchema() = runBlocking { + fun findOne_typedLinks_unknownClassBecomesDictionary() = runBlocking { Realm.open( SyncConfiguration.Builder(user, FLEXIBLE_SYNC_SCHEMA) .initialSubscriptions { @@ -404,7 +404,7 @@ sealed class MongoCollectionTests { } .build() ).use { realm -> - val syncedParent = realm.write { + realm.write { copyToRealm( ParentCollectionDataType().apply { any = RealmAny.create(ChildCollectionDataType()) @@ -423,13 +423,13 @@ sealed class MongoCollectionTests { ) ) - assertFailsWithMessage("Cannot resolve target class in schema: Unknown class '${"$"}ref=ChildCollectionDataType'") { - // We need to await until the response is non-null otherwise there will be nothing - // to deserialize and no exception will be thrown - retry( - action = { parentCollection.findOne() }, - until = { response -> response != null } - ) + // We need to await until the response is non-null otherwise there will be nothing + // to deserialize and no exception will be thrown + retry( + action = { parentCollection.findOne() }, + until = { response -> response != null } + )!!.run { + assertEquals("ChildCollectionDataType", any!!.asDictionary()["${"$"}ref"]!!.asString()) } } } @@ -578,7 +578,7 @@ sealed class MongoCollectionTests { collection.aggregate(listOf()).let { assertTrue { it.isEmpty() } } val names = (1..10).map { "object-${it % 5}" } - val ids: List = collection.insertMany(names.map { CollectionDataType(it) }) + collection.insertMany(names.map { CollectionDataType(it) }) collection.aggregate( pipeline = listOf() @@ -668,7 +668,7 @@ sealed class MongoCollectionTests { ).let { val parent = it.list.first() assertEquals(unmanagedParent._id, parent._id) - parent!!.child!!.let { + parent.child!!.let { assertEquals(unmanagedChild._id, it._id) assertEquals(unmanagedChild.name, it.name) } @@ -1185,7 +1185,7 @@ sealed class MongoCollectionTests { } // Sort - val sortedNames: List = collection.find().map { it.name!! }.sorted() + val sortedNames: List = collection.find().map { it.name }.sorted() collection.findOneAndUpdate( filter = BsonDocument(), update = BsonDocument(""" { "name": "FIRST"}"""), diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoDBSerializerTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoDBSerializerTests.kt new file mode 100644 index 0000000000..2863b2dcc3 --- /dev/null +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoDBSerializerTests.kt @@ -0,0 +1,569 @@ +/* + * Copyright 2024 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:OptIn(ExperimentalKBsonSerializerApi::class) + +package io.realm.kotlin.test.mongodb.common.mongo + +import io.realm.kotlin.entities.sync.SyncObjectWithAllTypes +import io.realm.kotlin.entities.sync.flx.FlexEmbeddedObject +import io.realm.kotlin.entities.sync.flx.FlexParentObject +import io.realm.kotlin.ext.asBsonObjectId +import io.realm.kotlin.ext.asRealmObject +import io.realm.kotlin.ext.realmAnyDictionaryOf +import io.realm.kotlin.ext.realmAnyListOf +import io.realm.kotlin.ext.realmDictionaryOf +import io.realm.kotlin.ext.realmListOf +import io.realm.kotlin.ext.realmSetOf +import io.realm.kotlin.mongodb.mongo.realmSerializerModule +import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage +import io.realm.kotlin.types.ObjectId +import io.realm.kotlin.types.RealmInstant +import io.realm.kotlin.types.RealmUUID +import kotlinx.serialization.SerializationException +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import org.mongodb.kbson.ExperimentalKBsonSerializerApi +import org.mongodb.kbson.serialization.EJson +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +class MongoDBSerializerTests { + + lateinit var eJson: EJson + + @BeforeTest + fun setUp() { + @Suppress("invisible_member") + eJson = EJson( + serializersModule = realmSerializerModule( + setOf( + SyncObjectWithAllTypes::class, + FlexParentObject::class, + FlexEmbeddedObject::class + ) + ) + ) + } + + @Test + fun serialize() { + assertEqualWithoutWhitespace(EXPECTED_EJSON, eJson.encodeToString(syncObjectWithAllTypes)) + } + + @Test + fun serialize_embeddedObject() { + assertEqualWithoutWhitespace( + EXPECTED_EJSON_EMBEDDED, + eJson.encodeToString(syncObjectWithEmbeddedObject) + ) + } + + @Test + fun deserialize() { + val actual = eJson.decodeFromString(EXPECTED_EJSON) + with(actual) { + // Verify all different types + assertEquals(syncObjectWithAllTypes._id, _id) + assertEquals(syncObjectWithAllTypes.stringField, stringField) + assertEquals(syncObjectWithAllTypes.byteField, byteField) + assertEquals(syncObjectWithAllTypes.charField, charField) + assertEquals(syncObjectWithAllTypes.shortField, shortField) + assertEquals(syncObjectWithAllTypes.intField, intField) + assertEquals(syncObjectWithAllTypes.longField, longField) + assertEquals(syncObjectWithAllTypes.booleanField, booleanField) + assertEquals(syncObjectWithAllTypes.doubleField, doubleField) + assertEquals(syncObjectWithAllTypes.floatField, floatField) + assertEquals(syncObjectWithAllTypes.decimal128Field, decimal128Field) + assertEquals(syncObjectWithAllTypes.realmInstantField, realmInstantField) + assertContentEquals(syncObjectWithAllTypes.binaryField, binaryField) + assertEquals(syncObjectWithAllTypes.mutableRealmIntField, mutableRealmIntField) + assertEquals(syncObjectWithAllTypes.objectField!!._id, objectField!!._id) + assertEquals(syncObjectWithAllTypes.objectIdField, objectIdField) + assertEquals(syncObjectWithAllTypes.realmUUIDField, realmUUIDField) + assertEquals(syncObjectWithAllTypes.realmInstantField, realmInstantField) + // Verify RealmAny with nested lists and dictionaries + nullableRealmAnyField!!.asList().let { + val expectedRealmAnyList = syncObjectWithAllTypes.nullableRealmAnyField!!.asList() + assertEquals(expectedRealmAnyList.size, it.size) + assertEquals( + expectedRealmAnyList[0]!!.asRealmObject()._id, + it[0]!!.asRealmObject()._id + ) + // Nested list + val expectedNestedList = expectedRealmAnyList[1]!!.asList() + val actualNestList = it[1]!!.asList() + assertEquals(expectedNestedList.size, actualNestList.size) + assertEquals(expectedNestedList[0], actualNestList[0]) + assertEquals(expectedNestedList[1], actualNestList[1]) + assertEquals( + expectedNestedList[2]!!.asRealmObject()._id, + actualNestList[2]!!.asRealmObject()._id + ) + // Nested dictionary + val expectedNestedDictionary = expectedRealmAnyList[2]!!.asDictionary() + val actualNestDictionary = it[2]!!.asDictionary() + assertEquals(expectedNestedDictionary.size, actualNestDictionary.size) + assertEquals(expectedNestedDictionary["int"], actualNestDictionary["int"]) + assertEquals(expectedNestedDictionary["string"], actualNestDictionary["string"]) + assertEquals( + expectedNestedDictionary["object"]!!.asRealmObject()._id, + actualNestDictionary["object"]!!.asRealmObject()._id + ) + } + // Smoke test collection fields. Assume that type specific details are verified by the above + // tests + assertEquals(syncObjectWithAllTypes.stringRealmList, stringRealmList) + assertEquals(syncObjectWithAllTypes.stringRealmSet, stringRealmSet) + assertEquals(syncObjectWithAllTypes.stringRealmDictionary, stringRealmDictionary) + } + } + + @Test + fun deserialize_embeddedObject() { + val actual = eJson.decodeFromString(EXPECTED_EJSON_EMBEDDED) + with(actual) { + assertEquals(syncObjectWithEmbeddedObject._id, _id) + assertEquals(syncObjectWithEmbeddedObject.embedded!!.embeddedName, embedded!!.embeddedName) + } + } + + @Test + fun deserialize_defaults() { + eJson.decodeFromString("{}") + } + + @Test + fun deserialize_throwsOnMalformedJSON() { + assertFailsWith { + eJson.decodeFromString("""{ "missing_value" : }""") + } + } + + @Test + fun deserialize_throwsOnUnknownField() { + assertFailsWithMessage("Unknown field 'unknown_field' for type SyncObjectWithAllTypes") { + eJson.decodeFromString("""{ "unknown_field": 1 }""") + } + } + + @Test + fun deserialize_unknownClassRefIsTreatedAsEmbeddedDict() { + val o = eJson.decodeFromString("""{ "nullableRealmAnyField": { "${"$"}ref": "unknown_class" } }""") + assertEquals("unknown_class", o.nullableRealmAnyField!!.asDictionary()["${"$"}ref"]!!.asString()) + } + @Test + fun deserialize_missingIdIsTreatedAsEmbeddedDict() { + val o = eJson.decodeFromString( + """ + { "nullableRealmAnyField": { "${"$"}ref" : "SyncObjectWithAllTyped", "unknown_field" : "UNKNOWN" } } + """.trimIndent() + ) + val realmAnyDictionary = o.nullableRealmAnyField!!.asDictionary() + assertEquals("SyncObjectWithAllTyped", realmAnyDictionary["${"$"}ref"]!!.asString()) + assertEquals("UNKNOWN", realmAnyDictionary["unknown_field"]!!.asString()) + } +} + +private fun assertEqualWithoutWhitespace(a: String, b: String) { + assertEquals(a.replace("\\s+".toRegex(), ""), b.replace("\\s+".toRegex(), "")) +} + +// Ensure test is reproducible by clearing random/time dependant values +// EJSON cannot represent nano second precision, so nanosecond fraction must be 0 +private val date = RealmInstant.from(172, 0) +private val objectId = ObjectId.from(byteArrayOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)) +private val uuid = RealmUUID.Companion.from(byteArrayOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)) +private val child = SyncObjectWithAllTypes().apply { _id = "CHILD" } + +private val syncObjectWithAllTypes: SyncObjectWithAllTypes = SyncObjectWithAllTypes().apply { + + _id = "PARENT" + realmInstantField = date + objectIdField = objectId + objectIdRealmList = realmListOf(objectId) + objectIdRealmSet = realmSetOf(objectId) + objectIdRealmDictionary = realmDictionaryOf("key" to objectId) + realmUUIDField = uuid + realmUUIDRealmList = realmListOf(uuid) + realmUUIDRealmSet = realmSetOf(uuid) + realmUUIDRealmDictionary = realmDictionaryOf("key" to uuid) + objectField = child + nullableRealmAnyField = realmAnyListOf( + child, + realmAnyListOf(1, "Realm", child), + realmAnyDictionaryOf("int" to 1, "string" to "Realm", "object" to child) + ) +} + +private val EXPECTED_EJSON_EMBEDDED = """ + {"_id":{"${"$"}oid":"000102030405060708090a0b"},"section":{"${"$"}numberInt":"0"},"name":"","age":{"${"$"}numberInt":"42"},"child":null,"embedded":{"embeddedName":"EMBEDDED"}} +""" + +private val syncObjectWithEmbeddedObject = FlexParentObject().apply { + _id = objectId.asBsonObjectId() + embedded = FlexEmbeddedObject().apply { + embeddedName = "EMBEDDED" + } +} + +private val EXPECTED_EJSON = """ +{ + "_id":"PARENT", + "stringField":"hello world", + "byteField":{ + "${"$"}numberInt":"0" + }, + "charField":{ + "${"$"}numberInt":"0" + }, + "shortField":{ + "${"$"}numberInt":"0" + }, + "intField":{ + "${"$"}numberInt":"0" + }, + "longField":{ + "${"$"}numberLong":"0" + }, + "booleanField":true, + "doubleField":{ + "${"$"}numberDouble":"0.0" + }, + "floatField":{ + "${"$"}numberDouble":"0.0" + }, + "decimal128Field":{ + "${"$"}numberDecimal":"0" + }, + "realmInstantField":{ + "${"$"}date":{ + "${"$"}numberLong":"172000" + } + }, + "objectIdField":{ + "${"$"}oid":"000102030405060708090a0b" + }, + "realmUUIDField":{ + "${"$"}binary":{ + "base64":"AAECAwQFBgcICQoLDA0ODw==", + "subType":"04" + } + }, + "binaryField":{ + "${"$"}binary":{ + "base64":"Kg==", + "subType":"00" + } + }, + "mutableRealmIntField":{ + "${"$"}numberLong":"42" + }, + "objectField":"CHILD", + "stringNullableField":null, + "byteNullableField":null, + "charNullableField":null, + "shortNullableField":null, + "intNullableField":null, + "longNullableField":null, + "booleanNullableField":null, + "doubleNullableField":null, + "floatNullableField":null, + "decimal128NullableField":null, + "realmInstantNullableField":null, + "objectIdNullableField":null, + "realmUUIDNullableField":null, + "binaryNullableField":null, + "objectNullableField":null, + "mutableRealmIntNullableField":null, + "nullableRealmAnyField":[ + { + "${"$"}ref":"SyncObjectWithAllTypes", + "${"$"}id":"CHILD" + }, + [ + { + "${"$"}numberLong":"1" + }, + "Realm", + { + "${"$"}ref":"SyncObjectWithAllTypes", + "${"$"}id":"CHILD" + } + ], + { + "int":{ + "${"$"}numberLong":"1" + }, + "string":"Realm", + "object":{ + "${"$"}ref":"SyncObjectWithAllTypes", + "${"$"}id":"CHILD" + } + } + ], + "nullableRealmAnyForObjectField":null, + "stringRealmList":[ + "hello world" + ], + "byteRealmList":[ + { + "${"$"}numberInt":"0" + } + ], + "charRealmList":[ + { + "${"$"}numberInt":"0" + } + ], + "shortRealmList":[ + { + "${"$"}numberInt":"0" + } + ], + "intRealmList":[ + { + "${"$"}numberInt":"0" + } + ], + "longRealmList":[ + { + "${"$"}numberLong":"0" + } + ], + "booleanRealmList":[ + true + ], + "doubleRealmList":[ + { + "${"$"}numberDouble":"0.0" + } + ], + "floatRealmList":[ + { + "${"$"}numberDouble":"0.0" + } + ], + "decimal128RealmList":[ + { + "${"$"}numberDecimal":"0.0" + } + ], + "realmInstantRealmList":[ + { + "${"$"}date":{ + "${"$"}numberLong":"-9223372036854775808" + } + } + ], + "objectIdRealmList":[ + { + "${"$"}oid":"000102030405060708090a0b" + } + ], + "realmUUIDRealmList":[ + { + "${"$"}binary":{ + "base64":"AAECAwQFBgcICQoLDA0ODw==", + "subType":"04" + } + } + ], + "binaryRealmList":[ + { + "${"$"}binary":{ + "base64":"Kg==", + "subType":"00" + } + } + ], + "objectRealmList":[ + + ], + "nullableRealmAnyRealmList":[ + { + "${"$"}numberLong":"42" + } + ], + "stringRealmSet":[ + "hello world" + ], + "byteRealmSet":[ + { + "${"$"}numberInt":"0" + } + ], + "charRealmSet":[ + { + "${"$"}numberInt":"0" + } + ], + "shortRealmSet":[ + { + "${"$"}numberInt":"0" + } + ], + "intRealmSet":[ + { + "${"$"}numberInt":"0" + } + ], + "longRealmSet":[ + { + "${"$"}numberLong":"0" + } + ], + "booleanRealmSet":[ + true + ], + "doubleRealmSet":[ + { + "${"$"}numberDouble":"0.0" + } + ], + "floatRealmSet":[ + { + "${"$"}numberDouble":"0.0" + } + ], + "decimal128RealmSet":[ + { + "${"$"}numberDecimal":"0.0" + } + ], + "realmInstantRealmSet":[ + { + "${"$"}date":{ + "${"$"}numberLong":"-9223372036854775808" + } + } + ], + "objectIdRealmSet":[ + { + "${"$"}oid":"000102030405060708090a0b" + } + ], + "realmUUIDRealmSet":[ + { + "${"$"}binary":{ + "base64":"AAECAwQFBgcICQoLDA0ODw==", + "subType":"04" + } + } + ], + "binaryRealmSet":[ + { + "${"$"}binary":{ + "base64":"Kg==", + "subType":"00" + } + } + ], + "objectRealmSet":[ + + ], + "nullableRealmAnyRealmSet":[ + { + "${"$"}numberLong":"42" + } + ], + "stringRealmDictionary":{ + "A":"hello world" + }, + "byteRealmDictionary":{ + "A":{ + "${"$"}numberInt":"0" + } + }, + "charRealmDictionary":{ + "A":{ + "${"$"}numberInt":"0" + } + }, + "shortRealmDictionary":{ + "A":{ + "${"$"}numberInt":"0" + } + }, + "intRealmDictionary":{ + "A":{ + "${"$"}numberInt":"0" + } + }, + "longRealmDictionary":{ + "A":{ + "${"$"}numberLong":"0" + } + }, + "booleanRealmDictionary":{ + "A":true + }, + "doubleRealmDictionary":{ + "A":{ + "${"$"}numberDouble":"0.0" + } + }, + "floatRealmDictionary":{ + "A":{ + "${"$"}numberDouble":"0.0" + } + }, + "decimal128RealmDictionary":{ + "A":{ + "${"$"}numberDecimal":"0.0" + } + }, + "realmInstantRealmDictionary":{ + "A":{ + "${"$"}date":{ + "${"$"}numberLong":"-9223372036854775808" + } + } + }, + "objectIdRealmDictionary":{ + "key":{ + "${"$"}oid":"000102030405060708090a0b" + } + }, + "realmUUIDRealmDictionary":{ + "key":{ + "${"$"}binary":{ + "base64":"AAECAwQFBgcICQoLDA0ODw==", + "subType":"04" + } + } + }, + "binaryRealmDictionary":{ + "A":{ + "${"$"}binary":{ + "base64":"Kg==", + "subType":"00" + } + } + }, + "nullableObjectRealmDictionary":{ + + }, + "nullableRealmAnyRealmDictionary":{ + "A":{ + "${"$"}numberLong":"42" + } + } +} +""".trimIndent() From 0d56526603213c01f02f09f639c24aa704c09a37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Wed, 15 May 2024 14:27:22 +0200 Subject: [PATCH 33/36] Add missing PublishedApi annotations --- .../kotlin/io/realm/kotlin/internal/platform/RealmObject.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/platform/RealmObject.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/platform/RealmObject.kt index 5c17d10bfb..610a56e443 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/platform/RealmObject.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/platform/RealmObject.kt @@ -25,10 +25,12 @@ import kotlin.reflect.KClass * associated [RealmObjectCompanion], in which case the `clazz` wasn't a user defined class * implementing [BaseRealmObject] augmented by our compiler plugin. */ +@PublishedApi internal expect fun realmObjectCompanionOrNull(clazz: KClass): RealmObjectCompanion? /** * Returns the [RealmObjectCompanion] associated with a given [BaseRealmObject]'s [KClass]. */ +@PublishedApi internal expect fun realmObjectCompanionOrThrow(clazz: KClass): RealmObjectCompanion From 53db0335b8d0e76436ccdaecb2b9b8bc9da75943 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Fri, 17 May 2024 10:38:58 +0200 Subject: [PATCH 34/36] Remove debug logging --- .../kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt index 65b9d2e2d9..2b7e9e0255 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt @@ -25,8 +25,6 @@ import io.realm.kotlin.entities.sync.ParentCollectionDataType import io.realm.kotlin.ext.asRealmObject import io.realm.kotlin.ext.query import io.realm.kotlin.internal.platform.runBlocking -import io.realm.kotlin.log.LogLevel -import io.realm.kotlin.log.RealmLog import io.realm.kotlin.mongodb.User import io.realm.kotlin.mongodb.exceptions.ServiceException import io.realm.kotlin.mongodb.ext.aggregate @@ -710,7 +708,6 @@ sealed class MongoCollectionTests { val childCollection = collection() assertEquals(0, childCollection.find().size) val unmanagedChild = ChildCollectionDataType() - RealmLog.level = LogLevel.ALL assertEquals(unmanagedChild._id, childCollection.insertOne(unmanagedChild)) // We can't rely on the translator to incorporate the insertOnes in order so we need to // assure that the child is actually added before verifying the link in the parent. From 98f93406cc9ef852d8438f46366768812c124af5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 21 May 2024 10:29:13 +0200 Subject: [PATCH 35/36] Updates according to review comments --- CHANGELOG.md | 2 +- .../io/realm/kotlin/entities/sync/SyncObjectWithAllTypes.kt | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a353638eb5..97648bc873 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ This release will bump the Realm file format from version 23 to 24. Opening a fi * Support for RealmLists and RealmDictionaries in `RealmAny`. (Issue [#1434](https://github.com/realm/realm-kotlin/issues/1434)) * Optimized `RealmList.indexOf()` and `RealmList.contains()` using Core implementation of operations instead of iterating elements and comparing them in Kotlin. (Issue [#1625](https://github.com/realm/realm-kotlin/pull/1666) [RKOTLIN-995](https://jira.mongodb.org/browse/RKOTLIN-995)). * Add support for filtering logs by category. (Issue [#1691](https://github.com/realm/realm-kotlin/issues/1691) [JIRA](https://jira.mongodb.org/browse/RKOTLIN-1038)) -* [Sync] Add Mongo Client API to access Atlas App Service collections. It can be accessed through `User.mongoClient`. (Issue [#972](https://github.com/realm/realm-kotlin/issues/972)) +* [Sync] Add Mongo Client API to access Atlas App Service collections. It can be accessed through `User.mongoClient`. (Issue [#972](https://github.com/realm/realm-kotlin/issues/972)/[RKOTLIN-612](https://jira.mongodb.org/browse/RKOTLIN-612)) ### Fixed * None. diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/SyncObjectWithAllTypes.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/SyncObjectWithAllTypes.kt index f1b61f906f..4d110b09c6 100644 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/SyncObjectWithAllTypes.kt +++ b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/SyncObjectWithAllTypes.kt @@ -31,6 +31,7 @@ import io.realm.kotlin.types.RealmObject import io.realm.kotlin.types.RealmSet import io.realm.kotlin.types.RealmUUID import io.realm.kotlin.types.annotations.PrimaryKey +import org.mongodb.kbson.BsonObjectId import org.mongodb.kbson.Decimal128 import kotlin.math.absoluteValue import kotlin.random.Random @@ -43,7 +44,7 @@ class SyncObjectWithAllTypes : RealmObject { @PrimaryKey @Suppress("VariableNaming") - var _id: String = "id-${Random.nextLong().absoluteValue}" + var _id: String = "id-${BsonObjectId()}" // Non-nullable types var stringField: String = "hello world" From 656ba492cdfa1cd05069195524273cdc639eb7bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 21 May 2024 10:58:14 +0200 Subject: [PATCH 36/36] Another round for linting --- .../io/realm/kotlin/entities/sync/SyncObjectWithAllTypes.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/SyncObjectWithAllTypes.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/SyncObjectWithAllTypes.kt index 4d110b09c6..41bda4032c 100644 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/SyncObjectWithAllTypes.kt +++ b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/SyncObjectWithAllTypes.kt @@ -33,8 +33,6 @@ import io.realm.kotlin.types.RealmUUID import io.realm.kotlin.types.annotations.PrimaryKey import org.mongodb.kbson.BsonObjectId import org.mongodb.kbson.Decimal128 -import kotlin.math.absoluteValue -import kotlin.random.Random private typealias FieldDataFactory = (SyncObjectWithAllTypes) -> Unit private typealias FieldValidator = (SyncObjectWithAllTypes) -> Unit