diff --git a/dsl/src/commonMain/kotlin/query/UpdateQuery.kt b/dsl/src/commonMain/kotlin/query/UpdateQuery.kt index 9bbf3eda..7a1d3e7c 100644 --- a/dsl/src/commonMain/kotlin/query/UpdateQuery.kt +++ b/dsl/src/commonMain/kotlin/query/UpdateQuery.kt @@ -393,6 +393,69 @@ interface UpdateQuery : CompoundBsonNode, FieldDsl { operator fun <@kotlin.internal.OnlyInputTypes V : Number> KProperty1.plusAssign(amount: V): Unit = this.field.plusAssign(amount) + // endregion + // region $mul + + /** + * Multiplies a field by the specified [amount]. + * + * If the field doesn't exist (either the document doesn't have it, or the operation is an upsert and a new document is created), + * the field is created with an initial value of 0. + * + * Use of this operator with a field with a `null` value will generate an error. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val price: Double, + * ) + * + * collection.updateMany { + * User::price mul 2.0 + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/mul/) + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + infix fun <@kotlin.internal.OnlyInputTypes V : Number> Field.mul(amount: V) + + /** + * Multiplies a field by the specified [amount]. + * + * If the field doesn't exist (either the document doesn't have it, or the operation is an upsert and a new document is created), + * the field is created with an initial value of 0. + * + * Use of this operator with a field with a `null` value will generate an error. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val price: Double, + * ) + * + * collection.updateMany { + * User::price mul 2.0 + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/mul/) + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + infix fun <@kotlin.internal.OnlyInputTypes V : Number> KProperty1.mul(amount: V) { + this.field.mul(amount) + } + // endregion // region $unset diff --git a/dsl/src/commonMain/kotlin/query/UpdateQueryImpl.kt b/dsl/src/commonMain/kotlin/query/UpdateQueryImpl.kt index 7c279b1c..8b9ddb8c 100644 --- a/dsl/src/commonMain/kotlin/query/UpdateQueryImpl.kt +++ b/dsl/src/commonMain/kotlin/query/UpdateQueryImpl.kt @@ -166,6 +166,33 @@ private class UpdateQueryImpl( } } + // endregion + // region $mul + + @OptIn(LowLevelApi::class, DangerousMongoApi::class) + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + override infix fun <@kotlin.internal.OnlyInputTypes V : Number> Field.mul(amount: V) { + accept(MultiplyBsonNodeNode(listOf(this.path to amount), context)) + } + + @LowLevelApi + private class MultiplyBsonNodeNode( + val mappings: List>, + context: BsonContext, + ) : UpdateBsonNodeNode(context) { + override fun simplify(): AbstractBsonNode? = + this.takeUnless { mappings.isEmpty() } + + override fun write(writer: BsonFieldWriter) = with(writer) { + writeDocument("\$mul") { + for ((field, value) in mappings) { + writeObjectSafe(field.toString(), value) + } + } + } + } + // endregion // region $unset @@ -234,6 +261,9 @@ private class UpdateQueryImpl( OperatorCombinator(IncrementBsonNodeNode::class) { sources, context -> IncrementBsonNodeNode(sources.flatMap { it.mappings }, context) }, + OperatorCombinator(MultiplyBsonNodeNode::class) { sources, context -> + MultiplyBsonNodeNode(sources.flatMap { it.mappings }, context) + }, OperatorCombinator(UnsetBsonNodeNode::class) { sources, context -> UnsetBsonNodeNode(sources.flatMap { it.fields }, context) }, diff --git a/dsl/src/commonTest/kotlin/query/update/FieldUpdateTest.kt b/dsl/src/commonTest/kotlin/query/update/FieldUpdateTest.kt index 74213fb3..75a75224 100644 --- a/dsl/src/commonTest/kotlin/query/update/FieldUpdateTest.kt +++ b/dsl/src/commonTest/kotlin/query/update/FieldUpdateTest.kt @@ -140,6 +140,46 @@ class FieldUpdateTest : PreparedSpec({ } } + suite("Operator $mul") { + test("Single field") { + update { + User::money mul 18.0 + } shouldBeBson """ + { + "$mul": { + "money": 18.0 + } + } + """.trimIndent() + } + + test("Nested field") { + update { + User::bestFriend / Friend::money mul -12.9f + } shouldBeBson """ + { + "$mul": { + "bestFriend.money": -12.899999618530273 + } + } + """.trimIndent() + } + + test("Multiple fields") { + update { + User::money mul 5.2 + User::bestFriend / Friend::money mul -5.2f + } shouldBeBson """ + { + "$mul": { + "money": 5.2, + "bestFriend.money": -5.199999809265137 + } + } + """.trimIndent() + } + } + suite("Operator $unset") { test("Single field") { update { diff --git a/dsl/src/commonTest/kotlin/query/update/UpdateUtils.kt b/dsl/src/commonTest/kotlin/query/update/UpdateUtils.kt index 86f8c8ce..f6e266dd 100644 --- a/dsl/src/commonTest/kotlin/query/update/UpdateUtils.kt +++ b/dsl/src/commonTest/kotlin/query/update/UpdateUtils.kt @@ -28,6 +28,7 @@ import opensavvy.prepared.runner.kotest.PreparedSpec val set = "\$set" val setOnInsert = "\$setOnInsert" val inc = "\$inc" +val mul = "\$mul" val unset = "\$unset" val rename = "\$rename"