diff --git a/driver-api/src/commonMain/kotlin/MongoCollection.kt b/driver-api/src/commonMain/kotlin/MongoCollection.kt --- a/driver-api/src/commonMain/kotlin/MongoCollection.kt +++ b/driver-api/src/commonMain/kotlin/MongoCollection.kt @@ -17,6 +17,7 @@ package opensavvy.ktmongo.api import opensavvy.ktmongo.api.operations.CountOperations +import opensavvy.ktmongo.api.operations.InsertOperations import opensavvy.ktmongo.bson.BsonFactory import opensavvy.ktmongo.bson.types.ObjectId import opensavvy.ktmongo.bson.types.ObjectIdGenerator @@ -52,7 +53,8 @@ * - [Size limits](https://www.mongodb.com/docs/manual/reference/limits/#bson-documents) */ interface MongoCollection : ObjectIdGenerator, - CountOperations { + CountOperations, + InsertOperations { /** * THe name of this collection. diff --git a/driver-coroutines/src/jvmMain/kotlin/CoroutineMongoCollectionImpl.kt b/driver-coroutines/src/jvmMain/kotlin/CoroutineMongoCollectionImpl.kt --- a/driver-coroutines/src/jvmMain/kotlin/CoroutineMongoCollectionImpl.kt +++ b/driver-coroutines/src/jvmMain/kotlin/CoroutineMongoCollectionImpl.kt @@ -25,11 +25,14 @@ import opensavvy.ktmongo.bson.types.ObjectIdGenerator import opensavvy.ktmongo.dsl.BsonContext import opensavvy.ktmongo.dsl.LowLevelApi -import opensavvy.ktmongo.dsl.command.Count -import opensavvy.ktmongo.dsl.command.CountOptions +import opensavvy.ktmongo.dsl.command.* +import opensavvy.ktmongo.dsl.options.WithWriteConcern +import opensavvy.ktmongo.dsl.options.WriteConcernOption +import opensavvy.ktmongo.dsl.options.option import opensavvy.ktmongo.dsl.path.PropertyNameStrategy import opensavvy.ktmongo.dsl.query.FilterQuery import opensavvy.ktmongo.official.options.toJava +import opensavvy.ktmongo.official.toJava import kotlin.reflect.KType import kotlin.reflect.typeOf @@ -85,6 +88,33 @@ override suspend fun countEstimated(): Long = inner.estimatedDocumentCount() + + // endregion + // region Insert + + @OptIn(LowLevelApi::class) + override suspend fun insertOne(document: Document, options: InsertOneOptions.() -> Unit) { + val model = InsertOne(context, document, type) + + model.options.options() + + inner.withWriteConcern(model.options).insertOne( + model.document, + com.mongodb.client.model.InsertOneOptions() + ) + } + + @OptIn(LowLevelApi::class) + override suspend fun insertMany(documents: Iterable, options: InsertManyOptions.() -> Unit) { + val model = InsertMany(context, documents.toList(), type) + + model.options.options() + + inner.withWriteConcern(model.options).insertMany( + model.documents, + com.mongodb.client.model.InsertManyOptions() + ) + } // endregion @@ -153,3 +183,11 @@ objectIdGenerator = objectIdGenerator, type = typeOf(), ) + +@LowLevelApi +private fun MongoCollection.withWriteConcern(option: WithWriteConcern): MongoCollection { + val concern = option.option()?.concern + ?: return this + + return this.withWriteConcern(concern.toJava()) +} diff --git a/test/src/commonMain/kotlin/Entrypoint.kt b/test/src/commonMain/kotlin/Entrypoint.kt --- a/test/src/commonMain/kotlin/Entrypoint.kt +++ b/test/src/commonMain/kotlin/Entrypoint.kt @@ -18,6 +18,7 @@ import opensavvy.ktmongo.api.MongoClient import opensavvy.ktmongo.tests.api.operations.verifyCountOperations +import opensavvy.ktmongo.tests.api.operations.verifyInsertOperations import opensavvy.prepared.suite.* import kotlin.coroutines.CoroutineContext @@ -48,5 +49,6 @@ verifyDatabase(client) verifyCollection(client) + verifyInsertOperations(client) verifyCountOperations(client) } diff --git a/driver-api/src/commonMain/kotlin/operations/InsertOperations.kt b/driver-api/src/commonMain/kotlin/operations/InsertOperations.kt new file mode 100644 --- /dev/null +++ b/driver-api/src/commonMain/kotlin/operations/InsertOperations.kt @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2026, 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.api.operations + +import opensavvy.ktmongo.dsl.command.InsertManyOptions +import opensavvy.ktmongo.dsl.command.InsertOneOptions + +/** + * The different MongoDB operations related to inserting documents. + */ +interface InsertOperations : BaseOperations { + + /** + * Inserts a [document]. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int, + * ) + * + * collection.insertOne(User(name = "Bob", age = 18)) + * ``` + * + * ### External resources + * + * - [Protocol documentation](https://www.mongodb.com/docs/manual/reference/command/insert/) + * - [`mongosh` documentation](https://www.mongodb.com/docs/manual/reference/method/db.collection.insertOne/) + * + * @see insertMany Insert multiple documents. + */ + suspend fun insertOne( + document: Document, + options: InsertOneOptions.() -> Unit = {}, + ) + + /** + * Inserts multiple [documents] in a single operation. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int, + * ) + * + * collection.insertMany(users) + * ``` + * + * ### External resources + * + * - [Protocol documentation](https://www.mongodb.com/docs/manual/reference/command/insert/) + * - [`mongosh` documentation](https://www.mongodb.com/docs/manual/reference/method/db.collection.insertMany/) + * + * @see insertOne Insert a single document. + */ + suspend fun insertMany( + documents: Iterable, + options: InsertManyOptions.() -> Unit = {}, + ) + + /** + * Inserts multiple [documents] in a single operation. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int, + * ) + * + * collection.insertMany( + * User(name = "Bob", age = 18), + * User(name = "Alice", age = 17) + * ) + * ``` + * + * ### External resources + * + * - [Protocol documentation](https://www.mongodb.com/docs/manual/reference/command/insert/) + * - [`mongosh` documentation](https://www.mongodb.com/docs/manual/reference/method/db.collection.insertMany/) + * + * @see insertOne Insert a single document. + */ + suspend fun insertMany( + vararg documents: Document, + options: InsertManyOptions.() -> Unit = {}, + ) { + insertMany(documents.asList(), options) + } + +} diff --git a/test/src/commonMain/kotlin/operations/InsertOperations.test.kt b/test/src/commonMain/kotlin/operations/InsertOperations.test.kt new file mode 100644 --- /dev/null +++ b/test/src/commonMain/kotlin/operations/InsertOperations.test.kt @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2026, 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.tests.api.operations + +import kotlinx.serialization.Serializable +import opensavvy.ktmongo.api.MongoClient +import opensavvy.ktmongo.bson.types.ObjectId +import opensavvy.ktmongo.dsl.options.WriteConcern +import opensavvy.ktmongo.tests.api.collection +import opensavvy.prepared.suite.Prepared +import opensavvy.prepared.suite.SuiteDsl + +@Serializable +data class InsertOperationsUser( + val _id: ObjectId? = null, + val name: String, +) + +fun SuiteDsl.verifyInsertOperations( + client: Prepared, +) = suite("Insert operations") { + val collection by client.collection("operation-insert-users") + + suite("Simple") { + test("Insert a document") { + collection().insertOne( + InsertOperationsUser( + _id = collection().newId(), + name = "Bob", + ) + ) + } + + test("Insert a document without ID") { + collection().insertOne( + InsertOperationsUser( + name = "Bob", + ) + ) + } + + test("Insert multiple documents at once") { + collection().insertMany( + InsertOperationsUser( + _id = collection().newId(), + name = "Alice", + ), + InsertOperationsUser( + _id = collection().newId(), + name = "Bob", + ) + ) + } + } + + suite("Options") { + test("insertOne • Write concern") { + collection().insertOne( + InsertOperationsUser( + _id = collection().newId(), + name = "Bob", + ), + options = { + writeConcern(WriteConcern.FireAndForget) + } + ) + + // TODO: write a test that can check that the option is correctly applied + } + + test("insertMany • Write concern") { + collection().insertMany( + InsertOperationsUser( + _id = collection().newId(), + name = "Bob", + ), + options = { + writeConcern(WriteConcern.FireAndForget) + } + ) + + // TODO: write a test that can check that the option is correctly applied + } + } +}