From 6d2b0fe5f38fa2f4626444aba15230303c19487f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Sat, 26 Oct 2024 13:15:45 +0200 Subject: [PATCH 1/7] build: Create the :driver-sync and :driver-coroutines modules --- driver-coroutines/build.gradle.kts | 51 ++++++++++++++++++++++++++++++ driver-sync/build.gradle.kts | 51 ++++++++++++++++++++++++++++++ gradle/libs.versions.toml | 3 ++ settings.gradle.kts | 3 ++ 4 files changed, 108 insertions(+) create mode 100644 driver-coroutines/build.gradle.kts create mode 100644 driver-sync/build.gradle.kts diff --git a/driver-coroutines/build.gradle.kts b/driver-coroutines/build.gradle.kts new file mode 100644 index 00000000..cdfecda0 --- /dev/null +++ b/driver-coroutines/build.gradle.kts @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2024, OpenSavvy and contributors. + * + * 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. + */ + +plugins { + alias(opensavvyConventions.plugins.base) + alias(opensavvyConventions.plugins.kotlin.library) +} + +kotlin { + jvm() + js { + nodejs() + } + + sourceSets.commonMain.dependencies { + api(projects.dsl) + } + + sourceSets.jvmMain.dependencies { + api(libs.mongodb.coroutines.jvm) + } + + sourceSets.commonTest.dependencies { + implementation(libs.prepared) + implementation(opensavvyConventions.aligned.kotlin.test) + } +} + +library { + name.set("MongoDB coroutines driver for Kotlin") + description.set("Kotlin-first MongoDB driver") + homeUrl.set("https://gitlab.com/opensavvy/ktmongo") + + license.set { + name.set("Apache 2.0") + url.set("https://www.apache.org/licenses/LICENSE-2.0.txt") + } +} diff --git a/driver-sync/build.gradle.kts b/driver-sync/build.gradle.kts new file mode 100644 index 00000000..f0e1ebc8 --- /dev/null +++ b/driver-sync/build.gradle.kts @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2024, OpenSavvy and contributors. + * + * 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. + */ + +plugins { + alias(opensavvyConventions.plugins.base) + alias(opensavvyConventions.plugins.kotlin.library) +} + +kotlin { + jvm() + js { + nodejs() + } + + sourceSets.commonMain.dependencies { + api(projects.dsl) + } + + sourceSets.jvmMain.dependencies { + api(libs.mongodb.sync.jvm) + } + + sourceSets.commonTest.dependencies { + implementation(libs.prepared) + implementation(opensavvyConventions.aligned.kotlin.test) + } +} + +library { + name.set("MongoDB synchronous driver for Kotlin") + description.set("Kotlin-first MongoDB driver") + homeUrl.set("https://gitlab.com/opensavvy/ktmongo") + + license.set { + name.set("Apache 2.0") + url.set("https://www.apache.org/licenses/LICENSE-2.0.txt") + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1672c7b7..5c3b4a35 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,6 +4,7 @@ prepared = "1.4.0" # https://gitlab.com/opensavvy/groundwork/prepared/-/releases mongodb-bson-jvm = "5.1.4" # https://central.sonatype.com/artifact/org.mongodb/bson mongodb-bson-js = "6.8.0" # https://www.npmjs.com/package/bson +mongodb-driver = "5.2.0" # https://central.sonatype.com/artifact/org.mongodb/mongodb-driver-kotlin-coroutine [plugins] @@ -11,5 +12,7 @@ mongodb-bson-js = "6.8.0" # https://www.npmjs.com/package/bson prepared = { module = "dev.opensavvy.prepared:runner-kotest", version.ref = "prepared" } mongodb-bson-jvm = { module = "org.mongodb:bson", version.ref = "mongodb-bson-jvm" } +mongodb-coroutines-jvm = { module = "org.mongodb:mongodb-driver-kotlin-coroutine", version.ref = "mongodb-driver" } +mongodb-sync-jvm = { module = "org.mongodb:mongodb-driver-kotlin-sync", version.ref = "mongodb-driver" } [bundles] diff --git a/settings.gradle.kts b/settings.gradle.kts index 78b850da..4447be0b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -57,6 +57,9 @@ include( "bson", "dsl", + "driver-sync", + "driver-coroutines", + "gradle:templates:template-app", "gradle:templates:template-lib", ) -- 2.51.2 From 282445bb6321c1656c93c3804717731b7f1a67bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Sat, 26 Oct 2024 14:49:16 +0200 Subject: [PATCH 2/7] feat(driver-sync): Create MongoCollection & MongoIterable --- .../src/commonMain/kotlin/MongoCollection.kt | 39 ++++++++ .../src/commonMain/kotlin/MongoIterable.kt | 86 ++++++++++++++++++ .../kotlin/operations/FindOperations.kt | 90 +++++++++++++++++++ 3 files changed, 215 insertions(+) create mode 100644 driver-sync/src/commonMain/kotlin/MongoCollection.kt create mode 100644 driver-sync/src/commonMain/kotlin/MongoIterable.kt create mode 100644 driver-sync/src/commonMain/kotlin/operations/FindOperations.kt diff --git a/driver-sync/src/commonMain/kotlin/MongoCollection.kt b/driver-sync/src/commonMain/kotlin/MongoCollection.kt new file mode 100644 index 00000000..1ae8801b --- /dev/null +++ b/driver-sync/src/commonMain/kotlin/MongoCollection.kt @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2024, OpenSavvy and contributors. + * + * 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 opensavvy.ktmongo.sync + +import opensavvy.ktmongo.bson.BsonContext +import opensavvy.ktmongo.dsl.LowLevelApi +import opensavvy.ktmongo.sync.operations.FindOperations + +/** + * Methods to interact with a MongoDB collection. + * + * ### Operations + * + * - [find] + * - [findOne] + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/tutorial/query-documents) + */ +interface MongoCollection : FindOperations { + + @LowLevelApi + val context: BsonContext +} diff --git a/driver-sync/src/commonMain/kotlin/MongoIterable.kt b/driver-sync/src/commonMain/kotlin/MongoIterable.kt new file mode 100644 index 00000000..f844ea15 --- /dev/null +++ b/driver-sync/src/commonMain/kotlin/MongoIterable.kt @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2024, OpenSavvy and contributors. + * + * 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 opensavvy.ktmongo.sync + +/** + * Streaming-ready iterable client to read data from the database. + */ +interface MongoIterable { + + // region Extract data + + /** + * Returns the first document found by this query, or throws an exception. + * + * @throws NoSuchElementException If this query returned no results. + * @see firstOrNull + */ + fun first(): Document = + firstOrNull() ?: throw NoSuchElementException("No element was returned by this query") + + /** + * Returns the first document found by this query, or `null` if none were found. + * + * @see first + */ + fun firstOrNull(): Document? + + /** + * Executes [action] for each document returned by this query. + * + * This method streams all returned elements into the [action] function. + * The entire response is not loaded at once into memory. + */ + fun forEach(action: (Document) -> Unit) + + // endregion + // region To another data structure + + /** + * Reads the entirety of this response into a [List]. + * + * Since lists are in-memory, this will load the entirety of the results of this query into memory. + * + * @see toSet + */ + fun toList(): List { + val list = ArrayList() + forEach { list.add(it) } + return list + } + + /** + * Reads the entirety of this response into a [Set]. + * + * Since sets are in-memory, this will load the entirety of the results of this query into memory. + * + * @see toList + */ + fun toSet(): Set { + val set = LinkedHashSet() + forEach { set.add(it) } + return set + } + + @Deprecated("Kotlin Sequences are not capable of closing a resource after they are done. Using sequences with a MongoIterable will create memory leaks. Instead, use toList, forEach, asStream (Java only) or the coroutines driver's asFlow", ReplaceWith("this.toList().asSequence()"), level = DeprecationLevel.ERROR) + fun asSequence(): Sequence = throw UnsupportedOperationException("Sequences are not supported because they create memory lists. Use lists, streams, flows, or simply forEach instead.") + + @Deprecated("Kotlin Sequences are not capable of closing a resource after they are done. Using sequences with a MongoIterable will create memory leaks. Instead, use toList, forEach, asStream (Java only) or the coroutines driver's asFlow", ReplaceWith("this.toList().asSequence()"), level = DeprecationLevel.ERROR) + fun toSequence(): Sequence = throw UnsupportedOperationException("Sequences are not supported because they create memory lists. Use lists, streams, flows, or simply forEach instead.") + + // endregion +} diff --git a/driver-sync/src/commonMain/kotlin/operations/FindOperations.kt b/driver-sync/src/commonMain/kotlin/operations/FindOperations.kt new file mode 100644 index 00000000..fc31e33a --- /dev/null +++ b/driver-sync/src/commonMain/kotlin/operations/FindOperations.kt @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2024, OpenSavvy and contributors. + * + * 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 opensavvy.ktmongo.sync.operations + +import opensavvy.ktmongo.dsl.expr.FilterExpression +import opensavvy.ktmongo.sync.MongoIterable + +/** + * Interface grouping MongoDB operations allowing to search for information. + */ +interface FindOperations { + + /** + * Finds all documents in this collection. + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/method/db.collection.find/) + */ + fun find(): MongoIterable + + /** + * Finds all documents in this collection that satisfy [predicate]. + * + * If multiple predicates are specified, an [and][FilterExpression.and] operator is implied. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int, + * ) + * + * collection.find { + * User::name eq "foo" + * User::age eq 10 + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/method/db.collection.find/) + * + * @see findOne When only one result is expected. + */ + fun find(predicate: FilterExpression.() -> Unit): MongoIterable + + /** + * Finds a document in this collection that satisfies [predicate]. + * + * If multiple predicates are specified, and [and][FilterExpression.and] operator is implied. + * + * This function doesn't check that there is exactly one value in the collection. + * It simply returns the first matching document it finds. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int, + * ) + * + * collection.findOne { + * User::name eq "foo" + * User::age eq 10 + * } + * ``` + * + * @see find When multiple results are expected. + */ + fun findOne(predicate: FilterExpression.() -> Unit): Document? = + find(predicate).firstOrNull() + +} -- 2.51.2 From 3ad01048fef8436539cbc194ef67d046aafb707c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Sat, 26 Oct 2024 14:49:42 +0200 Subject: [PATCH 3/7] feat(driver-sync): JVM implementation of MongoCollection & MongoIterable --- .../src/jvmMain/kotlin/JvmMongoCollection.kt | 72 ++++++++++++++ .../src/jvmMain/kotlin/JvmMongoIterable.kt | 99 +++++++++++++++++++ 2 files changed, 171 insertions(+) create mode 100644 driver-sync/src/jvmMain/kotlin/JvmMongoCollection.kt create mode 100644 driver-sync/src/jvmMain/kotlin/JvmMongoIterable.kt diff --git a/driver-sync/src/jvmMain/kotlin/JvmMongoCollection.kt b/driver-sync/src/jvmMain/kotlin/JvmMongoCollection.kt new file mode 100644 index 00000000..8cfd5cf1 --- /dev/null +++ b/driver-sync/src/jvmMain/kotlin/JvmMongoCollection.kt @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2024, OpenSavvy and contributors. + * + * 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 opensavvy.ktmongo.sync + +import opensavvy.ktmongo.bson.BsonContext +import opensavvy.ktmongo.bson.buildBsonDocument +import opensavvy.ktmongo.dsl.LowLevelApi +import opensavvy.ktmongo.dsl.expr.FilterExpression +import opensavvy.ktmongo.dsl.expr.common.AbstractCompoundExpression +import org.bson.BsonDocument + +/** + * Implementation of [MongoCollection] based on [MongoDB's MongoCollection][com.mongodb.kotlin.client.MongoCollection]. + * + * To access the inner iterable, see [asKotlinClient]. + * + * To convert an existing MongoDB iterable into an instance of this class, see [asKtMongo]. + */ +class JvmMongoCollection internal constructor( + private val inner: com.mongodb.kotlin.client.MongoCollection +) : MongoCollection { + + @LowLevelApi + fun asKotlinClient() = inner + + @LowLevelApi + override val context: BsonContext + get() = BsonContext(inner.codecRegistry) + + // region Find + + override fun find(): JvmMongoIterable = + JvmMongoIterable(inner.find()) + + @OptIn(LowLevelApi::class) + override fun find(predicate: FilterExpression.() -> Unit): JvmMongoIterable { + val filter = FilterExpression(context) + .apply(predicate) + .toBsonDocument() + + return JvmMongoIterable(inner.find(filter)) + } + + // endregion + +} + +@OptIn(LowLevelApi::class) +private fun AbstractCompoundExpression.toBsonDocument(): BsonDocument = + buildBsonDocument { + writeTo(this) + } + +/** + * Converts a [MongoDB collection][com.mongodb.kotlin.client.MongoCollection] into a [KtMongo collection][JvmMongoCollection]. + */ +fun com.mongodb.kotlin.client.MongoCollection.asKtMongo(): JvmMongoCollection = + JvmMongoCollection(this) diff --git a/driver-sync/src/jvmMain/kotlin/JvmMongoIterable.kt b/driver-sync/src/jvmMain/kotlin/JvmMongoIterable.kt new file mode 100644 index 00000000..a6305c47 --- /dev/null +++ b/driver-sync/src/jvmMain/kotlin/JvmMongoIterable.kt @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2024, OpenSavvy and contributors. + * + * 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 opensavvy.ktmongo.sync + +import com.mongodb.kotlin.client.MongoCursor +import opensavvy.ktmongo.dsl.LowLevelApi +import java.util.* +import java.util.Spliterator.* +import java.util.function.Consumer +import java.util.stream.Stream +import java.util.stream.StreamSupport + +/** + * Implementation of [MongoIterable] based on [MongoDB's MongoIterable][com.mongodb.kotlin.client.MongoIterable]. + * + * To access the inner iterable, see [asKotlinMongoIterable]. + * + * To convert an existing MongoDB iterable into an instance of this class, see [asKtMongo]. + */ +class JvmMongoIterable internal constructor( + private val inner: com.mongodb.kotlin.client.MongoIterable +) : MongoIterable { + + /** + * Converts a KtMongo [MongoIterable] into a [MongoDB MongoIterable][com.mongodb.kotlin.client.MongoIterable]. + */ + @LowLevelApi + fun asKotlinMongoIterable() = inner + + override fun first(): Document = + inner.first() + + override fun firstOrNull(): Document? = + inner.firstOrNull() + + override fun forEach(action: (Document) -> Unit) { + inner.forEach(action) + } + + override fun toList(): List = + inner.toList() + + /** + * Streams the results of this query into a Java [Stream]. + */ + fun asStream(): Stream { + val cursor = inner.cursor() + + return StreamSupport.stream( + /* spliterator = */ MongoSpliterator(cursor), + /* parallel = */ false, + ).onClose { cursor.close() } + } + + private class MongoSpliterator( + private val cursor: MongoCursor, + ) : Spliterator { + override fun tryAdvance(action: Consumer): Boolean { + if (cursor.hasNext()) { + action.accept(cursor.next()) + return true + } else { + return false + } + } + + override fun trySplit(): Spliterator? { + return null + } + + override fun estimateSize(): Long { + return Long.MAX_VALUE + } + + override fun characteristics(): Int = + ORDERED + NONNULL + IMMUTABLE + } +} + +/** + * Converts a [MongoDB MongoIterable][com.mongodb.kotlin.client.MongoIterable] into a + * [KtMongo MongoIterable][JvmMongoIterable]. + */ +fun com.mongodb.kotlin.client.MongoIterable.asKtMongo(): JvmMongoIterable = + JvmMongoIterable(this) -- 2.51.2 From 4ec092f57273260e1c82bf9bf5c203957aa7d062 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Sat, 26 Oct 2024 15:00:35 +0200 Subject: [PATCH 4/7] feat(driver-coroutines): Create MongoCollection & MongoIterable --- driver-coroutines/build.gradle.kts | 1 + .../src/commonMain/kotlin/MongoCollection.kt | 39 ++++++++ .../src/commonMain/kotlin/MongoIterable.kt | 93 +++++++++++++++++++ .../kotlin/operations/FindOperations.kt | 90 ++++++++++++++++++ gradle/libs.versions.toml | 3 + 5 files changed, 226 insertions(+) create mode 100644 driver-coroutines/src/commonMain/kotlin/MongoCollection.kt create mode 100644 driver-coroutines/src/commonMain/kotlin/MongoIterable.kt create mode 100644 driver-coroutines/src/commonMain/kotlin/operations/FindOperations.kt diff --git a/driver-coroutines/build.gradle.kts b/driver-coroutines/build.gradle.kts index cdfecda0..e4229de8 100644 --- a/driver-coroutines/build.gradle.kts +++ b/driver-coroutines/build.gradle.kts @@ -27,6 +27,7 @@ kotlin { sourceSets.commonMain.dependencies { api(projects.dsl) + api(libs.kotlinx.coroutines) } sourceSets.jvmMain.dependencies { diff --git a/driver-coroutines/src/commonMain/kotlin/MongoCollection.kt b/driver-coroutines/src/commonMain/kotlin/MongoCollection.kt new file mode 100644 index 00000000..73a80332 --- /dev/null +++ b/driver-coroutines/src/commonMain/kotlin/MongoCollection.kt @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2024, OpenSavvy and contributors. + * + * 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 opensavvy.ktmongo.coroutines + +import opensavvy.ktmongo.bson.BsonContext +import opensavvy.ktmongo.coroutines.operations.FindOperations +import opensavvy.ktmongo.dsl.LowLevelApi + +/** + * Methods to interact with a MongoDB collection. + * + * ### Operations + * + * - [find] + * - [findOne] + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/tutorial/query-documents) + */ +interface MongoCollection : FindOperations { + + @LowLevelApi + val context: BsonContext +} diff --git a/driver-coroutines/src/commonMain/kotlin/MongoIterable.kt b/driver-coroutines/src/commonMain/kotlin/MongoIterable.kt new file mode 100644 index 00000000..2f7f415b --- /dev/null +++ b/driver-coroutines/src/commonMain/kotlin/MongoIterable.kt @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2024, OpenSavvy and contributors. + * + * 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 opensavvy.ktmongo.coroutines + +import kotlinx.coroutines.flow.Flow + +/** + * Streaming-ready iterable client to read data from the database. + */ +interface MongoIterable { + + // region Extract data + + /** + * Returns the first document found by this query, or throws an exception. + * + * @throws NoSuchElementException If this query returned no results. + * @see firstOrNull + */ + suspend fun first(): Document = + firstOrNull() ?: throw NoSuchElementException("No element was returned by this query") + + /** + * Returns the first document found by this query, or `null` if none were found. + * + * @see first + */ + suspend fun firstOrNull(): Document? + + /** + * Executes [action] for each document returned by this query. + * + * This method streams all returned elements into the [action] function. + * The entire response is not loaded at once into memory. + */ + suspend fun forEach(action: suspend (Document) -> Unit) + + // endregion + // region To another data structure + + /** + * Reads the entirety of this response into a [List]. + * + * Since lists are in-memory, this will load the entirety of the results of this query into memory. + * + * @see toSet + */ + suspend fun toList(): List { + val list = ArrayList() + forEach { list.add(it) } + return list + } + + /** + * Reads the entirety of this response into a [Set]. + * + * Since sets are in-memory, this will load the entirety of the results of this query into memory. + * + * @see toList + */ + suspend fun toSet(): Set { + val set = LinkedHashSet() + forEach { set.add(it) } + return set + } + + /** + * Streams the results of this response into a [Flow]. + */ + fun asFlow(): Flow + + @Deprecated("Kotlin Sequences are not capable of closing a resource after they are done. Using sequences with a MongoIterable will create memory leaks. Instead, use toList, forEach, asStream (Java only) or the coroutines driver's asFlow", ReplaceWith("this.toList().asSequence()"), level = DeprecationLevel.ERROR) + fun asSequence(): Sequence = throw UnsupportedOperationException("Sequences are not supported because they create memory lists. Use lists, streams, flows, or simply forEach instead.") + + @Deprecated("Kotlin Sequences are not capable of closing a resource after they are done. Using sequences with a MongoIterable will create memory leaks. Instead, use toList, forEach, asStream (Java only) or the coroutines driver's asFlow", ReplaceWith("this.toList().asSequence()"), level = DeprecationLevel.ERROR) + fun toSequence(): Sequence = throw UnsupportedOperationException("Sequences are not supported because they create memory lists. Use lists, streams, flows, or simply forEach instead.") + + // endregion +} diff --git a/driver-coroutines/src/commonMain/kotlin/operations/FindOperations.kt b/driver-coroutines/src/commonMain/kotlin/operations/FindOperations.kt new file mode 100644 index 00000000..f6176f3a --- /dev/null +++ b/driver-coroutines/src/commonMain/kotlin/operations/FindOperations.kt @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2024, OpenSavvy and contributors. + * + * 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 opensavvy.ktmongo.coroutines.operations + +import opensavvy.ktmongo.coroutines.MongoIterable +import opensavvy.ktmongo.dsl.expr.FilterExpression + +/** + * Interface grouping MongoDB operations allowing to search for information. + */ +interface FindOperations { + + /** + * Finds all documents in this collection. + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/method/db.collection.find/) + */ + fun find(): MongoIterable + + /** + * Finds all documents in this collection that satisfy [predicate]. + * + * If multiple predicates are specified, an [and][opensavvy.ktmongo.dsl.expr.FilterExpression.and] operator is implied. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int, + * ) + * + * collection.find { + * User::name eq "foo" + * User::age eq 10 + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/method/db.collection.find/) + * + * @see findOne When only one result is expected. + */ + fun find(predicate: FilterExpression.() -> Unit): MongoIterable + + /** + * Finds a document in this collection that satisfies [predicate]. + * + * If multiple predicates are specified, and [and][FilterExpression.and] operator is implied. + * + * This function doesn't check that there is exactly one value in the collection. + * It simply returns the first matching document it finds. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int, + * ) + * + * collection.findOne { + * User::name eq "foo" + * User::age eq 10 + * } + * ``` + * + * @see find When multiple results are expected. + */ + suspend fun findOne(predicate: FilterExpression.() -> Unit): Document? = + find(predicate).firstOrNull() + +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5c3b4a35..c8968f13 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,7 @@ # List of dependencies of the project [versions] +coroutines = "1.9.0" # https://github.com/Kotlin/kotlinx.coroutines/releases prepared = "1.4.0" # https://gitlab.com/opensavvy/groundwork/prepared/-/releases mongodb-bson-jvm = "5.1.4" # https://central.sonatype.com/artifact/org.mongodb/bson mongodb-bson-js = "6.8.0" # https://www.npmjs.com/package/bson @@ -9,6 +10,8 @@ mongodb-driver = "5.2.0" # https://central.sonatype.com/artifact/org.mong [plugins] [libraries] +kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" } + prepared = { module = "dev.opensavvy.prepared:runner-kotest", version.ref = "prepared" } mongodb-bson-jvm = { module = "org.mongodb:bson", version.ref = "mongodb-bson-jvm" } -- 2.51.2 From 3ab87206f8e3a18824143f30f3c891cb41c90cdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Sat, 26 Oct 2024 15:08:24 +0200 Subject: [PATCH 5/7] feat(driver-coroutines): JVM implementation of MongoCollection and MongoIterable --- .../src/jvmMain/kotlin/JvmMongoCollection.kt | 72 +++++++++++++++++++ .../src/jvmMain/kotlin/JvmMongoIterable.kt | 64 +++++++++++++++++ 2 files changed, 136 insertions(+) create mode 100644 driver-coroutines/src/jvmMain/kotlin/JvmMongoCollection.kt create mode 100644 driver-coroutines/src/jvmMain/kotlin/JvmMongoIterable.kt diff --git a/driver-coroutines/src/jvmMain/kotlin/JvmMongoCollection.kt b/driver-coroutines/src/jvmMain/kotlin/JvmMongoCollection.kt new file mode 100644 index 00000000..92bdc540 --- /dev/null +++ b/driver-coroutines/src/jvmMain/kotlin/JvmMongoCollection.kt @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2024, OpenSavvy and contributors. + * + * 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 opensavvy.ktmongo.coroutines + +import opensavvy.ktmongo.bson.BsonContext +import opensavvy.ktmongo.bson.buildBsonDocument +import opensavvy.ktmongo.dsl.LowLevelApi +import opensavvy.ktmongo.dsl.expr.FilterExpression +import opensavvy.ktmongo.dsl.expr.common.AbstractCompoundExpression +import org.bson.BsonDocument + +/** + * Implementation of [MongoCollection] based on [MongoDB's MongoCollection][com.mongodb.kotlin.client.coroutine.MongoCollection]. + * + * To access the inner iterable, see [asKotlinClient]. + * + * To convert an existing MongoDB iterable into an instance of this class, see [asKtMongo]. + */ +class JvmMongoCollection internal constructor( + private val inner: com.mongodb.kotlin.client.coroutine.MongoCollection +) : MongoCollection { + + @LowLevelApi + fun asKotlinClient() = inner + + @LowLevelApi + override val context: BsonContext + get() = BsonContext(inner.codecRegistry) + + // region Find + + override fun find(): JvmMongoIterable = + JvmMongoIterable(inner.find()) + + @OptIn(LowLevelApi::class) + override fun find(predicate: FilterExpression.() -> Unit): JvmMongoIterable { + val filter = FilterExpression(context) + .apply(predicate) + .toBsonDocument() + + return JvmMongoIterable(inner.find(filter)) + } + + // endregion + +} + +@OptIn(LowLevelApi::class) +private fun AbstractCompoundExpression.toBsonDocument(): BsonDocument = + buildBsonDocument { + writeTo(this) + } + +/** + * Converts a [MongoDB collection][com.mongodb.kotlin.client.coroutine.MongoCollection] into a [KtMongo collection][JvmMongoCollection]. + */ +fun com.mongodb.kotlin.client.coroutine.MongoCollection.asKtMongo(): JvmMongoCollection = + JvmMongoCollection(this) diff --git a/driver-coroutines/src/jvmMain/kotlin/JvmMongoIterable.kt b/driver-coroutines/src/jvmMain/kotlin/JvmMongoIterable.kt new file mode 100644 index 00000000..6934c5bb --- /dev/null +++ b/driver-coroutines/src/jvmMain/kotlin/JvmMongoIterable.kt @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2024, OpenSavvy and contributors. + * + * 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 opensavvy.ktmongo.coroutines + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.toList +import opensavvy.ktmongo.dsl.LowLevelApi + +/** + * Implementation of [MongoIterable] based on [MongoDB's FindFlow][com.mongodb.kotlin.client.coroutine.FindFlow]. + * + * To access the inner iterable, see [asKotlinMongoIterable]. + * + * To convert an existing MongoDB iterable into an instance of this class, see [asKtMongo]. + */ +class JvmMongoIterable internal constructor( + private val inner: com.mongodb.kotlin.client.coroutine.FindFlow +) : MongoIterable { + + /** + * Converts a KtMongo [MongoIterable] into a [MongoDB FindFlow][com.mongodb.kotlin.client.coroutine.FindFlow]. + */ + @LowLevelApi + fun asKotlinMongoIterable() = inner + + override suspend fun first(): Document = + inner.first() + + override suspend fun firstOrNull(): Document? = + inner.firstOrNull() + + override suspend fun forEach(action: suspend (Document) -> Unit) { + inner.collect(action) + } + + override suspend fun toList(): List = + inner.toList() + + override fun asFlow(): Flow = + inner +} + +/** + * Converts a [MongoDB FindFlow][com.mongodb.kotlin.client.coroutine.FindFlow] into a + * [KtMongo MongoIterable][JvmMongoIterable]. + */ +fun com.mongodb.kotlin.client.coroutine.FindFlow.asKtMongo(): JvmMongoIterable = + JvmMongoIterable(this) -- 2.51.2 From f6707551992656e7295526c1071546938538ccc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Sat, 26 Oct 2024 15:28:56 +0200 Subject: [PATCH 6/7] docs(driver-sync): Add module header --- build.gradle.kts | 1 + driver-sync/README.jvm.md | 30 ++++++++ driver-sync/README.md | 73 +++++++++++++++++++ driver-sync/build.gradle.kts | 2 +- .../src/commonMain/kotlin/MongoCollection.kt | 4 +- 5 files changed, 107 insertions(+), 3 deletions(-) create mode 100644 driver-sync/README.jvm.md create mode 100644 driver-sync/README.md diff --git a/build.gradle.kts b/build.gradle.kts index 7bef3df6..09b21b18 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -24,6 +24,7 @@ dependencies { dokkatoo(projects.annotations) dokkatoo(projects.bson) dokkatoo(projects.dsl) + dokkatoo(projects.driverSync) } // region Check the users of the project didn't forget to rename the group diff --git a/driver-sync/README.jvm.md b/driver-sync/README.jvm.md new file mode 100644 index 00000000..148357bf --- /dev/null +++ b/driver-sync/README.jvm.md @@ -0,0 +1,30 @@ +# Module MongoDB driver for Kotlin (synchronous) + +Blocking/synchronous driver for MongoDB, using a rich Kotlin DSL. + + + + + +## Configuration + +Start by declaring a dependency on this module (see the common page for help). + +This driver is built on the top of the [official Kotlin driver](https://www.mongodb.com/docs/languages/kotlin/kotlin-sync-driver/current/). You can use the official tutorials to get started and learn how to connect to the database. + +The most basic option is to connect to your local MongoDB instance running on `localhost:27017`, use: +```kotlin +class User( + val _id: ObjectId, + val name: String, +) + +val client = MongoClient.create() +val database = client.getDatabase("my_project") +val collection = database.getCollection("users").asKtMongo() +``` + +Note the call to [`asKtMongo()`][opensavvy.ktmongo.sync.asKtMongo] which is the only difference from the official usage. +From then on, all methods from this driver are available on the `collection` variable: see [MongoCollection][opensavvy.ktmongo.sync.MongoCollection]. + +This means you are able to use KtMongo DSLs within your existing repositories: simply convert into a KtMongo equivalent where you need KtMongo functionality. diff --git a/driver-sync/README.md b/driver-sync/README.md new file mode 100644 index 00000000..da32a0a6 --- /dev/null +++ b/driver-sync/README.md @@ -0,0 +1,73 @@ +# Module MongoDB driver for Kotlin (synchronous) + +Blocking/synchronous driver for MongoDB, using a rich Kotlin DSL. + + + + + +## Configuration + +Add a dependency on `dev.opensavvy.ktmongo:driver-sync`. + +For example, in a Kotlin Multiplatform project: +```kotlin +plugins { + kotlin("multiplatform") +} + +kotlin { + jvm() + // … + + sourceSets.commonMain.dependencies { + implementation("dev.opensavvy.ktmongo:driver-sync:VERSION-HERE") + } +} +``` + +In a Kotlin JVM project: +```kotlin +plugins { + kotlin("jvm") +} + +dependencies { + implementation("dev.opensavvy.ktmongo:driver-sync:VERSION-HERE") +} +``` + +## Basic usage + +Once you have obtained an instance of [MongoCollection][opensavvy.ktmongo.sync.MongoCollection] (see platform-specific instructions on how to do this), you can use it to access the database: + +```kotlin +class User( + val _id: ObjectId, + val name: String, + val age: Int, +) + +collection.findOne { + User::age gte 18 +} + +collection.update( + filter = { + User::_id eq ObjectId("507f1f77bcf86cd799439011") + }, + update = { + User::name set "Bob" + } +) +``` + +[Learn more about the available operations][opensavvy.ktmongo.sync.MongoCollection]. + +# Package opensavvy.ktmongo.sync + +Declaration and implementation of collections and iterables. + +# Package opensavvy.ktmongo.sync.operations + +Declaration of the different types of operations. diff --git a/driver-sync/build.gradle.kts b/driver-sync/build.gradle.kts index f0e1ebc8..1220e58f 100644 --- a/driver-sync/build.gradle.kts +++ b/driver-sync/build.gradle.kts @@ -40,7 +40,7 @@ kotlin { } library { - name.set("MongoDB synchronous driver for Kotlin") + name.set("MongoDB driver for Kotlin (synchronous)") description.set("Kotlin-first MongoDB driver") homeUrl.set("https://gitlab.com/opensavvy/ktmongo") diff --git a/driver-sync/src/commonMain/kotlin/MongoCollection.kt b/driver-sync/src/commonMain/kotlin/MongoCollection.kt index 1ae8801b..b77b90ce 100644 --- a/driver-sync/src/commonMain/kotlin/MongoCollection.kt +++ b/driver-sync/src/commonMain/kotlin/MongoCollection.kt @@ -25,8 +25,8 @@ import opensavvy.ktmongo.sync.operations.FindOperations * * ### Operations * - * - [find] - * - [findOne] + * - [find][FindOperations.find] + * - [findOne][FindOperations.findOne] * * ### External resources * -- 2.51.2 From b2ab416130f5595bef71af4b96a3fc1f3ddc7ddb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Sat, 26 Oct 2024 15:46:24 +0200 Subject: [PATCH 7/7] docs(driver-coroutines): Add module header --- build.gradle.kts | 1 + driver-coroutines/README.jvm.md | 30 ++++++++ driver-coroutines/README.md | 73 +++++++++++++++++++ driver-coroutines/build.gradle.kts | 2 +- .../src/commonMain/kotlin/MongoCollection.kt | 4 +- 5 files changed, 107 insertions(+), 3 deletions(-) create mode 100644 driver-coroutines/README.jvm.md create mode 100644 driver-coroutines/README.md diff --git a/build.gradle.kts b/build.gradle.kts index 09b21b18..e9510080 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -25,6 +25,7 @@ dependencies { dokkatoo(projects.bson) dokkatoo(projects.dsl) dokkatoo(projects.driverSync) + dokkatoo(projects.driverCoroutines) } // region Check the users of the project didn't forget to rename the group diff --git a/driver-coroutines/README.jvm.md b/driver-coroutines/README.jvm.md new file mode 100644 index 00000000..7a6d5252 --- /dev/null +++ b/driver-coroutines/README.jvm.md @@ -0,0 +1,30 @@ +# Module MongoDB driver for Kotlin (coroutines) + +Asynchronous coroutines-based driver for MongoDB, using a rich Kotlin DSL. + + + + + +## Configuration + +Start by declaring a dependency on this module (see the common page for help). + +This driver is built on the top of the [official Kotlin driver](https://www.mongodb.com/docs/drivers/kotlin/coroutine/current/). You can use the official tutorials to get started and learn how to connect to the database. + +The most basic option is to connect to your local MongoDB instance running on `localhost:27017`, use: +```kotlin +class User( + val _id: ObjectId, + val name: String, +) + +val client = MongoClient.create() +val database = client.getDatabase("my_project") +val collection = database.getCollection("users").asKtMongo() +``` + +Note the call to [`asKtMongo()`][opensavvy.ktmongo.coroutines.asKtMongo] which is the only difference from the official usage. +From then on, all methods from this driver are available on the `collection` variable: see [MongoCollection][opensavvy.ktmongo.coroutines.MongoCollection]. + +This means you are able to use KtMongo DSLs within your existing repositories: simply convert into a KtMongo equivalent where you need KtMongo functionality. diff --git a/driver-coroutines/README.md b/driver-coroutines/README.md new file mode 100644 index 00000000..71dcfd4d --- /dev/null +++ b/driver-coroutines/README.md @@ -0,0 +1,73 @@ +# Module MongoDB driver for Kotlin (coroutines) + +Asynchronous coroutines-based driver for MongoDB, using a rich Kotlin DSL. + + + + + +## Configuration + +Add a dependency on `dev.opensavvy.ktmongo:driver-coroutines`. + +For example, in a Kotlin Multiplatform project: +```kotlin +plugins { + kotlin("multiplatform") +} + +kotlin { + jvm() + // … + + sourceSets.commonMain.dependencies { + implementation("dev.opensavvy.ktmongo:driver-coroutines:VERSION-HERE") + } +} +``` + +In a Kotlin JVM project: +```kotlin +plugins { + kotlin("jvm") +} + +dependencies { + implementation("dev.opensavvy.ktmongo:driver-coroutines:VERSION-HERE") +} +``` + +## Basic usage + +Once you have obtained an instance of [MongoCollection][opensavvy.ktmongo.coroutines.MongoCollection] (see platform-specific instructions on how to do this), you can use it to access the database: + +```kotlin +class User( + val _id: ObjectId, + val name: String, + val age: Int, +) + +collection.findOne { + User::age gte 18 +} + +collection.update( + filter = { + User::_id eq ObjectId("507f1f77bcf86cd799439011") + }, + update = { + User::name set "Bob" + } +) +``` + +[Learn more about the available operations][opensavvy.ktmongo.coroutines.MongoCollection]. + +# Package opensavvy.ktmongo.coroutines + +Declaration and implementation of collections and iterables. + +# Package opensavvy.ktmongo.coroutines.operations + +Declaration of the different types of operations. diff --git a/driver-coroutines/build.gradle.kts b/driver-coroutines/build.gradle.kts index e4229de8..50f3d9bf 100644 --- a/driver-coroutines/build.gradle.kts +++ b/driver-coroutines/build.gradle.kts @@ -41,7 +41,7 @@ kotlin { } library { - name.set("MongoDB coroutines driver for Kotlin") + name.set("MongoDB driver for Kotlin (coroutines)") description.set("Kotlin-first MongoDB driver") homeUrl.set("https://gitlab.com/opensavvy/ktmongo") diff --git a/driver-coroutines/src/commonMain/kotlin/MongoCollection.kt b/driver-coroutines/src/commonMain/kotlin/MongoCollection.kt index 73a80332..06533363 100644 --- a/driver-coroutines/src/commonMain/kotlin/MongoCollection.kt +++ b/driver-coroutines/src/commonMain/kotlin/MongoCollection.kt @@ -25,8 +25,8 @@ import opensavvy.ktmongo.dsl.LowLevelApi * * ### Operations * - * - [find] - * - [findOne] + * - [find][FindOperations.find] + * - [findOne][FindOperations.findOne] * * ### External resources * -- 2.51.2