Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RKOTLIN-612] MongoClient API #1593

Merged
merged 44 commits into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
99489a4
Initial scaffolding of API
rorbech Dec 4, 2023
8e37043
API experiments
rorbech Dec 5, 2023
77c5a68
More API experiments
rorbech Dec 5, 2023
673b7b2
Clean up API experiments
rorbech Dec 6, 2023
4a60867
Liniting
rorbech Dec 8, 2023
a6a1fb0
Clean up internal function call
rorbech Dec 8, 2023
f4a35f9
Fix nullability of findOne-variants and add custom serialization tests
rorbech Dec 13, 2023
1e221b9
More testing
rorbech Dec 15, 2023
bc3acb8
Another round for linting
rorbech Dec 18, 2023
82c85ac
Add CHANGELOG entry
rorbech Dec 18, 2023
e1c00c8
More test coverage
rorbech Dec 18, 2023
e492629
More testing
rorbech Dec 18, 2023
4818ad2
Update test after fixing bson deserializer
rorbech Dec 19, 2023
caa9e6b
Minor clean ups
rorbech Dec 19, 2023
58ded6d
Updates according to review comments
rorbech Dec 19, 2023
d5591da
Updates according to review comments
rorbech Dec 19, 2023
490b8ef
Merge branch 'main' into cr/mongo-client
rorbech Dec 19, 2023
9e65380
Clean up experimental kbson serialization annotation usage
rorbech Dec 20, 2023
b1e740e
Add data classes for UpdateOne and UpdateMany responses
rorbech Dec 20, 2023
27d2e51
Add documentation of collection operations
rorbech Dec 20, 2023
f46cad5
Apply suggestions from code review
rorbech Jan 2, 2024
7ce74af
Link serialization
rorbech Feb 13, 2024
988e69a
Merge branch 'main' into cr/mongo-client
rorbech Feb 15, 2024
d09e23a
Merge remote-tracking branch 'origin/cr/mongo-client' into cr/mongo-c…
rorbech Feb 15, 2024
88750e8
Updates according to review comments
rorbech Feb 15, 2024
63a300e
Typed link serialization
rorbech Apr 5, 2024
8743faa
Merge branch 'main' into cr/mongo-client
rorbech Apr 5, 2024
22ab855
Clean up and linting
rorbech Apr 5, 2024
51e3e29
Test upcoming kbson release
rorbech Apr 10, 2024
9d2d6b4
Merge branch 'main' into cr/mongo-client
rorbech Apr 10, 2024
7006b9e
Fix CopyFromRealmTests
rorbech Apr 10, 2024
81013a2
Use public available kbson 0.4.0
rorbech Apr 10, 2024
ab6d33e
Increace device farm timeout
rorbech Apr 11, 2024
40e01be
Remove primary key type generic type paramenter
rorbech May 6, 2024
b2f8008
Updates according to review comments
rorbech May 13, 2024
08525c9
Merge branch 'main' into cr/mongo-client
rorbech May 13, 2024
c40fce4
Add support for serialization of collections in mixed
rorbech May 13, 2024
2be6411
Add tests for MongoDBSerializer
rorbech May 15, 2024
0d56526
Add missing PublishedApi annotations
rorbech May 15, 2024
4bcba20
Merge branch 'main' into cr/mongo-client
rorbech May 17, 2024
53db033
Remove debug logging
rorbech May 17, 2024
98f9340
Updates according to review comments
rorbech May 21, 2024
656ba49
Another round for linting
rorbech May 21, 2024
a70ae52
Merge branch 'main' into cr/mongo-client
rorbech May 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

### Enhancements
* [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] 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))
Expand Down Expand Up @@ -37,6 +38,7 @@

### Enhancements
* None.
>>>>>>> main
rorbech marked this conversation as resolved.
Show resolved Hide resolved

### 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)
Expand Down
2 changes: 1 addition & 1 deletion buildSrc/src/main/kotlin/Config.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
rorbech marked this conversation as resolved.
Show resolved Hide resolved
// 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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -732,6 +732,7 @@ expect object RealmInterop {
app: RealmAppPointer,
user: RealmUserPointer,
name: String,
serviceName: String? = null,
serializedEjsonArgs: String, // as ejson
callback: AppCallback<String>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1616,14 +1616,16 @@ actual object RealmInterop {
)
}

