From fbbb22731279cd77c458de38715d637cc05b45f4 Mon Sep 17 00:00:00 2001 From: Maxime Girardet Date: Mon, 26 May 2025 21:19:05 +0000 Subject: [PATCH] merge: Implement the $mul update operator See merge request opensavvy/ktmongo!74 --- dsl/src/commonMain/kotlin/query/UpdateQuery.kt | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ dsl/src/commonMain/kotlin/query/UpdateQueryImpl.kt | 30 ++++++++++++++++++++++++++++++ dsl/src/commonTest/kotlin/query/update/FieldUpdateTest.kt | 40 ++++++++++++++++++++++++++++++++++++++++ dsl/src/commonTest/kotlin/query/update/UpdateUtils.kt | 1 + 4 file(s) changed, 134 insertion(s)(+), 0 deletion(s)(-) diff --git a/dsl/src/commonMain/kotlin/query/UpdateQuery.kt b/dsl/src/commonMain/kotlin/query/UpdateQuery.kt --- a/dsl/src/commonMain/kotlin/query/UpdateQuery.kt +++ b/dsl/src/commonMain/kotlin/query/UpdateQuery.kt @@ -394,6 +394,69 @@ 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 --- a/dsl/src/commonMain/kotlin/query/UpdateQueryImpl.kt +++ b/dsl/src/commonMain/kotlin/query/UpdateQueryImpl.kt @@ -167,6 +167,33 @@ } // 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 @OptIn(LowLevelApi::class, DangerousMongoApi::class) @@ -233,6 +260,9 @@ }, 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 --- a/dsl/src/commonTest/kotlin/query/update/FieldUpdateTest.kt +++ b/dsl/src/commonTest/kotlin/query/update/FieldUpdateTest.kt @@ -140,6 +140,46 @@ } } + 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 --- a/dsl/src/commonTest/kotlin/query/update/UpdateUtils.kt +++ b/dsl/src/commonTest/kotlin/query/update/UpdateUtils.kt @@ -28,6 +28,7 @@ val set = "\$set" val setOnInsert = "\$setOnInsert" val inc = "\$inc" +val mul = "\$mul" val unset = "\$unset" val rename = "\$rename" -- tangled.sh