diff --git a/driver-kotlin-extensions/src/main/kotlin/com/mongodb/kotlin/client/model/Updates.kt b/driver-kotlin-extensions/src/main/kotlin/com/mongodb/kotlin/client/model/Updates.kt new file mode 100644 index 00000000000..4bb272b9fb5 --- /dev/null +++ b/driver-kotlin-extensions/src/main/kotlin/com/mongodb/kotlin/client/model/Updates.kt @@ -0,0 +1,506 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * Copyright (C) 2016/2022 Litote + * + * 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. + * + * @custom-license-header + */ +package com.mongodb.kotlin.client.model + +import com.mongodb.client.model.PushOptions +import com.mongodb.client.model.UpdateOptions +import com.mongodb.client.model.Updates +import kotlin.internal.OnlyInputTypes +import kotlin.reflect.KProperty +import org.bson.conversions.Bson + +/** + * Updates extension methods to improve Kotlin interop + * + * @since 5.3 + */ +@Suppress("TooManyFunctions") +public object Updates { + + /** + * Creates an update that sets the value of the property to the given value. + * + * @param value the value + * @param the value type + * @return the update @mongodb.driver.manual reference/operator/update/set/ $set + */ + @JvmSynthetic + @JvmName("setExt") + public infix fun <@OnlyInputTypes T> KProperty.set(value: T?): Bson = Updates.set(path(), value) + + /** + * Creates an update that sets the value of the property to the given value. + * + * @param property the data class property + * @param value the value + * @param the value type + * @return the update @mongodb.driver.manual reference/operator/update/set/ $set + */ + public fun <@OnlyInputTypes T> set(property: KProperty, value: T?): Bson = property.set(value) + + /** + * Combine a list of updates into a single update. + * + * @param updates the list of updates + * @return a combined update + */ + public fun combine(vararg updates: Bson): Bson = Updates.combine(*updates) + + /** + * Combine a list of updates into a single update. + * + * @param updates the list of updates + * @return a combined update + */ + public fun combine(updates: List): Bson = Updates.combine(updates) + + /** + * Creates an update that deletes the property with the given name. + * + * @param property the property + * @return the update @mongodb.driver.manual reference/operator/update/unset/ $unset + */ + public fun unset(property: KProperty): Bson = Updates.unset(property.path()) + + /** + * Creates an update that sets the value of the property to the given value, but only if the update is an upsert + * that results in an insert of a document. + * + * @param value the value + * @param the value type + * @return the update @mongodb.driver.manual reference/operator/update/setOnInsert/ $setOnInsert + * @see UpdateOptions#upsert(boolean) + */ + @JvmSynthetic + @JvmName("setOnInsertExt") + public infix fun <@OnlyInputTypes T> KProperty.setOnInsert(value: T?): Bson = Updates.setOnInsert(path(), value) + + /** + * Creates an update that sets the value of the property to the given value, but only if the update is an upsert + * that results in an insert of a document. + * + * @param property the property + * @param value the value + * @param the value type + * @return the update @mongodb.driver.manual reference/operator/update/setOnInsert/ $setOnInsert + * @see UpdateOptions#upsert(boolean) + */ + public fun <@OnlyInputTypes T> setOnInsert(property: KProperty, value: T?): Bson = property.setOnInsert(value) + + /** + * Creates an update that renames a field. + * + * @param newProperty the new property + * @return the update @mongodb.driver.manual reference/operator/update/rename/ $rename + */ + @JvmSynthetic + @JvmName("renameExt") + public infix fun <@OnlyInputTypes T> KProperty.rename(newProperty: KProperty): Bson = + Updates.rename(path(), newProperty.path()) + + /** + * Creates an update that renames a field. + * + * @param property the property + * @param newProperty the new property + * @return the update @mongodb.driver.manual reference/operator/update/rename/ $rename + */ + public fun <@OnlyInputTypes T> rename(property: KProperty, newProperty: KProperty): Bson = + property.rename(newProperty) + + /** + * Creates an update that increments the value of the property by the given value. + * + * @param number the value + * @return the update @mongodb.driver.manual reference/operator/update/inc/ $inc + */ + @JvmSynthetic + @JvmName("incExt") + public infix fun KProperty.inc(number: Number): Bson = Updates.inc(path(), number) + + /** + * Creates an update that increments the value of the property by the given value. + * + * @param property the property + * @param number the value + * @return the update @mongodb.driver.manual reference/operator/update/inc/ $inc + */ + public fun inc(property: KProperty, number: Number): Bson = property.inc(number) + + /** + * Creates an update that multiplies the value of the property by the given number. + * + * @param number the non-null number + * @return the update @mongodb.driver.manual reference/operator/update/mul/ $mul + */ + @JvmSynthetic + @JvmName("mulExt") + public infix fun KProperty.mul(number: Number): Bson = Updates.mul(path(), number) + + /** + * Creates an update that multiplies the value of the property by the given number. + * + * @param property the property + * @param number the non-null number + * @return the update @mongodb.driver.manual reference/operator/update/mul/ $mul + */ + public fun mul(property: KProperty, number: Number): Bson = property.mul(number) + + /** + * Creates an update that sets the value of the property if the given value is less than the current value of the + * property. + * + * @param value the value + * @param the value type + * @return the update @mongodb.driver.manual reference/operator/update/min/ $min + */ + @JvmSynthetic + @JvmName("minExt") + public infix fun <@OnlyInputTypes T> KProperty.min(value: T): Bson = Updates.min(path(), value) + + /** + * Creates an update that sets the value of the property if the given value is less than the current value of the + * property. + * + * @param property the property + * @param value the value + * @param the value type + * @return the update @mongodb.driver.manual reference/operator/update/min/ $min + */ + public fun <@OnlyInputTypes T> min(property: KProperty, value: T): Bson = property.min(value) + + /** + * Creates an update that sets the value of the property if the given value is greater than the current value of the + * property. + * + * @param value the value + * @param the value type + * @return the update @mongodb.driver.manual reference/operator/update/min/ $min + */ + @JvmSynthetic + @JvmName("maxExt") + public infix fun <@OnlyInputTypes T> KProperty.max(value: T): Bson = Updates.max(path(), value) + + /** + * Creates an update that sets the value of the property if the given value is greater than the current value of the + * property. + * + * @param property the property + * @param value the value + * @param the value type + * @return the update @mongodb.driver.manual reference/operator/update/min/ $min + */ + public fun <@OnlyInputTypes T> max(property: KProperty, value: T): Bson = property.max(value) + + /** + * Creates an update that sets the value of the property to the current date as a BSON date. + * + * @param property the property + * @return the update @mongodb.driver.manual reference/operator/update/currentDate/ + * $currentDate @mongodb.driver.manual reference/bson-types/#date Date + */ + public fun currentDate(property: KProperty): Bson = Updates.currentDate(property.path()) + + /** + * Creates an update that sets the value of the property to the current date as a BSON timestamp. + * + * @param property the property + * @return the update @mongodb.driver.manual reference/operator/update/currentDate/ + * $currentDate @mongodb.driver.manual reference/bson-types/#document-bson-type-timestamp Timestamp + */ + public fun currentTimestamp(property: KProperty): Bson = Updates.currentTimestamp(property.path()) + + /** + * Creates an update that adds the given value to the array value of the property, unless the value is already + * present, in which case it does nothing + * + * @param value the value + * @param the value type + * @return the update @mongodb.driver.manual reference/operator/update/addToSet/ $addToSet + */ + @JvmSynthetic + @JvmName("addToSetExt") + public infix fun <@OnlyInputTypes T> KProperty?>.addToSet(value: T): Bson = + Updates.addToSet(path(), value) + + /** + * Creates an update that adds the given value to the array value of the property, unless the value is already + * present, in which case it does nothing + * + * @param property the property + * @param value the value + * @param the value type + * @return the update @mongodb.driver.manual reference/operator/update/addToSet/ $addToSet + */ + public fun <@OnlyInputTypes T> addToSet(property: KProperty?>, value: T): Bson = + property.addToSet(value) + + /** + * Creates an update that adds each of the given values to the array value of the property, unless the value is + * already present, in which case it does nothing + * + * @param property the property + * @param values the values + * @param the value type + * @return the update @mongodb.driver.manual reference/operator/update/addToSet/ $addToSet + */ + public fun <@OnlyInputTypes T> addEachToSet(property: KProperty?>, values: List): Bson = + Updates.addEachToSet(property.path(), values) + + /** + * Creates an update that adds the given value to the array value of the property. + * + * @param property the property + * @param value the value + * @param the value type + * @return the update @mongodb.driver.manual reference/operator/update/push/ $push + */ + public fun <@OnlyInputTypes T> push(property: KProperty?>, value: T): Bson = + Updates.push(property.path(), value) + + /** + * Creates an update that adds each of the given values to the array value of the property, applying the given + * options for positioning the pushed values, and then slicing and/or sorting the array. + * + * @param property the property + * @param values the values + * @param options the non-null push options + * @param the value type + * @return the update @mongodb.driver.manual reference/operator/update/push/ $push + */ + public fun <@OnlyInputTypes T> pushEach( + property: KProperty?>, + values: List, + options: PushOptions = PushOptions() + ): Bson = Updates.pushEach(property.path(), values, options) + + /** + * Creates an update that removes all instances of the given value from the array value of the property. + * + * @param value the value + * @param the value type + * @return the update @mongodb.driver.manual reference/operator/update/pull/ $pull + */ + @JvmSynthetic + @JvmName("pullExt") + public infix fun <@OnlyInputTypes T> KProperty?>.pull(value: T?): Bson = Updates.pull(path(), value) + + /** + * Creates an update that removes all instances of the given value from the array value of the property. + * + * @param property the property + * @param value the value + * @param the value type + * @return the update @mongodb.driver.manual reference/operator/update/pull/ $pull + */ + public fun <@OnlyInputTypes T> pull(property: KProperty?>, value: T?): Bson = property.pull(value) + + /** + * Creates an update that removes all instances of the given value from the array value of the property. + * + * @param filter the value + * @return the update @mongodb.driver.manual reference/operator/update/pull/ $pull + */ + @JvmSynthetic + @JvmName("pullByFilterExt") + public infix fun KProperty<*>.pullByFilter(filter: Bson): Bson = Updates.pull(path(), filter) + + /** + * Creates an update that removes all instances of the given value from the array value of the property. + * + * @param property the property + * @param filter the value + * @return the update @mongodb.driver.manual reference/operator/update/pull/ $pull + */ + public fun pullByFilter(property: KProperty<*>, filter: Bson): Bson = property.pullByFilter(filter) + + /** + * Creates an update that removes from an array all elements that match the given filter. + * + * @param filter the query filter + * @return the update @mongodb.driver.manual reference/operator/update/pull/ $pull + */ + public fun pullByFilter(filter: Bson): Bson = Updates.pullByFilter(filter) + + /** + * Creates an update that removes all instances of the given values from the array value of the property. + * + * @param values the values + * @param the value type + * @return the update @mongodb.driver.manual reference/operator/update/pull/ $pull + */ + @JvmSynthetic + @JvmName("pullAllExt") + public infix fun <@OnlyInputTypes T> KProperty?>.pullAll(values: List?): Bson = + Updates.pullAll(path(), values ?: emptyList()) + + /** + * Creates an update that removes all instances of the given values from the array value of the property. + * + * @param property the property + * @param values the values + * @param the value type + * @return the update @mongodb.driver.manual reference/operator/update/pull/ $pull + */ + public fun <@OnlyInputTypes T> pullAll(property: KProperty?>, values: List?): Bson = + property.pullAll(values ?: emptyList()) + + /** + * Creates an update that pops the first element of an array that is the value of the property. + * + * @param property the property + * @return the update @mongodb.driver.manual reference/operator/update/pop/ $pop + */ + public fun popFirst(property: KProperty): Bson = Updates.popFirst(property.path()) + + /** + * Creates an update that pops the last element of an array that is the value of the property. + * + * @param property the property + * @return the update @mongodb.driver.manual reference/operator/update/pop/ $pop + */ + public fun popLast(property: KProperty): Bson = Updates.popLast(property.path()) + + /** + * Creates an update that performs a bitwise and between the given integer value and the integral value of the + * property. + * + * @param value the value + * @return the update + */ + @JvmSynthetic + @JvmName("bitwiseAndExt") + public infix fun KProperty.bitwiseAnd(value: Int): Bson = Updates.bitwiseAnd(path(), value) + + /** + * Creates an update that performs a bitwise and between the given integer value and the integral value of the + * property. + * + * @param property the property + * @param value the value + * @return the update + */ + public fun bitwiseAnd(property: KProperty, value: Int): Bson = property.bitwiseAnd(value) + + /** + * Creates an update that performs a bitwise and between the given long value and the integral value of the + * property. + * + * @param value the value + * @return the update @mongodb.driver.manual reference/operator/update/bit/ $bit + */ + @JvmSynthetic + @JvmName("bitwiseAndExt") + public infix fun KProperty.bitwiseAnd(value: Long): Bson = Updates.bitwiseAnd(path(), value) + + /** + * Creates an update that performs a bitwise and between the given long value and the integral value of the + * property. + * + * @param property the property + * @param value the value + * @return the update @mongodb.driver.manual reference/operator/update/bit/ $bit + */ + public fun bitwiseAnd(property: KProperty, value: Long): Bson = property.bitwiseAnd(value) + + /** + * Creates an update that performs a bitwise or between the given integer value and the integral value of the + * property. + * + * @param value the value + * @return the update @mongodb.driver.manual reference/operator/update/bit/ $bit + */ + @JvmSynthetic + @JvmName("bitwiseOrExt") + public infix fun KProperty.bitwiseOr(value: Int): Bson = Updates.bitwiseOr(path(), value) + + /** + * Creates an update that performs a bitwise or between the given integer value and the integral value of the + * property. + * + * @param property the property + * @param value the value + * @return the update @mongodb.driver.manual reference/operator/update/bit/ $bit + */ + public fun bitwiseOr(property: KProperty, value: Int): Bson = + Updates.bitwiseOr(property.path(), value) + + /** + * Creates an update that performs a bitwise or between the given long value and the integral value of the property. + * + * @param value the value + * @return the update @mongodb.driver.manual reference/operator/update/bit/ $bit + */ + @JvmSynthetic + @JvmName("bitwiseOrExt") + public infix fun KProperty.bitwiseOr(value: Long): Bson = Updates.bitwiseOr(path(), value) + + /** + * Creates an update that performs a bitwise or between the given long value and the integral value of the property. + * + * @param property the property + * @param value the value + * @return the update @mongodb.driver.manual reference/operator/update/bit/ $bit + */ + public fun bitwiseOr(property: KProperty, value: Long): Bson = property.bitwiseOr(value) + + /** + * Creates an update that performs a bitwise xor between the given integer value and the integral value of the + * property. + * + * @param value the value + * @return the update + */ + @JvmSynthetic + @JvmName("bitwiseXorExt") + public infix fun KProperty.bitwiseXor(value: Int): Bson = Updates.bitwiseXor(path(), value) + + /** + * Creates an update that performs a bitwise xor between the given integer value and the integral value of the + * property. + * + * @param property the property + * @param value the value + * @return the update + */ + public fun bitwiseXor(property: KProperty, value: Int): Bson = + Updates.bitwiseXor(property.path(), value) + + /** + * Creates an update that performs a bitwise xor between the given long value and the integral value of the + * property. + * + * @param value the value + * @return the update + */ + @JvmSynthetic + @JvmName("addToSetExt") + public infix fun KProperty.bitwiseXor(value: Long): Bson = Updates.bitwiseXor(path(), value) + + /** + * Creates an update that performs a bitwise xor between the given long value and the integral value of the + * property. + * + * @param property the property + * @param value the value + * @return the update + */ + public fun bitwiseXor(property: KProperty, value: Long): Bson = + Updates.bitwiseXor(property.path(), value) +} diff --git a/driver-kotlin-extensions/src/test/kotlin/com/mongodb/kotlin/client/model/ExtensionsApiTest.kt b/driver-kotlin-extensions/src/test/kotlin/com/mongodb/kotlin/client/model/ExtensionsApiTest.kt index d71312f31ab..312ad754779 100644 --- a/driver-kotlin-extensions/src/test/kotlin/com/mongodb/kotlin/client/model/ExtensionsApiTest.kt +++ b/driver-kotlin-extensions/src/test/kotlin/com/mongodb/kotlin/client/model/ExtensionsApiTest.kt @@ -39,6 +39,15 @@ class ExtensionsApiTest { assertTrue(notImplemented.isEmpty(), "Some possible Projections were not implemented: $notImplemented") } + @Test + fun shouldHaveAllUpdatesExtensions() { + val kotlinExtensions: Set = getKotlinExtensions("Updates") + val javaMethods: Set = getJavaMethods("Updates") + + val notImplemented = javaMethods subtract kotlinExtensions + assertTrue(notImplemented.isEmpty(), "Some possible Updates were not implemented: $notImplemented") + } + private fun getKotlinExtensions(className: String): Set { return ClassGraph() .enableClassInfo() diff --git a/driver-kotlin-extensions/src/test/kotlin/com/mongodb/kotlin/client/model/UpdatesTest.kt b/driver-kotlin-extensions/src/test/kotlin/com/mongodb/kotlin/client/model/UpdatesTest.kt new file mode 100644 index 00000000000..aa51bc98921 --- /dev/null +++ b/driver-kotlin-extensions/src/test/kotlin/com/mongodb/kotlin/client/model/UpdatesTest.kt @@ -0,0 +1,287 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * Copyright (C) 2016/2022 Litote + * + * 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. + * + * @custom-license-header + */ +package com.mongodb.kotlin.client.model + +import com.mongodb.client.model.PushOptions +import com.mongodb.kotlin.client.model.Filters.gte +import com.mongodb.kotlin.client.model.Updates.addEachToSet +import com.mongodb.kotlin.client.model.Updates.addToSet +import com.mongodb.kotlin.client.model.Updates.bitwiseAnd +import com.mongodb.kotlin.client.model.Updates.bitwiseOr +import com.mongodb.kotlin.client.model.Updates.bitwiseXor +import com.mongodb.kotlin.client.model.Updates.combine +import com.mongodb.kotlin.client.model.Updates.currentDate +import com.mongodb.kotlin.client.model.Updates.currentTimestamp +import com.mongodb.kotlin.client.model.Updates.inc +import com.mongodb.kotlin.client.model.Updates.max +import com.mongodb.kotlin.client.model.Updates.min +import com.mongodb.kotlin.client.model.Updates.mul +import com.mongodb.kotlin.client.model.Updates.popFirst +import com.mongodb.kotlin.client.model.Updates.popLast +import com.mongodb.kotlin.client.model.Updates.pull +import com.mongodb.kotlin.client.model.Updates.pullAll +import com.mongodb.kotlin.client.model.Updates.pullByFilter +import com.mongodb.kotlin.client.model.Updates.push +import com.mongodb.kotlin.client.model.Updates.pushEach +import com.mongodb.kotlin.client.model.Updates.rename +import com.mongodb.kotlin.client.model.Updates.set +import com.mongodb.kotlin.client.model.Updates.setOnInsert +import com.mongodb.kotlin.client.model.Updates.unset +import java.time.Instant +import java.util.Date +import kotlin.test.assertEquals +import org.bson.BsonDocument +import org.bson.Document +import org.bson.codecs.configuration.CodecRegistries +import org.bson.codecs.configuration.CodecRegistries.fromProviders +import org.bson.codecs.configuration.CodecRegistry +import org.bson.codecs.pojo.PojoCodecProvider +import org.bson.conversions.Bson +import org.junit.Test + +class UpdatesTest { + @Test + fun `should render $set`() { + assertEquals(""" {"${'$'}set": {"name": "foo"}} """, set(Person::name, "foo")) + assertEquals(""" {"${'$'}set": {"name": null}} """, set(Person::name, null)) + + assertEquals(set(Person::name, "foo"), Person::name set "foo") + assertEquals(set(Person::name, null), Person::name set null) + } + + @Test + fun `should render $setOnInsert`() { + assertEquals(""" {${'$'}setOnInsert : { age : 42} } """, setOnInsert(Person::age, 42)) + assertEquals(setOnInsert(Person::age, 42), Person::age setOnInsert 42) + + assertEquals(""" {${'$'}setOnInsert : { age : null} } """, setOnInsert(Person::age, null)) + assertEquals(setOnInsert(Person::age, null), Person::age setOnInsert null) + + assertEquals( + """ {"${'$'}setOnInsert": {"name": "foo", "age": 42}}""", + combine(listOf(setOnInsert(Person::name, "foo"), setOnInsert(Person::age, 42)))) + } + + @Test + fun `should render $unset`() { + assertEquals(""" {"${'$'}unset": {"name": ""}} """, unset(Person::name)) + } + + @Test + fun `should render $rename`() { + assertEquals(""" {${'$'}rename : { "age" : "score"} } """, rename(Person::age, Grade::score)) + } + + @Test + fun `should render $inc`() { + assertEquals(""" {${'$'}inc : { age : 1} } """, inc(Person::age, 1)) + assertEquals(inc(Person::age, 1), Person::age inc 1) + + assertEquals(""" {${'$'}inc : { age : {${'$'}numberLong : "42"}} } """, inc(Person::age, 42L)) + assertEquals(""" {${'$'}inc : { age : 3.14 } } """, inc(Person::age, 3.14)) + } + + @Test + fun `should render $mul`() { + assertEquals(""" {${'$'}mul : { "age" : 1} } """, mul(Person::age, 1)) + assertEquals(""" {${'$'}mul : { "age" : 1} } """, Person::age mul 1) + + assertEquals(""" {${'$'}mul : { "age" : {${'$'}numberLong : "5"}} } """, mul(Person::age, 5L)) + assertEquals(""" {${'$'}mul : { "age" : 3.14} } """, mul(Person::age, 3.14)) + } + + @Test + fun `should render $min`() { + assertEquals(""" {${'$'}min : { age : 42} } """, min(Person::age, 42)) + assertEquals(""" {${'$'}min : { age : 42} } """, Person::age min 42) + } + + @Test + fun `should render max`() { + assertEquals(""" {${'$'}max : { age : 42} } """, max(Person::age, 42)) + assertEquals(""" {${'$'}max : { age : 42} } """, Person::age max 42) + } + + @Test + fun `should render $currentDate`() { + assertEquals(""" {${'$'}currentDate : { date : true} } """, currentDate(Person::date)) + assertEquals( + """ {${'$'}currentDate : { date : {${'$'}type : "timestamp"}} } """, currentTimestamp(Person::date)) + } + + @Test + fun `should render $addToSet`() { + assertEquals(""" {${'$'}addToSet : { results : 1} } """, addToSet(Person::results, 1)) + assertEquals(""" {${'$'}addToSet : { results : 1} } """, Person::results addToSet 1) + assertEquals( + """ {"${'$'}addToSet": {"results": {"${'$'}each": [1, 2, 3]}}} """, + addEachToSet(Person::results, listOf(1, 2, 3))) + } + + @Test + fun `should render $push`() { + assertEquals(""" {${'$'}push : { results : 1} } """, push(Person::results, 1)) + assertEquals( + """ {"${'$'}push": {"results": {"${'$'}each": [1, 2, 3]}}} """, + pushEach(Person::results, listOf(1, 2, 3), options = PushOptions())) + + assertEquals( + """ {"${'$'}push": {"grades": {"${'$'}each": + |[{"comments": [], "score": 11, "subject": "Science"}], + | "${'$'}position": 0, "${'$'}slice": 3, "${'$'}sort": {"score": -1}}}} """ + .trimMargin(), + pushEach( + Student::grades, + listOf(Grade("Science", 11, emptyList())), + options = PushOptions().position(0).slice(3).sortDocument(Document("score", -1)))) + + assertEquals( + """ {${'$'}push : { results : { ${'$'}each : + |[89, 65], ${'$'}position : 0, ${'$'}slice : 3, ${'$'}sort : -1 } } } """ + .trimMargin(), + pushEach(Person::results, listOf(89, 65), options = PushOptions().position(0).slice(3).sort(-1))) + } + + @Test + fun `should render $pull`() { + assertEquals(""" {${'$'}pull : { address : "foo"} } """, pull(Person::address, "foo")) + assertEquals(""" {${'$'}pull : { address : "foo"} } """, Person::address pull "foo") + + assertEquals(""" {${'$'}pull : { score : { ${'$'}gte : 5 }} } """, pullByFilter(Grade::score gte 5)) + assertEquals( + """ {"${'$'}pull": {"grades": {"score": {"${'$'}gte": 5}}}} """, + pullByFilter(Student::grades, Grade::score gte 5)) + assertEquals( + """ {"${'$'}pull": {"grades": {"score": {"${'$'}gte": 5}}}} """, + Student::grades pullByFilter (Grade::score gte 5)) + } + + @Test + fun `should render $pullAll`() { + assertEquals(""" {${'$'}pullAll : { results : []} } """, pullAll(Person::results, emptyList())) + assertEquals(""" {${'$'}pullAll : { results : []} } """, Person::results pullAll emptyList()) + assertEquals(""" {${'$'}pullAll : { results : [1,2,3]} } """, pullAll(Person::results, listOf(1, 2, 3))) + } + + @Test + fun `should render $pop`() { + assertEquals(""" {${'$'}pop : { address : -1} } """, popFirst(Person::address)) + assertEquals(""" {${'$'}pop : { address : 1} } """, popLast(Person::address)) + } + + @Test + fun `should render $bit`() { + assertEquals(""" {${'$'}bit : { "score" : {and : 5} } } """, bitwiseAnd(Grade::score, 5)) + assertEquals(""" {${'$'}bit : { "score" : {and : 5} } } """, Grade::score bitwiseAnd 5) + assertEquals( + """ {${'$'}bit : { "score" : {and : {${'$'}numberLong : "5"}} } } """, bitwiseAnd(Grade::score, 5L)) + assertEquals( + """ {${'$'}bit : { "score" : {and : {${'$'}numberLong : "5"}} } } """, Grade::score bitwiseAnd (5L)) + assertEquals(""" {${'$'}bit : { "score" : {or : 5} } } """, bitwiseOr(Grade::score, 5)) + assertEquals(""" {${'$'}bit : { "score" : {or : 5} } } """, Grade::score bitwiseOr 5) + assertEquals(""" {${'$'}bit : { "score" : {or : {${'$'}numberLong : "5"}} } } """, bitwiseOr(Grade::score, 5L)) + assertEquals(""" {${'$'}bit : { "score" : {or : {${'$'}numberLong : "5"}} } } """, Grade::score bitwiseOr 5L) + assertEquals(""" {${'$'}bit : { "score" : {xor : 5} } } """, bitwiseXor(Grade::score, 5)) + assertEquals(""" {${'$'}bit : { "score" : {xor : 5} } } """, Grade::score bitwiseXor 5) + assertEquals(""" {${'$'}bit : { "score" : {xor : {${'$'}numberLong : "5"}} } } """, Grade::score bitwiseXor 5L) + assertEquals( + """ {${'$'}bit : { "score" : {xor : {${'$'}numberLong : "5"}} } } """, bitwiseXor(Grade::score, 5L)) + } + + @Test + fun `should combine updates`() { + assertEquals(""" {${'$'}set : { name : "foo"} } """, combine(set(Person::name, "foo"))) + assertEquals( + """ {${'$'}set : { name : "foo", age: 42} } """, combine(set(Person::name, "foo"), set(Person::age, 42))) + assertEquals( + """ {${'$'}set : { name : "bar"} } """, combine(set(Person::name, "foo"), set(Person::name, "bar"))) + assertEquals( + """ {"${'$'}set": {"name": "foo", "date": {"${'$'}date": "1970-01-01T00:00:00Z"}}, + | "${'$'}inc": {"age": 3, "floatField": 3.14}} """ + .trimMargin(), + combine( + set(Person::name, "foo"), + inc(Person::age, 3), + set(Person::date, Date.from(Instant.EPOCH)), + inc(Person::floatField, 3.14))) + + assertEquals(""" {${'$'}set : { "name" : "foo"} } """, combine(combine(set(Person::name, "foo")))) + assertEquals( + """ {${'$'}set : { "name" : "foo", "age": 42} } """, + combine(combine(set(Person::name, "foo"), set(Person::age, 42)))) + assertEquals( + """ {${'$'}set : { "name" : "bar"} } """, + combine(combine(set(Person::name, "foo"), set(Person::name, "bar")))) + + assertEquals( + """ {"${'$'}set": {"name": "bar"}, "${'$'}inc": {"age": 3, "floatField": 3.14}} """, + combine( + combine( + set(Person::name, "foo"), + inc(Person::age, 3), + set(Person::name, "bar"), + inc(Person::floatField, 3.14)))) + } + + @Test + fun `should create string representation for simple updates`() { + assertEquals( + """Update{fieldName='name', operator='${'$'}set', value=foo}""", set(Person::name, "foo").toString()) + } + + @Test + fun `should create string representation for with each update`() { + assertEquals( + """Each Update{fieldName='results', operator='${'$'}addToSet', values=[1, 2, 3]}""", + addEachToSet(Person::results, listOf(1, 2, 3)).toString()) + } + + @Test + fun `should test equals for SimpleBsonKeyValue`() { + assertEquals(setOnInsert(Person::name, "foo"), setOnInsert(Person::name, "foo")) + assertEquals(setOnInsert(Person::name, null), setOnInsert(Person::name, null)) + } + + @Test + fun `should test hashCode for SimpleBsonKeyValue`() { + assertEquals(setOnInsert(Person::name, "foo").hashCode(), setOnInsert(Person::name, "foo").hashCode()) + assertEquals(setOnInsert(Person::name, null).hashCode(), setOnInsert(Person::name, null).hashCode()) + } + + // Utils + private data class Person( + val name: String, + val age: Int, + val address: List, + val results: List, + val date: Date, + val floatField: Float + ) + + private data class Student(val name: String, val grade: Int, val grades: List) + data class Grade(val subject: String, val score: Int?, val comments: List) + + private val defaultsAndPojoCodecRegistry: CodecRegistry = + CodecRegistries.fromRegistries( + Bson.DEFAULT_CODEC_REGISTRY, fromProviders(PojoCodecProvider.builder().automatic(true).build())) + + private fun assertEquals(expected: String, result: Bson) = + assertEquals( + BsonDocument.parse(expected), result.toBsonDocument(BsonDocument::class.java, defaultsAndPojoCodecRegistry)) +}