@Suppress("LongParameterList")
actual fun realm_app_call_function(
app: RealmAppPointer,
user: RealmUserPointer,
name: String,
serviceName: String?,
serializedEjsonArgs: String,
callback: AppCallback<String>
) {
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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3152,6 +3152,7 @@ actual object RealmInterop {
app: RealmAppPointer,
user: RealmUserPointer,
name: String,
serviceName: String?,
serializedEjsonArgs: String,
callback: AppCallback<String>
) {
Expand All @@ -3160,7 +3161,7 @@ actual object RealmInterop {
user.cptr(),
name,
serializedEjsonArgs,
null,
serviceName,
staticCFunction { userData: CPointer<out CPointed>?, data: CPointer<ByteVarOf<Byte>>?, error: CPointer<realm_app_error_t>? ->
handleAppCallback(userData, error) {
data.safeKString()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@ 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 <T : Any> realmObjectCompanionOrNull(clazz: KClass<T>): RealmObjectCompanion?

/**
* Returns the [RealmObjectCompanion] associated with a given [BaseRealmObject]'s [KClass].
*/
@PublishedApi
internal expect fun <T : BaseRealmObject> realmObjectCompanionOrThrow(clazz: KClass<T>): RealmObjectCompanion
Original file line number Diff line number Diff line change
Expand Up @@ -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 <reified T : Any?> isType(arg: Any?, errorMessage: String) {
public inline fun <reified T : Any?> isType(arg: Any?, errorMessage: String = "Object '$arg' is not of type ${T::class.qualifiedName}") {
contract {
returns() implies (arg is T)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
@PublishedApi
internal actual fun <T : Any> realmObjectCompanionOrNull(clazz: KClass<T>): RealmObjectCompanion? =
if (clazz.companionObjectInstance is RealmObjectCompanion) {
clazz.companionObjectInstance as RealmObjectCompanion
} else null

@PublishedApi
internal actual fun <T : BaseRealmObject> realmObjectCompanionOrThrow(clazz: KClass<T>): 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.")
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@ import kotlin.reflect.ExperimentalAssociatedObjects
import kotlin.reflect.KClass
import kotlin.reflect.findAssociatedObject

@PublishedApi
internal actual fun <T : Any> realmObjectCompanionOrNull(clazz: KClass<T>): RealmObjectCompanion? =
@OptIn(ExperimentalAssociatedObjects::class)
when (val associatedObject = clazz.findAssociatedObject<ModelObject>()) {
is RealmObjectCompanion -> associatedObject
else -> null
}

@PublishedApi
internal actual fun <T : BaseRealmObject> realmObjectCompanionOrThrow(clazz: KClass<T>): 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.")
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ 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
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
Expand Down Expand Up @@ -201,6 +204,20 @@ public interface User {
*/
public suspend fun linkCredentials(credentials: Credentials): 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
* [AppConfiguration.Builder.ejson].
*/
@ExperimentalKBsonSerializerApi
public fun mongoClient(serviceName: String, eJson: EJson? = null): MongoClient

/**
* Two Users are considered equal if they have the same user identity and are associated
* with the same app.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,56 +62,13 @@ public inline fun User.customDataAsBsonDocument(): BsonDocument? =
*/
@ExperimentalRealmSerializerApi
@OptIn(ExperimentalKBsonSerializerApi::class)
public fun <T> User.profile(serializer: KSerializer<T>): T =
public inline fun <reified T> User.profile(serializer: KSerializer<T> = (this as UserImpl).app.configuration.ejson.serializersModule.serializerOrRealmBuiltInSerializer()): T =
(this as UserImpl).app.configuration.ejson.let { ejson: EJson ->
profileInternal { ejsonEncodedProfile ->
ejson.decodeFromString(serializer, ejsonEncodedProfile)
}
}

/**
* 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 <reified T> User.profile(): T =
profile(
(this as UserImpl).app
.configuration
.ejson
.serializersModule
.serializerOrRealmBuiltInSerializer<T>()
)

/**
* 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 <reified T> User.customData(): T? =
customData(
(this as UserImpl).app
.configuration
.ejson
.serializersModule
.serializerOrRealmBuiltInSerializer<T>()
)

/**
* Returns the custom user data associated with the user in the Realm App as [T].
*
Expand All @@ -127,7 +84,7 @@ public inline fun <reified T> User.customData(): T? =
*/
@ExperimentalRealmSerializerApi
@OptIn(ExperimentalKBsonSerializerApi::class)
public fun <T> User.customData(serializer: KSerializer<T>): T? =
public inline fun <reified T> User.customData(serializer: KSerializer<T> = (this as UserImpl).app.configuration.ejson.serializersModule.serializerOrRealmBuiltInSerializer()): T? =
(this as UserImpl).app.configuration.ejson.let { ejson: EJson ->
customDataInternal { ejsonEncodedCustomData ->
ejson.decodeFromString(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,25 @@ 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,
functionName: String,
serializedEjsonArgs: String
): String = Channel<Result<String>>(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 ->
// First we decode from ejson -> BsonValue
Expand All @@ -44,4 +48,7 @@ internal class FunctionsImpl(

return channel.receive().getOrThrow()
}

internal suspend fun callInternal(functionName: String, bsonValue: BsonValue): BsonValue =
Bson(callInternal(functionName, Bson.toJson(bsonValue)))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* 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
import org.mongodb.kbson.ExperimentalKBsonSerializerApi
import org.mongodb.kbson.serialization.EJson

@PublishedApi
@OptIn(ExperimentalKBsonSerializerApi::class)
internal class MongoClientImpl(
internal val user: UserImpl,
override val serviceName: String,
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)
}
Loading