diff --git a/dsl-template/src/commonMain/kotlin/query/UpdateQuery.kt b/dsl-template/src/commonMain/kotlin/query/UpdateQuery.kt index e423c040..37105783 100644 --- a/dsl-template/src/commonMain/kotlin/query/UpdateQuery.kt +++ b/dsl-template/src/commonMain/kotlin/query/UpdateQuery.kt @@ -76,6 +76,11 @@ import kotlin.time.Instant * Time management: * - [`$currentDate`][setToCurrentDate] * + * Bitwise operators: + * - [`$bit`][bitAnd] (AND) + * - [`$bit`][bitOr] (OR) + * - [`$bit`][bitXor] (XOR) + * * If you can't find the operator you're searching for, visit the [tracking issue](https://gitlab.com/opensavvy/ktmongo/-/issues/5). * * ### External resources @@ -1810,6 +1815,171 @@ interface UpdateQuery : CompoundBsonNode, FieldDsl { this.pullValues(predicate, typeOf()) } + // endregion + // region Bitwise operators + + /** + * Performs a bitwise AND operation on a field. + * + * This operator should always be used with a field of type [Int] or [Long]. + * + * ### Example + * + * ```kotlin + * class Switch( + * val _id: Int, + * val expdata: Int, + * ) + * + * switches.filter { + * Switch::_id eq 1 + * }.updateOne { + * Switch::expdata bitAnd 10 + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/bit/) + */ + @KtMongoDsl + infix fun Field.bitAnd(mask: Long) + + /** + * Performs a bitwise AND operation on a field. + * + * This operator should always be used with a field of type [Int] or [Long]. + * + * ### Example + * + * ```kotlin + * class Switch( + * val _id: Int, + * val expdata: Int, + * ) + * + * switches.filter { + * Switch::_id eq 1 + * }.updateOne { + * Switch::expdata bitAnd 10 + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/bit/) + */ + @KtMongoDsl + infix fun Field.bitAnd(mask: Int) + + /** + * Performs a bitwise OR operation on a field. + * + * This operator should always be used with a field of type [Int] or [Long]. + * + * ### Example + * + * ```kotlin + * class Switch( + * val _id: Int, + * val expdata: Int, + * ) + * + * switches.filter { + * Switch::_id eq 1 + * }.updateOne { + * Switch::expdata bitOr 10 + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/bit/) + */ + @KtMongoDsl + infix fun Field.bitOr(mask: Long) + + /** + * Performs a bitwise OR operation on a field. + * + * This operator should always be used with a field of type [Int] or [Long]. + * + * ### Example + * + * ```kotlin + * class Switch( + * val _id: Int, + * val expdata: Int, + * ) + * + * switches.filter { + * Switch::_id eq 1 + * }.updateOne { + * Switch::expdata bitOr 10 + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/bit/) + */ + @KtMongoDsl + infix fun Field.bitOr(mask: Int) + + /** + * Performs a bitwise XOR (exclusive OR) operation on a field. + * + * This operator should always be used with a field of type [Int] or [Long]. + * + * ### Example + * + * ```kotlin + * class Switch( + * val _id: Int, + * val expdata: Int, + * ) + * + * switches.filter { + * Switch::_id eq 1 + * }.updateOne { + * Switch::expdata bitXor 10 + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/bit/) + */ + @KtMongoDsl + infix fun Field.bitXor(mask: Long) + + /** + * Performs a bitwise XOR (exclusive OR) operation on a field. + * + * This operator should always be used with a field of type [Int] or [Long]. + * + * ### Example + * + * ```kotlin + * class Switch( + * val _id: Int, + * val expdata: Int, + * ) + * + * switches.filter { + * Switch::_id eq 1 + * }.updateOne { + * Switch::expdata bitXor 10 + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/bit/) + */ + @KtMongoDsl + infix fun Field.bitXor(mask: Int) + // endregion } diff --git a/dsl-template/src/commonMain/kotlin/query/UpdateQueryImpl.kt b/dsl-template/src/commonMain/kotlin/query/UpdateQueryImpl.kt index f123b6f1..04e3873e 100644 --- a/dsl-template/src/commonMain/kotlin/query/UpdateQueryImpl.kt +++ b/dsl-template/src/commonMain/kotlin/query/UpdateQueryImpl.kt @@ -38,6 +38,7 @@ import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName import kotlin.reflect.KClass import kotlin.reflect.KType +import kotlin.reflect.typeOf import kotlin.time.Instant /** @@ -732,6 +733,61 @@ private class UpdateQueryImpl( } } + // endregion + // region Bitwise operators + + @OptIn(DangerousMongoApi::class, LowLevelApi::class) + override fun Field.bitAnd(mask: Int) { + accept(BitBsonNode(listOf(Triple(path, "and", Value(mask, typeOf()))), context)) + } + + @OptIn(DangerousMongoApi::class, LowLevelApi::class) + override fun Field.bitAnd(mask: Long) { + accept(BitBsonNode(listOf(Triple(path, "and", Value(mask, typeOf()))), context)) + } + + @OptIn(DangerousMongoApi::class, LowLevelApi::class) + override fun Field.bitOr(mask: Int) { + accept(BitBsonNode(listOf(Triple(path, "or", Value(mask, typeOf()))), context)) + } + + @OptIn(DangerousMongoApi::class, LowLevelApi::class) + override fun Field.bitOr(mask: Long) { + accept(BitBsonNode(listOf(Triple(path, "or", Value(mask, typeOf()))), context)) + } + + @OptIn(DangerousMongoApi::class, LowLevelApi::class) + override fun Field.bitXor(mask: Int) { + accept(BitBsonNode(listOf(Triple(path, "xor", Value(mask, typeOf()))), context)) + } + + @OptIn(DangerousMongoApi::class, LowLevelApi::class) + override fun Field.bitXor(mask: Long) { + accept(BitBsonNode(listOf(Triple(path, "xor", Value(mask, typeOf()))), context)) + } + + @LowLevelApi + private class BitBsonNode( + // List<(field, operatorName, mask)> + val mappings: List>, + context: BsonContext, + ) : UpdateBsonNodeNode(context) { + + override fun write(writer: BsonFieldWriter) = with(writer) { + val mappingsByField = mappings.groupBy { it.first } + + writeDocument($$"$bit") { + for ((field, operators) in mappingsByField) { + writeDocument(field.toString()) { + for ((_, operator, mask) in operators) { + writeSafe(operator, mask.value, mask.type) + } + } + } + } + } + } + // endregion companion object { @@ -776,6 +832,9 @@ private class UpdateQueryImpl( OperatorCombinator(CurrentDateBsonNode::class) { sources, context -> CurrentDateBsonNode(sources.flatMap { it.mappings }, context) }, + OperatorCombinator(BitBsonNode::class) { sources, context -> + BitBsonNode(sources.flatMap { it.mappings }, context) + }, ) } } diff --git a/dsl/src/commonMain/kotlin/query/UpdateQuery.kt b/dsl/src/commonMain/kotlin/query/UpdateQuery.kt index 5d5a31bd..74098388 100644 --- a/dsl/src/commonMain/kotlin/query/UpdateQuery.kt +++ b/dsl/src/commonMain/kotlin/query/UpdateQuery.kt @@ -79,6 +79,11 @@ import kotlin.time.Instant * Time management: * - [`$currentDate`][setToCurrentDate] * + * Bitwise operators: + * - [`$bit`][bitAnd] (AND) + * - [`$bit`][bitOr] (OR) + * - [`$bit`][bitXor] (XOR) + * * If you can't find the operator you're searching for, visit the [tracking issue](https://gitlab.com/opensavvy/ktmongo/-/issues/5). * * ### External resources @@ -2823,6 +2828,345 @@ interface UpdateQuery : CompoundBsonNode, FieldDsl { return this.field.pullValues(predicate) } + // endregion + // region Bitwise operators + + /** + * Performs a bitwise AND operation on a field. + * + * This operator should always be used with a field of type [Int] or [Long]. + * + * ### Example + * + * ```kotlin + * class Switch( + * val _id: Int, + * val expdata: Int, + * ) + * + * switches.filter { + * Switch::_id eq 1 + * }.updateOne { + * Switch::expdata bitAnd 10 + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/bit/) + */ + @KtMongoDsl + infix fun Field.bitAnd(mask: Long) + + /** + * Performs a bitwise AND operation on a field. + * + * This operator should always be used with a field of type [Int] or [Long]. + * + * ### Example + * + * ```kotlin + * class Switch( + * val _id: Int, + * val expdata: Int, + * ) + * + * switches.filter { + * Switch::_id eq 1 + * }.updateOne { + * Switch::expdata bitAnd 10 + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/bit/) + */ + @KtMongoDsl + infix fun kotlin.reflect.KProperty1.bitAnd(mask: Long) { + return this.field.bitAnd(mask) + } + + /** + * Performs a bitwise AND operation on a field. + * + * This operator should always be used with a field of type [Int] or [Long]. + * + * ### Example + * + * ```kotlin + * class Switch( + * val _id: Int, + * val expdata: Int, + * ) + * + * switches.filter { + * Switch::_id eq 1 + * }.updateOne { + * Switch::expdata bitAnd 10 + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/bit/) + */ + @KtMongoDsl + infix fun Field.bitAnd(mask: Int) + + /** + * Performs a bitwise AND operation on a field. + * + * This operator should always be used with a field of type [Int] or [Long]. + * + * ### Example + * + * ```kotlin + * class Switch( + * val _id: Int, + * val expdata: Int, + * ) + * + * switches.filter { + * Switch::_id eq 1 + * }.updateOne { + * Switch::expdata bitAnd 10 + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/bit/) + */ + @KtMongoDsl + infix fun kotlin.reflect.KProperty1.bitAnd(mask: Int) { + return this.field.bitAnd(mask) + } + + /** + * Performs a bitwise OR operation on a field. + * + * This operator should always be used with a field of type [Int] or [Long]. + * + * ### Example + * + * ```kotlin + * class Switch( + * val _id: Int, + * val expdata: Int, + * ) + * + * switches.filter { + * Switch::_id eq 1 + * }.updateOne { + * Switch::expdata bitOr 10 + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/bit/) + */ + @KtMongoDsl + infix fun Field.bitOr(mask: Long) + + /** + * Performs a bitwise OR operation on a field. + * + * This operator should always be used with a field of type [Int] or [Long]. + * + * ### Example + * + * ```kotlin + * class Switch( + * val _id: Int, + * val expdata: Int, + * ) + * + * switches.filter { + * Switch::_id eq 1 + * }.updateOne { + * Switch::expdata bitOr 10 + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/bit/) + */ + @KtMongoDsl + infix fun kotlin.reflect.KProperty1.bitOr(mask: Long) { + return this.field.bitOr(mask) + } + + /** + * Performs a bitwise OR operation on a field. + * + * This operator should always be used with a field of type [Int] or [Long]. + * + * ### Example + * + * ```kotlin + * class Switch( + * val _id: Int, + * val expdata: Int, + * ) + * + * switches.filter { + * Switch::_id eq 1 + * }.updateOne { + * Switch::expdata bitOr 10 + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/bit/) + */ + @KtMongoDsl + infix fun Field.bitOr(mask: Int) + + /** + * Performs a bitwise OR operation on a field. + * + * This operator should always be used with a field of type [Int] or [Long]. + * + * ### Example + * + * ```kotlin + * class Switch( + * val _id: Int, + * val expdata: Int, + * ) + * + * switches.filter { + * Switch::_id eq 1 + * }.updateOne { + * Switch::expdata bitOr 10 + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/bit/) + */ + @KtMongoDsl + infix fun kotlin.reflect.KProperty1.bitOr(mask: Int) { + return this.field.bitOr(mask) + } + + /** + * Performs a bitwise XOR (exclusive OR) operation on a field. + * + * This operator should always be used with a field of type [Int] or [Long]. + * + * ### Example + * + * ```kotlin + * class Switch( + * val _id: Int, + * val expdata: Int, + * ) + * + * switches.filter { + * Switch::_id eq 1 + * }.updateOne { + * Switch::expdata bitXor 10 + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/bit/) + */ + @KtMongoDsl + infix fun Field.bitXor(mask: Long) + + /** + * Performs a bitwise XOR (exclusive OR) operation on a field. + * + * This operator should always be used with a field of type [Int] or [Long]. + * + * ### Example + * + * ```kotlin + * class Switch( + * val _id: Int, + * val expdata: Int, + * ) + * + * switches.filter { + * Switch::_id eq 1 + * }.updateOne { + * Switch::expdata bitXor 10 + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/bit/) + */ + @KtMongoDsl + infix fun kotlin.reflect.KProperty1.bitXor(mask: Long) { + return this.field.bitXor(mask) + } + + /** + * Performs a bitwise XOR (exclusive OR) operation on a field. + * + * This operator should always be used with a field of type [Int] or [Long]. + * + * ### Example + * + * ```kotlin + * class Switch( + * val _id: Int, + * val expdata: Int, + * ) + * + * switches.filter { + * Switch::_id eq 1 + * }.updateOne { + * Switch::expdata bitXor 10 + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/bit/) + */ + @KtMongoDsl + infix fun Field.bitXor(mask: Int) + + /** + * Performs a bitwise XOR (exclusive OR) operation on a field. + * + * This operator should always be used with a field of type [Int] or [Long]. + * + * ### Example + * + * ```kotlin + * class Switch( + * val _id: Int, + * val expdata: Int, + * ) + * + * switches.filter { + * Switch::_id eq 1 + * }.updateOne { + * Switch::expdata bitXor 10 + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/bit/) + */ + @KtMongoDsl + infix fun kotlin.reflect.KProperty1.bitXor(mask: Int) { + return this.field.bitXor(mask) + } + // endregion } diff --git a/dsl/src/commonMain/kotlin/query/UpdateQueryImpl.kt b/dsl/src/commonMain/kotlin/query/UpdateQueryImpl.kt index 9d2bdc55..e4e22f02 100644 --- a/dsl/src/commonMain/kotlin/query/UpdateQueryImpl.kt +++ b/dsl/src/commonMain/kotlin/query/UpdateQueryImpl.kt @@ -41,6 +41,7 @@ import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName import kotlin.reflect.KClass import kotlin.reflect.KType +import kotlin.reflect.typeOf import kotlin.time.Instant /** @@ -735,6 +736,61 @@ private class UpdateQueryImpl( } } + // endregion + // region Bitwise operators + + @OptIn(DangerousMongoApi::class, LowLevelApi::class) + override fun Field.bitAnd(mask: Int) { + accept(BitBsonNode(listOf(Triple(path, "and", Value(mask, typeOf()))), context)) + } + + @OptIn(DangerousMongoApi::class, LowLevelApi::class) + override fun Field.bitAnd(mask: Long) { + accept(BitBsonNode(listOf(Triple(path, "and", Value(mask, typeOf()))), context)) + } + + @OptIn(DangerousMongoApi::class, LowLevelApi::class) + override fun Field.bitOr(mask: Int) { + accept(BitBsonNode(listOf(Triple(path, "or", Value(mask, typeOf()))), context)) + } + + @OptIn(DangerousMongoApi::class, LowLevelApi::class) + override fun Field.bitOr(mask: Long) { + accept(BitBsonNode(listOf(Triple(path, "or", Value(mask, typeOf()))), context)) + } + + @OptIn(DangerousMongoApi::class, LowLevelApi::class) + override fun Field.bitXor(mask: Int) { + accept(BitBsonNode(listOf(Triple(path, "xor", Value(mask, typeOf()))), context)) + } + + @OptIn(DangerousMongoApi::class, LowLevelApi::class) + override fun Field.bitXor(mask: Long) { + accept(BitBsonNode(listOf(Triple(path, "xor", Value(mask, typeOf()))), context)) + } + + @LowLevelApi + private class BitBsonNode( + // List<(field, operatorName, mask)> + val mappings: List>, + context: BsonContext, + ) : UpdateBsonNodeNode(context) { + + override fun write(writer: BsonFieldWriter) = with(writer) { + val mappingsByField = mappings.groupBy { it.first } + + writeDocument($$"$bit") { + for ((field, operators) in mappingsByField) { + writeDocument(field.toString()) { + for ((_, operator, mask) in operators) { + writeSafe(operator, mask.value, mask.type) + } + } + } + } + } + } + // endregion companion object { @@ -779,6 +835,9 @@ private class UpdateQueryImpl( OperatorCombinator(CurrentDateBsonNode::class) { sources, context -> CurrentDateBsonNode(sources.flatMap { it.mappings }, context) }, + OperatorCombinator(BitBsonNode::class) { sources, context -> + BitBsonNode(sources.flatMap { it.mappings }, context) + }, ) } } diff --git a/dsl/src/commonTest/kotlin/query/update/FieldUpdateTest.kt b/dsl/src/commonTest/kotlin/query/update/FieldUpdateTest.kt index b81e761d..e6e07749 100644 --- a/dsl/src/commonTest/kotlin/query/update/FieldUpdateTest.kt +++ b/dsl/src/commonTest/kotlin/query/update/FieldUpdateTest.kt @@ -1071,4 +1071,54 @@ val FieldUpdateTest by multiContextSuite { """.trimIndent() } } + + suite($$"$bit") { + test("Simple AND on Int") { + update { + User::score bitAnd 10 + } shouldBeBson $$""" + { + "$bit": { + "score": { + "and": 10 + } + } + } + """.trimIndent() + } + + test("Two different bit operators on different fields") { + update { + User::externalId bitOr 7 + User::score bitXor 1 + } shouldBeBson $$""" + { + "$bit": { + "externalId": { + "or": 7 + }, + "score": { + "xor": 1 + } + } + } + """.trimIndent() + } + + test("Two different bit operators on the same field") { + update { + User::score bitAnd 10 + User::score bitXor 7 + } shouldBeBson $$""" + { + "$bit": { + "score": { + "and": 10, + "xor": 7 + } + } + } + """.trimIndent() + } + } } diff --git a/dsl/src/commonTest/kotlin/query/update/UpdateUtils.kt b/dsl/src/commonTest/kotlin/query/update/UpdateUtils.kt index 24bf4917..19b4f51b 100644 --- a/dsl/src/commonTest/kotlin/query/update/UpdateUtils.kt +++ b/dsl/src/commonTest/kotlin/query/update/UpdateUtils.kt @@ -52,6 +52,8 @@ class User( val creationInstant: Instant, val modificationTimestamp: Timestamp, val recursive: List, + val score: Int, + val externalId: Long = 1, ) @OptIn(LowLevelApi::class)