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 1 commit
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
Prev Previous commit
Next Next commit
API experiments
  • Loading branch information
rorbech committed Dec 5, 2023
commit 8e37043a65264c648119247c89cbd18db0be9aa4
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -27,4 +30,12 @@ internal class MongoDatabaseImpl(
) : MongoDatabase {
override fun collection(collectionName: String): MongoCollection =
MongoCollectionImpl(this, collectionName)

override fun typedCollectionbson(collectionName: String): TypedMongoCollection<BsonValue, BsonValue> {
return TypedMongoCollectionImpl(collection(collectionName) as MongoCollectionImpl)
}

override fun <T, R> typedCollection(collectionName: String): TypedMongoCollection<T, R> {
return TypedMongoCollectionImpl(collection(collectionName) as MongoCollectionImpl)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,24 @@
* 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
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
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 {

Expand All @@ -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 <T, K> typedCollection(): TypedMongoCollection<T, K> = TypedMongoCollectionImpl<T, K>(this as MongoCollectionImpl)
}

public interface TypedMongoCollection<T> {

}
public interface TypedMongoCollection<T, K>: MongoCollection { }

@PublishedApi
internal class TypedMongoCollectionImpl<T, K>(collectionImpl: MongoCollectionImpl): TypedMongoCollection<T, K>, MongoCollectionImpl(collectionImpl.database, collectionImpl.name)

@OptIn(ExperimentalKBsonSerializerApi::class)
@ExperimentalRealmSerializerApi
public suspend inline fun <reified R> MongoCollection.findOne(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null): R {
return EJson.decodeFromBsonValue((this as MongoCollectionImpl).call("findOne") {
public suspend inline fun <reified R> MongoCollection.findOne(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null, serializer: KSerializer<R>? = 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 <reified R> MongoCollection.decodeFromBsonValue(bsonValue: BsonValue, serializer: KSerializer<R>? = 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 {

}
Expand Down Expand Up @@ -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 <T> MongoCollection.count(filter: BsonDocument, limit: Long): Long {
Expand Down Expand Up @@ -127,6 +139,18 @@ public suspend inline fun <reified T> MongoCollection.findOne(configuration: Fin
return EJson.decodeFromBsonValue(response)
}

@OptIn(ExperimentalKBsonSerializerApi::class)
@ExperimentalRealmSerializerApi
public suspend inline fun <reified T> MongoCollection.find(filter: BsonDocument? = null, limit: Long? = null, projection: BsonDocument? = null, sort: BsonDocument? = null): List<T> {
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<T> } else {objects.map {EJson.decodeFromBsonValue(it) } }
}

//@ExperimentalRealmSerializerApi
//@OptIn(ExperimentalKBsonSerializerApi::class)
//public suspend inline fun <reified T, reified R> MongoCollection.insertOne(document: T): R {
Expand All @@ -149,8 +173,15 @@ public suspend inline fun <reified T> MongoCollection.findOne(configuration: Fin
// }.asDocument().get("insertedId")!!.asObjectId()
//}

// insertOne(doc:): Bson(insertedId)
// document
@ExperimentalRealmSerializerApi
@OptIn(ExperimentalKBsonSerializerApi::class)
public suspend inline fun <reified T> MongoCollection.aggregate(pipeline: List<BsonDocument>): List<T> {
val insertedId: List<BsonValue> = (this as MongoCollectionImpl).call("aggregate") {
put("pipeline", BsonArray(pipeline))
}.asArray().toList()
return if (T::class == BsonValue::class) { insertedId as List<T> } else { insertedId.map { EJson.decodeFromBsonValue(it) } }
}

@ExperimentalRealmSerializerApi
@OptIn(ExperimentalKBsonSerializerApi::class)
public suspend inline fun <reified T, reified R : Any> MongoCollection.insertOne(document: T): R {
Expand All @@ -167,6 +198,30 @@ public suspend inline fun <reified T, reified R : Any> 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 <reified T, reified R : Any> TypedMongoCollection<T, R>.insertMany(
documents: Collection<T>,
): List<R> {
val encodedDocument: BsonValue = EJson.encodeToBsonValue(
EJson.serializersModule.serializerOrRealmBuiltInSerializer(),
documents
)
val insertedId: List<BsonValue> = (this as MongoCollectionImpl).call("insertMany") {
put("documents", encodedDocument)
}.asDocument().get("insertedIds")!!.asArray().toList()
return if (R::class == BsonValue::class) {
insertedId as List<R>
} else {
insertedId.map { decodeFromBsonValue(it) }
}
}

@ExperimentalRealmSerializerApi
public suspend inline fun <reified T, reified R : Any> TypedMongoCollection<*, *>.insertMany(documents: Collection<T>): List<R> =
(this as TypedMongoCollectionImpl<T, R>).insertMany<T, R>(documents)

@ExperimentalRealmSerializerApi
@OptIn(ExperimentalKBsonSerializerApi::class)
public suspend inline fun <reified T, reified R : Any> MongoCollection.insertMany(documents: Collection<T>): List<R> {
Expand Down Expand Up @@ -248,7 +303,6 @@ public suspend inline fun <reified R : Any> 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 <reified R : Any> MongoCollection.findOneAndUpdate(
Expand All @@ -259,29 +313,21 @@ public suspend inline fun <reified R : Any> 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 <reified R : Any> MongoCollection.findOneAndReplace(
Expand All @@ -292,52 +338,38 @@ public suspend inline fun <reified R : Any> 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 <reified R : Any> 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??
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand All @@ -27,5 +30,8 @@ public interface MongoDatabase {
public val name: String

public fun collection(collectionName: String): MongoCollection
public fun typedCollectionbson(collectionName: String): TypedMongoCollection<BsonValue, BsonValue>
public fun <T, R> typedCollection(collectionName: String): TypedMongoCollection<T, R>

}

Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -162,6 +165,11 @@ class MongoClientTest {
// Projection select field
// Limit
// Sort
val y: BsonDocument = collection.findOne<BsonDocument>(filter = BsonDocument("name", "dog-0"))

val y2: BsonValue = collection.findOne<BsonValue>(filter = BsonDocument("name", "dog-0"))
println(y)
println(y2)
}

@OptIn(ExperimentalKBsonSerializerApi::class)
Expand Down Expand Up @@ -310,6 +318,16 @@ class MongoClientTest {
// }
// }
// }

@Test
fun find() = runBlocking<Unit> {
RealmLog.level = LogLevel.ALL
assertTrue { collection.find<SyncDog>().isEmpty() }

val x: List<ObjectId> = collection.insertMany(listOf(SyncDog("dog1"), SyncDog("dog2")))
assertEquals(2, collection.find<SyncDog>().size)
}

//
// @Test
// fun find() {
Expand Down Expand Up @@ -374,6 +392,18 @@ class MongoClientTest {
// }
// }
//

@Test
fun aggregate() = runBlocking<Unit> {
RealmLog.level = LogLevel.ALL
collection.aggregate<SyncDog>(listOf())

val x: List<ObjectId> = collection.insertMany(listOf(SyncDog(name = "dog1"), SyncDog(name = "dog2")))
collection.aggregate<SyncDog>(listOf())


collection.aggregate<SyncDog>(listOf(BsonDocument("\$sort", BsonDocument("name", -1)), BsonDocument("\$limit", 1)))
}
// @Test
// fun aggregate() {
// with(getCollectionInternal()) {
Expand Down Expand Up @@ -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)
}

//
Expand Down Expand Up @@ -507,7 +540,20 @@ class MongoClientTest {
println(x)
println(y)

val typedCollection = collection.typedCollection<SyncDog, ObjectId>()
val z: List<ObjectId> = typedCollection.insertMany(listOf(SyncDog("sadf")))
val tyz = typedCollection.insertMany<SyncDog, BsonValue>(listOf(SyncDog("sadf")))

val bsonSyncDogs /*: TypedMongoCollection<BsonValue, BsonValue> */ = database.typedCollectionbson("SyncDog")
val insertMany /*: List<BsonValue> */ = bsonSyncDogs.insertMany(listOf(BsonDocument("name", "x")))

val syncDogs: TypedMongoCollection<SyncDog, ObjectId> = database.typedCollection<SyncDog, ObjectId>("SyncDog")

val objectIds = syncDogs.insertMany(listOf(SyncDog("name")))

val objectIds2: List<ObjectId> = syncDogs.insertMany<BsonValue, ObjectId>(listOf(BsonDocument("name", "asdf")))
}

//
// @Test
// fun insertMany_singleDocument() {
Expand Down Expand Up @@ -1261,7 +1307,6 @@ class MongoClientTest {
val x: SyncDog = collection.findOneAndDelete<SyncDog>(
BsonDocument(),
BsonDocument("""{ "name": "dog1" }"""),
upsert = true
)
}
//
Expand Down