diff --git a/test/src/commonTest/kotlin/AggregationTests.kt b/test/src/commonTest/kotlin/AggregationTests.kt --- a/test/src/commonTest/kotlin/AggregationTests.kt +++ b/test/src/commonTest/kotlin/AggregationTests.kt @@ -78,4 +78,22 @@ check(Song(creationDate = 12, editionDate = 1) in anomalies.find().toList()) } + + test("Update with a condition") { + songs().insertOne(Song(creationDate = 0, editionDate = 1)) + songs().insertOne(Song(creationDate = 1, editionDate = 1)) + songs().insertOne(Song(creationDate = 2, editionDate = 3)) + + songs().updateManyWithPipeline { + set { + Song::creationDate set cond( + of(Song::editionDate) gt of(Song::creationDate), + of(12), + of(11) + ) + } + } + + check(Song(creationDate = 12, editionDate = 3) in songs().find().toList()) + } }) diff --git a/dsl/src/commonMain/kotlin/aggregation/ValueDsl.kt b/dsl/src/commonMain/kotlin/aggregation/ValueDsl.kt --- a/dsl/src/commonMain/kotlin/aggregation/ValueDsl.kt +++ b/dsl/src/commonMain/kotlin/aggregation/ValueDsl.kt @@ -21,6 +21,7 @@ import opensavvy.ktmongo.dsl.KtMongoDsl import opensavvy.ktmongo.dsl.LowLevelApi import opensavvy.ktmongo.dsl.aggregation.operators.ComparisonValueOperators +import opensavvy.ktmongo.dsl.aggregation.operators.ConditionalOperators import opensavvy.ktmongo.dsl.aggregation.operators.ValueOperators import opensavvy.ktmongo.dsl.expr.FilterOperators import opensavvy.ktmongo.dsl.path.Field @@ -94,7 +95,8 @@ */ @KtMongoDsl interface ValueDsl : ValueOperators, - ComparisonValueOperators { + ComparisonValueOperators, + ConditionalOperators { /** * Refers to a [field] within an [aggregation value][ValueDsl]. diff --git a/dsl/src/commonMain/kotlin/expr/UpdateOperators.kt b/dsl/src/commonMain/kotlin/expr/UpdateOperators.kt --- a/dsl/src/commonMain/kotlin/expr/UpdateOperators.kt +++ b/dsl/src/commonMain/kotlin/expr/UpdateOperators.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, OpenSavvy and contributors. + * Copyright (c) 2024-2025, 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. @@ -48,7 +48,7 @@ * ### Operators * * On regular fields: - * - [`$inc`][inc] + * - [`$inc`][plusAssign] * - [`$rename`][renameTo] * - [`$set`][set] * - [`$setOnInsert`][UpsertOperators.setOnInsert] (only for [upserts][UpsertOperators]) @@ -130,6 +130,134 @@ this.field.set(value) } + /** + * Replaces the value of a field with the specified [value], if [condition] is `true`. + * + * If [condition] is `false`, this operator does nothing. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String?, + * val age: Int, + * ) + * + * collection.filter { + * User::name eq "foo" + * }.updateMany { + * User::age.setIf(someComplexOperation, 18) + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/set/) + * + * @see UpsertOperators.setOnInsert Only set if a new document is created. + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + fun <@kotlin.internal.OnlyInputTypes V> Field.setIf(condition: Boolean, value: V) { + if (condition) + this set value + } + + /** + * Replaces the value of a field with the specified [value], if [condition] is `true`. + * + * If [condition] is `false`, this operator does nothing. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String?, + * val age: Int, + * ) + * + * collection.filter { + * User::name eq "foo" + * }.updateMany { + * User::age.setIf(someComplexOperation, 18) + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/set/) + * + * @see UpsertOperators.setOnInsert Only set if a new document is created. + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + fun <@kotlin.internal.OnlyInputTypes V> KProperty1.setIf(condition: Boolean, value: V) = + this.field.setIf(condition, value) + + /** + * Replaces the value of a field with the specified [value], if [condition] is `false`. + * + * If [condition] is `true`, this operator does nothing. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String?, + * val age: Int, + * ) + * + * collection.filter { + * User::name eq "foo" + * }.updateMany { + * User::age.setUnless(someComplexOperation, 18) + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/set/) + * + * @see UpsertOperators.setOnInsert Only set if a new document is created. + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + fun <@kotlin.internal.OnlyInputTypes V> Field.setUnless(condition: Boolean, value: V) { + if (!condition) + this set value + } + + /** + * Replaces the value of a field with the specified [value], if [condition] is `false`. + * + * If [condition] is `true`, this operator does nothing. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String?, + * val age: Int, + * ) + * + * collection.filter { + * User::name eq "foo" + * }.updateMany { + * User::age.setUnless(someComplexOperation, 18) + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/set/) + * + * @see UpsertOperators.setOnInsert Only set if a new document is created. + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + fun <@kotlin.internal.OnlyInputTypes V> KProperty1.setUnless(condition: Boolean, value: V) = + this.field.setUnless(condition, value) + // endregion // region $inc @@ -198,6 +326,72 @@ infix fun <@kotlin.internal.OnlyInputTypes V : Number> KProperty1.inc(amount: V) { this.field.inc(amount) } + + /** + * Increments a field by the specified [amount]. + * + * [amount] may be negative, in which case the field is decremented. + * + * 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 [amount]. + * + * Use of this operator with a field with a `null` value will generate an error. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int, + * ) + * + * // It's the new year! + * collection.updateMany { + * User::age += 1 + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/inc/) + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + operator fun <@kotlin.internal.OnlyInputTypes V : Number> Field.plusAssign(amount: V): Unit = + this.inc(amount) + + /** + * Increments a field by the specified [amount]. + * + * [amount] may be negative, in which case the field is decremented. + * + * 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 [amount]. + * + * Use of this operator with a field with a `null` value will generate an error. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int, + * ) + * + * // It's the new year! + * collection.updateMany { + * User::age += 1 + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/inc/) + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + operator fun <@kotlin.internal.OnlyInputTypes V : Number> KProperty1.plusAssign(amount: V): Unit = + this.field.plusAssign(amount) // endregion // region $unset diff --git a/dsl/src/commonTest/kotlin/aggregation/ValueTest.kt b/dsl/src/commonTest/kotlin/aggregation/ValueTest.kt --- a/dsl/src/commonTest/kotlin/aggregation/ValueTest.kt +++ b/dsl/src/commonTest/kotlin/aggregation/ValueTest.kt @@ -23,6 +23,7 @@ import opensavvy.prepared.runner.kotest.PreparedSpec const val literal = "\$literal" +const val cond = "\$cond" @LowLevelApi class ValueTest : PreparedSpec({ diff --git a/dsl/src/commonMain/kotlin/aggregation/operators/ConditionalOperators.kt b/dsl/src/commonMain/kotlin/aggregation/operators/ConditionalOperators.kt new file mode 100644 --- /dev/null +++ b/dsl/src/commonMain/kotlin/aggregation/operators/ConditionalOperators.kt @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2025, 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.dsl.aggregation.operators + +import opensavvy.ktmongo.bson.BsonContext +import opensavvy.ktmongo.bson.BsonValueWriter +import opensavvy.ktmongo.dsl.KtMongoDsl +import opensavvy.ktmongo.dsl.LowLevelApi +import opensavvy.ktmongo.dsl.aggregation.AbstractValue +import opensavvy.ktmongo.dsl.aggregation.Value +import opensavvy.ktmongo.dsl.aggregation.ValueDsl + +/** + * Operators to conditionally create a value. + * + * To learn more about aggregation operators, view [ValueDsl]. + */ +@KtMongoDsl +interface ConditionalOperators : ValueOperators { + + /** + * Decides between two [values][Value] depending on the evaluation of a boolean value. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val score: Int, + * val multiplier: Int, + * ) + * + * users.updateManyWithPipeline { + * set { + * User::score set cond( + * condition = of(User::multiplier) gt of(2), + * ifTrue = of(User::score) * of(User::multiplier), + * ifFalse = of(User::score) + * ) + * } + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/aggregation/cond/) + */ + @OptIn(LowLevelApi::class) + @KtMongoDsl + fun cond( + condition: Value, + ifTrue: Value, + ifFalse: Value, + ): Value = + ConditionalValue(context, condition, ifTrue, ifFalse) + + @OptIn(LowLevelApi::class) + private class ConditionalValue( + context: BsonContext, + private val condition: Value, + private val ifTrue: Value, + private val ifFalse: Value, + ) : Value, AbstractValue(context) { + + @LowLevelApi + override fun write(writer: BsonValueWriter) = with(writer) { + writeDocument { + writeDocument("\$cond") { + write("if") { + condition.writeTo(this) + } + + write("then") { + ifTrue.writeTo(this) + } + + write("else") { + ifFalse.writeTo(this) + } + } + } + } + } +} diff --git a/dsl/src/commonMain/kotlin/aggregation/stages/Set.kt b/dsl/src/commonMain/kotlin/aggregation/stages/Set.kt --- a/dsl/src/commonMain/kotlin/aggregation/stages/Set.kt +++ b/dsl/src/commonMain/kotlin/aggregation/stages/Set.kt @@ -76,6 +76,8 @@ @KtMongoDsl interface SetOperators : CompoundExpression, ValueDsl, FieldDsl { + // region $set + /** * Replaces the value of a field with the specified [value]. * @@ -126,6 +128,259 @@ this.field.set(value) } + // endregion + // region Conditional $set + // region setIf + + /** + * Replaces the value of a field with the specified [value], if [condition] is `true`. + * + * If [condition] is `false`, this operator does nothing. + * + * ### External resources + * + * - [`$set`](https://www.mongodb.com/docs/manual/reference/operator/update/set/) + * - [`$cond`](https://www.mongodb.com/docs/manual/reference/operator/aggregation/cond/) + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + fun <@kotlin.internal.OnlyInputTypes V> Field.setIf(condition: Value, value: Value) = + this set cond(condition, value, of(this)) + + /** + * Replaces the value of a field with the specified [value], if [condition] is `true`. + * + * If [condition] is `false`, this operator does nothing. + * + * ### External resources + * + * - [`$set`](https://www.mongodb.com/docs/manual/reference/operator/update/set/) + * - [`$cond`](https://www.mongodb.com/docs/manual/reference/operator/aggregation/cond/) + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + fun <@kotlin.internal.OnlyInputTypes V> KProperty1.setIf(condition: Value, value: Value) = + this.field.setIf(condition, value) + + /** + * Replaces the value of a field with the specified [value], if [condition] is `true`. + * + * If [condition] is `false`, this operator does nothing. + * + * ### External resources + * + * - [`$set`](https://www.mongodb.com/docs/manual/reference/operator/update/set/) + * - [`$cond`](https://www.mongodb.com/docs/manual/reference/operator/aggregation/cond/) + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + fun <@kotlin.internal.OnlyInputTypes V> Field.setIf(condition: Value, value: V) = + this.setIf(condition, of(value)) + + /** + * Replaces the value of a field with the specified [value], if [condition] is `true`. + * + * If [condition] is `false`, this operator does nothing. + * + * ### External resources + * + * - [`$set`](https://www.mongodb.com/docs/manual/reference/operator/update/set/) + * - [`$cond`](https://www.mongodb.com/docs/manual/reference/operator/aggregation/cond/) + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + fun <@kotlin.internal.OnlyInputTypes V> KProperty1.setIf(condition: Value, value: V) = + this.field.setIf(condition, value) + + /** + * Replaces the value of a field with the specified [value], if [condition] is `true`. + * + * If [condition] is `false`, this operator does nothing. + * + * ### External resources + * + * - [`$set`](https://www.mongodb.com/docs/manual/reference/operator/update/set/) + * - [`$cond`](https://www.mongodb.com/docs/manual/reference/operator/aggregation/cond/) + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + fun <@kotlin.internal.OnlyInputTypes V> Field.setIf(condition: Boolean, value: Value) { + if (condition) + this.set(value) + } + + /** + * Replaces the value of a field with the specified [value], if [condition] is `true`. + * + * If [condition] is `false`, this operator does nothing. + * + * ### External resources + * + * - [`$set`](https://www.mongodb.com/docs/manual/reference/operator/update/set/) + * - [`$cond`](https://www.mongodb.com/docs/manual/reference/operator/aggregation/cond/) + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + fun <@kotlin.internal.OnlyInputTypes V> KProperty1.setIf(condition: Boolean, value: Value) = + this.field.setIf(condition, value) + + /** + * Replaces the value of a field with the specified [value], if [condition] is `true`. + * + * If [condition] is `false`, this operator does nothing. + * + * ### External resources + * + * - [`$set`](https://www.mongodb.com/docs/manual/reference/operator/update/set/) + * - [`$cond`](https://www.mongodb.com/docs/manual/reference/operator/aggregation/cond/) + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + fun <@kotlin.internal.OnlyInputTypes V> Field.setIf(condition: Boolean, value: V) = + this.setIf(condition, of(value)) + + /** + * Replaces the value of a field with the specified [value], if [condition] is `true`. + * + * If [condition] is `false`, this operator does nothing. + * + * ### External resources + * + * - [`$set`](https://www.mongodb.com/docs/manual/reference/operator/update/set/) + * - [`$cond`](https://www.mongodb.com/docs/manual/reference/operator/aggregation/cond/) + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + fun <@kotlin.internal.OnlyInputTypes V> KProperty1.setIf(condition: Boolean, value: V) = + this.field.setIf(condition, value) + + // endregion + // region setUnless + + /** + * Replaces the value of a field with the specified [value], if [condition] is `false`. + * + * If [condition] is `true`, this operator does nothing. + * + * ### External resources + * + * - [`$set`](https://www.mongodb.com/docs/manual/reference/operator/update/set/) + * - [`$cond`](https://www.mongodb.com/docs/manual/reference/operator/aggregation/cond/) + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + fun <@kotlin.internal.OnlyInputTypes V> Field.setUnless(condition: Value, value: Value) = + this set cond(condition, of(this), value) + + /** + * Replaces the value of a field with the specified [value], if [condition] is `false`. + * + * If [condition] is `true`, this operator does nothing. + * + * ### External resources + * + * - [`$set`](https://www.mongodb.com/docs/manual/reference/operator/update/set/) + * - [`$cond`](https://www.mongodb.com/docs/manual/reference/operator/aggregation/cond/) + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + fun <@kotlin.internal.OnlyInputTypes V> KProperty1.setUnless(condition: Value, value: Value) = + this.field.setUnless(condition, value) + + /** + * Replaces the value of a field with the specified [value], if [condition] is `false`. + * + * If [condition] is `true`, this operator does nothing. + * + * ### External resources + * + * - [`$set`](https://www.mongodb.com/docs/manual/reference/operator/update/set/) + * - [`$cond`](https://www.mongodb.com/docs/manual/reference/operator/aggregation/cond/) + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + fun <@kotlin.internal.OnlyInputTypes V> Field.setUnless(condition: Value, value: V) = + this.setUnless(condition, of(value)) + + /** + * Replaces the value of a field with the specified [value], if [condition] is `false`. + * + * If [condition] is `true`, this operator does nothing. + * + * ### External resources + * + * - [`$set`](https://www.mongodb.com/docs/manual/reference/operator/update/set/) + * - [`$cond`](https://www.mongodb.com/docs/manual/reference/operator/aggregation/cond/) + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + fun <@kotlin.internal.OnlyInputTypes V> KProperty1.setUnless(condition: Value, value: V) = + this.field.setUnless(condition, value) + + /** + * Replaces the value of a field with the specified [value], if [condition] is `false`. + * + * If [condition] is `true`, this operator does nothing. + * + * ### External resources + * + * - [`$set`](https://www.mongodb.com/docs/manual/reference/operator/update/set/) + * - [`$cond`](https://www.mongodb.com/docs/manual/reference/operator/aggregation/cond/) + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + fun <@kotlin.internal.OnlyInputTypes V> Field.setUnless(condition: Boolean, value: Value) { + if (!condition) + this.set(value) + } + + /** + * Replaces the value of a field with the specified [value], if [condition] is `false`. + * + * If [condition] is `true`, this operator does nothing. + * + * ### External resources + * + * - [`$set`](https://www.mongodb.com/docs/manual/reference/operator/update/set/) + * - [`$cond`](https://www.mongodb.com/docs/manual/reference/operator/aggregation/cond/) + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + fun <@kotlin.internal.OnlyInputTypes V> KProperty1.setUnless(condition: Boolean, value: Value) = + this.field.setUnless(condition, value) + + /** + * Replaces the value of a field with the specified [value], if [condition] is `false`. + * + * If [condition] is `true`, this operator does nothing. + * + * ### External resources + * + * - [`$set`](https://www.mongodb.com/docs/manual/reference/operator/update/set/) + * - [`$cond`](https://www.mongodb.com/docs/manual/reference/operator/aggregation/cond/) + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + fun <@kotlin.internal.OnlyInputTypes V> Field.setUnless(condition: Boolean, value: V) = + this.setUnless(condition, of(value)) + + /** + * Replaces the value of a field with the specified [value], if [condition] is `false`. + * + * If [condition] is `true`, this operator does nothing. + * + * ### External resources + * + * - [`$set`](https://www.mongodb.com/docs/manual/reference/operator/update/set/) + * - [`$cond`](https://www.mongodb.com/docs/manual/reference/operator/aggregation/cond/) + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + fun <@kotlin.internal.OnlyInputTypes V> KProperty1.setUnless(condition: Boolean, value: V) = + this.field.setUnless(condition, value) + + // endregion + // endregion } private class SetExpression( diff --git a/dsl/src/commonTest/kotlin/aggregation/stages/SetTest.kt b/dsl/src/commonTest/kotlin/aggregation/stages/SetTest.kt --- a/dsl/src/commonTest/kotlin/aggregation/stages/SetTest.kt +++ b/dsl/src/commonTest/kotlin/aggregation/stages/SetTest.kt @@ -16,10 +16,7 @@ package opensavvy.ktmongo.dsl.aggregation.stages -import opensavvy.ktmongo.dsl.aggregation.TestPipeline -import opensavvy.ktmongo.dsl.aggregation.literal -import opensavvy.ktmongo.dsl.aggregation.set -import opensavvy.ktmongo.dsl.aggregation.shouldBeBson +import opensavvy.ktmongo.dsl.aggregation.* import opensavvy.ktmongo.dsl.expr.filter.gt import opensavvy.prepared.runner.kotest.PreparedSpec import kotlin.text.Typography.dollar @@ -57,6 +54,96 @@ } ] """.trimIndent()) + } + + suite("setIf and setUnless") { + + test("Aggregation values") { + TestPipeline() + .set { + Target::deathDate.setIf(of(Target::isAlive), of(12)) + } + .shouldBeBson(""" + [ + { + "$set": { + "deathDate": { + "$cond": { + "if": "${dollar}isAlive", + "then": { + "$literal": 12 + }, + "else": "${dollar}deathDate" + } + } + } + } + ] + """.trimIndent()) + } + + test("Kotlin values") { + TestPipeline() + .set { + Target::deathDate.setIf(of(Target::isAlive), 12) + } + .shouldBeBson(""" + [ + { + "$set": { + "deathDate": { + "$cond": { + "if": "${dollar}isAlive", + "then": { + "$literal": 12 + }, + "else": "${dollar}deathDate" + } + } + } + } + ] + """.trimIndent()) + } + + test("Boolean condition") { + TestPipeline() + .set { + Target::deathDate.setIf(true, of(12)) + Target::deathDate.setIf(false, of(13)) + } + .shouldBeBson(""" + [ + { + "$set": { + "deathDate": { + "$literal": 12 + } + } + } + ] + """.trimIndent()) + } + + test("Boolean condition and Kotlin values") { + TestPipeline() + .set { + Target::deathDate.setIf(true, 12) + Target::deathDate.setIf(false, 13) + } + .shouldBeBson(""" + [ + { + "$set": { + "deathDate": { + "$literal": 12 + } + } + } + ] + """.trimIndent()) + } + } }) diff --git a/dsl/src/commonTest/kotlin/expr/update/FieldUpdateTest.kt b/dsl/src/commonTest/kotlin/expr/update/FieldUpdateTest.kt --- a/dsl/src/commonTest/kotlin/expr/update/FieldUpdateTest.kt +++ b/dsl/src/commonTest/kotlin/expr/update/FieldUpdateTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, OpenSavvy and contributors. + * Copyright (c) 2024-2025, 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. @@ -127,8 +127,8 @@ test("Multiple fields") { update { - User::money inc 5.2 - User::bestFriend / Friend::money inc -5.2f + User::money += 5.2 + User::bestFriend / Friend::money += -5.2f } shouldBeBson """ { "$inc": {