diff --git a/dsl/README.md b/dsl/README.md index b1ec4603..05b9a878 100644 --- a/dsl/README.md +++ b/dsl/README.md @@ -37,6 +37,14 @@ To create a custom operator (for example because it isn't part of the library ye Annotations and other global concepts. +# Package opensavvy.ktmongo.dsl.tree + +Helpers to represent trees of data, built bottom-up. + +Users of the library are not expected to interact with this package. + +However, contributors to the library, and users wanting to implement custom operators, should familiarize themselves with this package. + # Package opensavvy.ktmongo.dsl.expr Operators, classified by the context in which they are available in. diff --git a/dsl/src/commonMain/kotlin/expr/common/CompoundExpression.kt b/dsl/src/commonMain/kotlin/expr/common/CompoundExpression.kt index ad72f105..d0b0fd06 100644 --- a/dsl/src/commonMain/kotlin/expr/common/CompoundExpression.kt +++ b/dsl/src/commonMain/kotlin/expr/common/CompoundExpression.kt @@ -21,6 +21,7 @@ import opensavvy.ktmongo.bson.BsonFieldWriter import opensavvy.ktmongo.dsl.DangerousMongoApi import opensavvy.ktmongo.dsl.KtMongoDsl import opensavvy.ktmongo.dsl.LowLevelApi +import opensavvy.ktmongo.dsl.tree.CompoundNode import opensavvy.ktmongo.dsl.utils.asImmutable /** @@ -36,7 +37,7 @@ import opensavvy.ktmongo.dsl.utils.asImmutable * * Prefer implementing [AbstractCompoundExpression] instead of implementing this interface directly. */ -interface CompoundExpression : Expression { +interface CompoundExpression : Expression, CompoundNode { /** * Adds a new [expression] as a child of this one. @@ -53,7 +54,7 @@ interface CompoundExpression : Expression { @LowLevelApi @DangerousMongoApi @KtMongoDsl - fun accept(expression: Expression) + override fun accept(expression: Expression) companion object } diff --git a/dsl/src/commonMain/kotlin/expr/common/Expression.kt b/dsl/src/commonMain/kotlin/expr/common/Expression.kt index 7676d728..ded8ca05 100644 --- a/dsl/src/commonMain/kotlin/expr/common/Expression.kt +++ b/dsl/src/commonMain/kotlin/expr/common/Expression.kt @@ -21,6 +21,8 @@ import opensavvy.ktmongo.bson.BsonFieldWriter import opensavvy.ktmongo.bson.buildBsonDocument import opensavvy.ktmongo.dsl.LowLevelApi import opensavvy.ktmongo.dsl.expr.PredicateExpression +import opensavvy.ktmongo.dsl.tree.AbstractNode +import opensavvy.ktmongo.dsl.tree.Node /** * A node in the BSON AST. @@ -40,7 +42,7 @@ import opensavvy.ktmongo.dsl.expr.PredicateExpression * * Use [toString][Any.toString] to view the JSON representation of this expression. */ -interface Expression { +interface Expression : Node { /** * The context used to generate this expression. @@ -55,7 +57,7 @@ interface Expression { * This ensures that expressions cannot change after they have been used within other expressions. */ @LowLevelApi - fun freeze() + override fun freeze() /** * Returns a simplified (but equivalent) expression to the current expression. @@ -63,7 +65,7 @@ interface Expression { * Returns `null` when the current expression was simplified into a no-op (= it does nothing). */ @LowLevelApi - fun simplify(): Expression? + override fun simplify(): Expression? /** * Writes the result of [simplifying][simplify] this expression into [writer]. @@ -135,18 +137,7 @@ interface Expression { */ abstract class AbstractExpression( @property:LowLevelApi override val context: BsonContext, -) : Expression { - - /** - * `true` if this expression is immutable. - */ - protected var frozen: Boolean = false - private set - - @LowLevelApi - final override fun freeze() { - frozen = true - } +) : AbstractNode(), Expression { /** * Called when this operator should be written to a [writer]. diff --git a/dsl/src/commonMain/kotlin/tree/CompoundNode.kt b/dsl/src/commonMain/kotlin/tree/CompoundNode.kt new file mode 100644 index 00000000..a91a11fc --- /dev/null +++ b/dsl/src/commonMain/kotlin/tree/CompoundNode.kt @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2024, 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.tree + +import opensavvy.ktmongo.dsl.DangerousMongoApi +import opensavvy.ktmongo.dsl.KtMongoDsl +import opensavvy.ktmongo.dsl.LowLevelApi + +/** + * A [Node] that combines multiple other nodes into a single node. + * + * A compound node may have `0.n` children. + * Children are added by calling the [accept] method. + * + * Accepted children must follow a few invariants. See [Node] for more information. + * + * There are no general-purpose way of accessing the children after they have been accepted. + * Instead, this node should be considered as representing the children itself, as a single unit. + * Subtypes may decide to provide such a feature, however. + */ +interface CompoundNode> { + + /** + * Adds a new [Node] into the current node. + * + * This method is generally considered unsafe, as it allows inserting any kind of node into the current node. + * Since this library is about representing database requests, this method allows inserting any kind + * of operation, without necessarily checking any security or coherence invariants. + * + * Users should only interact with this method when they add a new type of node that doesn't exist in the library. + * For example, when adding an operator that is missing from the library. + * In these cases, we highly recommend users to contact the maintainers of KtMongo to ensure the created operator + * respects all invariants. + * If possible, upstreaming the operator would be of benefit to all users, and guarantees future bug fixes. + * + * In all other cases, it is expected that implementations of this interface provide methods for each added functionality + * that are responsible for checking invariants and are safe to call. + * + * For a more detailed explanation of the contract of this method, see [Node]. + */ + @LowLevelApi + @DangerousMongoApi + @KtMongoDsl + fun accept(node: N) + +} diff --git a/dsl/src/commonMain/kotlin/tree/Node.kt b/dsl/src/commonMain/kotlin/tree/Node.kt new file mode 100644 index 00000000..2c9a16de --- /dev/null +++ b/dsl/src/commonMain/kotlin/tree/Node.kt @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2024, 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.tree + +import opensavvy.ktmongo.dsl.LowLevelApi + +/** + * An element in an abstract tree. + * + * It is not expected that users of this library have to deal with this class directly. + * + * ### Trees + * + * Trees are expected to be built bottom-up: a node is always built before its parents. + * Once a node has been [accepted][CompoundNode.accept] by a parent, it [freezes][freeze] (becomes forever immutable). + * Before the node is accepted, however, it gets a chance to [simplify] itself. + * The same node may be added to multiple parent nodes. + * + * This schemes ensures that trees are always as simplified as possible: we are always building a single node at a time, + * and all its children are guaranteed to be immutable and fully simplified. + * + * There are two main categories of nodes: + * - Nodes that represent some data by themselves, + * - Nodes that group other nodes into a single larger node. + * + * The former category implements this interface, whereas the latter implements [CompoundNode]. + * + * ### Implementing this interface + * + * See [AbstractNode]. + * + * @param Self The type of node returned by the [simplify] methods. + * In most cases, it should be an interface that implements [Node] and is implemented by the current subtype. + */ +interface Node> { + + /** + * Makes this node immutable. + * + * After this method has been called, the expression can never be modified again. + * This ensures that nodes cannot change after they have been used within other nodes. + * + * To learn more about this process, see [Node]. + */ + @LowLevelApi + fun freeze() + + /** + * Returns a simplified (but equivalent) node to the current node. + * + * This function is always called before this node is added to a parent node; + * the result value is added in its stead after being [frozen][freeze]. + * To learn more about this process, see [Node]. + * + * The simplest default implementation is to return `this`. + * + * @return `null` when the current node was simplified into nothingness (i.e. it does nothing). + */ + @LowLevelApi + fun simplify(): Self? + +} + +/** + * Helper to implement [Node]. + * + * This class takes care of handling [freezing][freeze]. + * Implementors should ensure to check the value of [frozen] before accepting any mutation. + */ +abstract class AbstractNode> : Node { + + /** + * `true` if [freeze] has been called. Can never become `false` again. + * + * If this value is `true`, this node should reject any attempt to mutate it. + * It is the responsibility of the implementor to satisfy this invariant. + */ + protected var frozen: Boolean = false + private set + + @LowLevelApi + final override fun freeze() { + frozen = true + } + +} -- 2.51.2 From 332cde1c4e4077d872ed7e2441055a881f033da6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Tue, 5 Nov 2024 23:06:03 +0100 Subject: [PATCH 02/10] feat(dsl): Extract all filter operators to the FilterOperators interface --- dsl/README.md | 2 +- .../kotlin/expr/FilterExpression.kt | 2014 +--------------- .../commonMain/kotlin/expr/FilterOperators.kt | 2074 +++++++++++++++++ .../kotlin/expr/filter/FilterUtils.kt | 3 +- 4 files changed, 2085 insertions(+), 2008 deletions(-) create mode 100644 dsl/src/commonMain/kotlin/expr/FilterOperators.kt diff --git a/dsl/README.md b/dsl/README.md index 05b9a878..824b0ede 100644 --- a/dsl/README.md +++ b/dsl/README.md @@ -28,7 +28,7 @@ Operators are organized by the context in which they are available in. For examp Instances of these classes are usually provided by the driver as part of its functions. -- [Filter operators][opensavvy.ktmongo.dsl.expr.FilterExpression] +- [Filter operators][opensavvy.ktmongo.dsl.expr.FilterOperators] - [Update operators][opensavvy.ktmongo.dsl.expr.UpdateExpression] To create a custom operator (for example because it isn't part of the library yet), see [AbstractExpression][opensavvy.ktmongo.dsl.expr.common.AbstractExpression]. diff --git a/dsl/src/commonMain/kotlin/expr/FilterExpression.kt b/dsl/src/commonMain/kotlin/expr/FilterExpression.kt index 912fa4a7..e78c6c9e 100644 --- a/dsl/src/commonMain/kotlin/expr/FilterExpression.kt +++ b/dsl/src/commonMain/kotlin/expr/FilterExpression.kt @@ -18,8 +18,6 @@ package opensavvy.ktmongo.dsl.expr import opensavvy.ktmongo.bson.BsonContext import opensavvy.ktmongo.bson.BsonFieldWriter -import opensavvy.ktmongo.bson.DEPRECATED_IN_BSON_SPEC -import opensavvy.ktmongo.bson.types.BsonType import opensavvy.ktmongo.dsl.DangerousMongoApi import opensavvy.ktmongo.dsl.KtMongoDsl import opensavvy.ktmongo.dsl.LowLevelApi @@ -28,106 +26,16 @@ import opensavvy.ktmongo.dsl.expr.common.AbstractExpression import opensavvy.ktmongo.dsl.expr.common.Expression import opensavvy.ktmongo.dsl.path.Field import opensavvy.ktmongo.dsl.path.FieldDsl -import opensavvy.ktmongo.dsl.path.FieldImpl import opensavvy.ktmongo.dsl.path.Path -import kotlin.jvm.JvmName -import kotlin.reflect.KProperty1 /** - * DSL for MongoDB operators that are used as predicates in conditions. - * - * ### Example - * - * This expression type is available in multiple operators, most commonly `find`: - * ```kotlin - * class User( - * val name: String, - * val age: Int, - * ) - * - * collection.find { - * User::age gte 18 - * } - * ``` - * - * ### Beware of arrays! - * - * MongoDB operators do not discriminate between scalars and arrays. - * When an array is encountered, all operators attempt to match on the array itself. - * If the match fails, the operators attempt to match array elements. - * - * It is not possible to mimic this behavior in KtMongo while still keeping type-safety, - * so operators may behave strangely when arrays are encountered. - * - * Note that if the collection corresponds to the declared Kotlin type, - * these situations can never happen, as the Kotlin type system doesn't allow them to. - * - * When developers attempt to perform an operator on the entire array, - * they should use operators as normal: - * ```kotlin - * class User( - * val name: String, - * val favoriteNumbers: List - * ) - * - * collection.find { - * User::favoriteNumbers eq listOf(1, 2) - * } - * ``` - * Developers should use the request above when they want to match a document similar to: - * ```json - * { - * favoriteNumbers: [1, 2] - * } - * ``` - * The following document will NOT match: - * ```json - * { - * favoriteNumbers: [3] - * } - * ``` - * - * However, due to MongoDB's behavior when encountering arrays, it should be noted - * that the following document WILL match: - * ```json - * { - * favoriteNumbers: [ - * [3], - * [1, 2], - * [7, 2] - * ] - * } - * ``` - * - * To execute an operator on one of the elements of an array, see [any]. - * - * ### Operators - * - * Comparison query: - * - [`$eq`][eq] - * - [`$gt`][gt] - * - [`$gte`][gte] - * - [`$in`][isOneOf] - * - [`$lt`][lt] - * - [`$lte`][lte] - * - [`$ne`][ne] - * - * Logical query: - * - [`$and`][and] - * - [`$not`][not] - * - [`$or`][or] - * - * Element query: - * - [`$exists`][exists] - * - [`$type`][hasType] - * - * Array query: - * - [`$elemMatch`][anyObject] + * Implementation of the [FilterOperators] interface. */ @KtMongoDsl class FilterExpression( context: BsonContext, ) : AbstractCompoundExpression(context), + FilterOperators, FieldDsl { // region Low-level operations @@ -147,35 +55,9 @@ class FilterExpression( // endregion // region $and, $or - /** - * Performs a logical `AND` operation on one or more expressions, - * and selects the documents that satisfy *all* the expressions. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String?, - * val age: Int, - * ) - * - * collection.findOne { - * and { - * User::name eq "foo" - * User::age eq 18 - * } - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/and/) - * - * @see or Logical `OR` operation. - */ @OptIn(LowLevelApi::class, DangerousMongoApi::class) @KtMongoDsl - fun and(block: FilterExpression.() -> Unit) { + override fun and(block: FilterOperators.() -> Unit) { accept(AndFilterExpressionNode(FilterExpression(context).apply(block).children, context)) } @@ -220,36 +102,9 @@ class FilterExpression( } } - /** - * Performs a logical `OR` operation on one or more expressions, - * and selects the documents that satisfy *at least one* of the expressions. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String?, - * val age: Int, - * ) - * - * collection.find { - * or { - * User::name eq "foo" - * User::name eq "bar" - * User::age eq 18 - * } - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/or/) - * - * @see and Logical `AND` operation. - */ @OptIn(LowLevelApi::class, DangerousMongoApi::class) @KtMongoDsl - fun or(block: FilterExpression.() -> Unit) { + override fun or(block: FilterOperators.() -> Unit) { accept(OrFilterExpressionNode(FilterExpression(context).apply(block).children, context)) } @@ -284,73 +139,13 @@ class FilterExpression( // endregion // region Predicate access - /** - * Targets a single field to execute a [targeted predicate][PredicateExpression]. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String?, - * val age: Int, - * ) - * - * collection.find { - * User::name { - * eq("foo") - * } - * } - * ``` - * - * Note that many operators available this way have a convenience function directly in this class to - * shorten this. For this example, see [eq]: - * - * ```kotlin - * collection.find { - * User::name eq "foo" - * } - * ``` - */ @OptIn(LowLevelApi::class, DangerousMongoApi::class) @Suppress("INVISIBLE_REFERENCE") @KtMongoDsl - operator fun <@kotlin.internal.OnlyInputTypes V> Field.invoke(block: PredicateExpression.() -> Unit) { + override operator fun <@kotlin.internal.OnlyInputTypes V> Field.invoke(block: PredicateExpression.() -> Unit) { accept(PredicateInFilterExpression(path, PredicateExpression(context).apply(block), context)) } - /** - * Targets a single field to execute a [targeted predicate][PredicateExpression]. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String?, - * val age: Int, - * ) - * - * collection.find { - * User::name { - * eq("foo") - * } - * } - * ``` - * - * Note that many operators available this way have a convenience function directly in this class to - * shorten this. For this example, see [eq]: - * - * ```kotlin - * collection.find { - * User::name eq "foo" - * } - * ``` - */ - @Suppress("INVISIBLE_REFERENCE") - @KtMongoDsl - operator fun <@kotlin.internal.OnlyInputTypes V> KProperty1.invoke(block: PredicateExpression.() -> Unit) { - this.field.invoke(block) - } - @LowLevelApi private class PredicateInFilterExpression( val target: Path, @@ -369,1769 +164,21 @@ class FilterExpression( } } - // endregion - // region $not - - /** - * Performs a logical `NOT` operation on the specified [expression] and selects the - * documents that *do not* match the expression. This includes the elements - * that do not contain the field. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int, - * ) - * - * collection.find { - * User::age not { - * hasType(BsonType.STRING) - * } - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/not/) - */ - @Suppress("INVISIBLE_REFERENCE") - @KtMongoDsl - infix fun <@kotlin.internal.OnlyInputTypes V> Field.not(expression: PredicateExpression.() -> Unit) { - this { this.not(expression) } - } - - /** - * Performs a logical `NOT` operation on the specified [expression] and selects the - * documents that *do not* match the expression. This includes the elements - * that do not contain the field. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int, - * ) - * - * collection.find { - * User::age not { - * hasType(BsonType.STRING) - * } - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/not/) - */ - @Suppress("INVISIBLE_REFERENCE") - @KtMongoDsl - infix fun <@kotlin.internal.OnlyInputTypes V> KProperty1.not(expression: PredicateExpression.() -> Unit) { - this.field.not(expression) - } - - // endregion - // region $eq - - /** - * Matches documents where the value of a field equals the [value]. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String?, - * val age: Int, - * ) - * - * collection.find { - * User::name eq "foo" - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/eq/) - */ - @Suppress("INVISIBLE_REFERENCE") - @KtMongoDsl - infix fun <@kotlin.internal.OnlyInputTypes V> Field.eq(value: V) { - this { eq(value) } - } - - /** - * Matches documents where the value of a field equals the [value]. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String?, - * val age: Int, - * ) - * - * collection.find { - * User::name eq "foo" - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/eq/) - */ - @Suppress("INVISIBLE_REFERENCE") - @KtMongoDsl - infix fun <@kotlin.internal.OnlyInputTypes V> KProperty1.eq(value: V) { - this.field.eq(value) - } - - /** - * Matches documents where the value of a field equals [value]. - * - * If [value] is `null`, the operator is not added (all documents are matched). - * - * ### Example - * - * This operator is useful to simplify searches when the criteria are optional. - * For example, instead of writing: - * ```kotlin - * collection.find { - * if (criteria.name != null) - * User::name eq criteria.name - * } - * ``` - * this operator can be used instead: - * ```kotlin - * collection.find { - * User::name eqNotNull criteria.name - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/eq/) - * - * @see eq Equality filter. - */ - @Suppress("INVISIBLE_REFERENCE") - @KtMongoDsl - infix fun <@kotlin.internal.OnlyInputTypes V> Field.eqNotNull(value: V?) { - this { eqNotNull(value) } - } - - /** - * Matches documents where the value of a field equals [value]. - * - * If [value] is `null`, the operator is not added (all documents are matched). - * - * ### Example - * - * This operator is useful to simplify searches when the criteria are optional. - * For example, instead of writing: - * ```kotlin - * collection.find { - * if (criteria.name != null) - * User::name eq criteria.name - * } - * ``` - * this operator can be used instead: - * ```kotlin - * collection.find { - * User::name eqNotNull criteria.name - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/eq/) - * - * @see eq Equality filter. - */ - @Suppress("INVISIBLE_REFERENCE") - @KtMongoDsl - infix fun <@kotlin.internal.OnlyInputTypes V> KProperty1.eqNotNull(value: V?) { - this.field.eqNotNull(value) - } - - // endregion - // region $ne - - /** - * Matches documents where the value of a field does not equal the [value]. - * - * The result includes documents which do not contain the specified field. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String?, - * val age: Int, - * ) - * - * collection.find { - * User::name ne "foo" - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/ne/) - * - * @see eq - */ - @Suppress("INVISIBLE_REFERENCE") - @KtMongoDsl - infix fun <@kotlin.internal.OnlyInputTypes V> Field.ne(value: V) { - this { ne(value) } - } - - /** - * Matches documents where the value of a field does not equal the [value]. - * - * The result includes documents which do not contain the specified field. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String?, - * val age: Int, - * ) - * - * collection.find { - * User::name ne "foo" - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/ne/) - * - * @see eq - */ - @Suppress("INVISIBLE_REFERENCE") - @KtMongoDsl - infix fun <@kotlin.internal.OnlyInputTypes V> KProperty1.ne(value: V) { - this.field.ne(value) - } - - // endregion - // region $exists - - /** - * Matches documents that contain the specified field, including - * values where the field value is `null`. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String?, - * val age: Int, - * ) - * - * collection.find { - * User::age.exists() - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/exists/) - * - * @see doesNotExist Opposite. - * @see isNotNull Identical, but does not match elements where the field is `null`. - */ - @KtMongoDsl - fun Field.exists() { - this { exists() } - } - - /** - * Matches documents that contain the specified field, including - * values where the field value is `null`. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String?, - * val age: Int, - * ) - * - * collection.find { - * User::age.exists() - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/exists/) - * - * @see doesNotExist Opposite. - * @see isNotNull Identical, but does not match elements where the field is `null`. - */ - @KtMongoDsl - fun KProperty1.exists() { - this.field.exists() - } - - /** - * Matches documents that do not contain the specified field. - * Documents where the field if `null` are not matched. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String?, - * val age: Int, - * ) - * - * collection.find { - * User::age.doesNotExist() - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/exists/) - * - * @see exists Opposite. - * @see isNull Only matches documents that are specifically `null`. - */ - @KtMongoDsl - fun Field.doesNotExist() { - this { doesNotExist() } - } - - /** - * Matches documents that do not contain the specified field. - * Documents where the field if `null` are not matched. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String?, - * val age: Int, - * ) - * - * collection.find { - * User::age.doesNotExist() - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/exists/) - * - * @see exists Opposite. - * @see isNull Only matches documents that are specifically `null`. - */ - @KtMongoDsl - fun KProperty1.doesNotExist() { - this.field.doesNotExist() - } - - // endregion - // region $type - - /** - * Selects documents where the value of the field is an instance of the specified BSON [type]. - * - * Querying by data type is useful when dealing with highly unstructured data where data types - * are not predictable. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Any, - * ) - * - * collection.find { - * User::age hasType BsonType.STRING - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/type/) - * - * @see isNull Checks if a value has the type [BsonType.Null]. - * @see isUndefined Checks if a value has the type [BsonType.Undefined]. - */ - @KtMongoDsl - infix fun Field.hasType(type: BsonType) { - this { hasType(type) } - } - - /** - * Selects documents where the value of the field is an instance of the specified BSON [type]. - * - * Querying by data type is useful when dealing with highly unstructured data where data types - * are not predictable. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Any, - * ) - * - * collection.find { - * User::age hasType BsonType.STRING - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/type/) - * - * @see isNull Checks if a value has the type [BsonType.Null]. - * @see isUndefined Checks if a value has the type [BsonType.Undefined]. - */ - @KtMongoDsl - infix fun KProperty1.hasType(type: BsonType) { - this.field.hasType(type) - } - - /** - * Selects documents for which the field is `null`. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int?, - * ) - * - * collection.find { - * User::age.isNull() - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/tutorial/query-for-null-fields/#type-check) - * - * @see doesNotExist Checks if the value is not set. - * @see isNotNull Opposite. - */ - @KtMongoDsl - fun Field.isNull() { - this { isNull() } - } - - /** - * Selects documents for which the field is `null`. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int?, - * ) - * - * collection.find { - * User::age.isNull() - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/tutorial/query-for-null-fields/#type-check) - * - * @see doesNotExist Checks if the value is not set. - * @see isNotNull Opposite. - */ - @KtMongoDsl - fun KProperty1.isNull() { - this.field.isNull() - } - - /** - * Selects documents for which the field is not `null`. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int?, - * ) - * - * collection.find { - * User::age.isNotNull() - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/tutorial/query-for-null-fields/#type-check) - * - * @see isNull Opposite. - */ - @KtMongoDsl - fun Field.isNotNull() { - this { isNotNull() } - } - - /** - * Selects documents for which the field is not `null`. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int?, - * ) - * - * collection.find { - * User::age.isNotNull() - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/tutorial/query-for-null-fields/#type-check) - * - * @see isNull Opposite. - */ - @KtMongoDsl - fun KProperty1.isNotNull() { - this.field.isNotNull() - } - - /** - * Selects documents for which the field is `undefined`. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int?, - * ) - * - * collection.find { - * User::age.isUndefined() - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/tutorial/query-for-null-fields/#type-check) - * - * @see isNotUndefined Opposite. - */ - @Suppress("DEPRECATION") - @Deprecated(DEPRECATED_IN_BSON_SPEC) - @KtMongoDsl - fun Field.isUndefined() { - this { isUndefined() } - } - - /** - * Selects documents for which the field is `undefined`. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int?, - * ) - * - * collection.find { - * User::age.isUndefined() - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/tutorial/query-for-null-fields/#type-check) - * - * @see isNotUndefined Opposite. - */ - @Suppress("DEPRECATION") - @Deprecated(DEPRECATED_IN_BSON_SPEC) - @KtMongoDsl - fun KProperty1.isUndefined() { - this.field.isUndefined() - } - - /** - * Selects documents for which the field is not `undefined`. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int?, - * ) - * - * collection.find { - * User::age.isNotUndefined() - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/tutorial/query-for-null-fields/#type-check) - * - * @see isUndefined Opposite. - */ - @Suppress("DEPRECATION") - @Deprecated(DEPRECATED_IN_BSON_SPEC) - @KtMongoDsl - fun Field.isNotUndefined() { - this { isNotUndefined() } - } - - /** - * Selects documents for which the field is not `undefined`. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int?, - * ) - * - * collection.find { - * User::age.isNotUndefined() - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/tutorial/query-for-null-fields/#type-check) - * - * @see isUndefined Opposite. - */ - @Suppress("DEPRECATION") - @Deprecated(DEPRECATED_IN_BSON_SPEC) - @KtMongoDsl - fun KProperty1.isNotUndefined() { - this.field.isNotUndefined() - } - - // endregion - // region $gt, $gte, $lt, $lte - - /** - * Selects documents for which this field has a value strictly greater than [value]. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int?, - * ) - * - * collection.find { - * User::age gt 18 - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/gt/) - * - * @see gtNotNull - */ - @Suppress("INVISIBLE_REFERENCE") - @KtMongoDsl - infix fun <@kotlin.internal.OnlyInputTypes V> Field.gt(value: V) { - this { gt(value) } - } - - /** - * Selects documents for which this field has a value strictly greater than [value]. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int?, - * ) - * - * collection.find { - * User::age gt 18 - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/gt/) - * - * @see gtNotNull - */ - @Suppress("INVISIBLE_REFERENCE") - @KtMongoDsl - infix fun <@kotlin.internal.OnlyInputTypes V> KProperty1.gt(value: V) { - this.field.gt(value) - } - - /** - * Selects documents for which this field has a value strictly greater than [value]. - * - * If [value] is `null`, the operator is not added (all elements are matched). - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int? - * ) - * - * collection.find { - * User::age gtNotNull 10 - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/gt/) - * - * @see gt - * @see eqNotNull Learn more about the 'notNull' variants - */ - @Suppress("INVISIBLE_REFERENCE") - @KtMongoDsl - infix fun <@kotlin.internal.OnlyInputTypes V> Field.gtNotNull(value: V?) { - this { gtNotNull(value) } - } - - /** - * Selects documents for which this field has a value strictly greater than [value]. - * - * If [value] is `null`, the operator is not added (all elements are matched). - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int? - * ) - * - * collection.find { - * User::age gtNotNull 10 - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/gt/) - * - * @see gt - * @see eqNotNull Learn more about the 'notNull' variants - */ - @Suppress("INVISIBLE_REFERENCE") - @KtMongoDsl - infix fun <@kotlin.internal.OnlyInputTypes V> KProperty1.gtNotNull(value: V?) { - this.field.gtNotNull(value) - } - - /** - * Selects documents for which this field has a value greater or equal to [value]. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int?, - * ) - * - * collection.find { - * User::age gte 18 - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/gte/) - * - * @see gteNotNull - */ - @Suppress("INVISIBLE_REFERENCE") - @KtMongoDsl - infix fun <@kotlin.internal.OnlyInputTypes V> Field.gte(value: V) { - this { gte(value) } - } - - /** - * Selects documents for which this field has a value greater or equal to [value]. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int?, - * ) - * - * collection.find { - * User::age gte 18 - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/gte/) - * - * @see gteNotNull - */ - @Suppress("INVISIBLE_REFERENCE") - @KtMongoDsl - infix fun <@kotlin.internal.OnlyInputTypes V> KProperty1.gte(value: V) { - this.field.gte(value) - } - - /** - * Selects documents for which this field has a value greater or equal to [value]. - * - * If [value] is `null`, the operator is not added (all elements are matched). - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int? - * ) - * - * collection.find { - * User::age gteNotNull 10 - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/gte/) - * - * @see gte - * @see eqNotNull Learn more about the 'notNull' variants - */ - @Suppress("INVISIBLE_REFERENCE") - @KtMongoDsl - infix fun <@kotlin.internal.OnlyInputTypes V> Field.gteNotNull(value: V?) { - this { gteNotNull(value) } - } - - /** - * Selects documents for which this field has a value greater or equal to [value]. - * - * If [value] is `null`, the operator is not added (all elements are matched). - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int? - * ) - * - * collection.find { - * User::age gteNotNull 10 - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/gte/) - * - * @see gte - * @see eqNotNull Learn more about the 'notNull' variants - */ - @Suppress("INVISIBLE_REFERENCE") - @KtMongoDsl - infix fun <@kotlin.internal.OnlyInputTypes V> KProperty1.gteNotNull(value: V?) { - this.field.gteNotNull(value) - } - - /** - * Selects documents for which this field has a value strictly lesser than [value]. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int?, - * ) - * - * collection.find { - * User::age lt 18 - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/lt/) - * - * @see ltNotNull - */ - @Suppress("INVISIBLE_REFERENCE") - @KtMongoDsl - infix fun <@kotlin.internal.OnlyInputTypes V> Field.lt(value: V) { - this { lt(value) } - } - - /** - * Selects documents for which this field has a value strictly lesser than [value]. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int?, - * ) - * - * collection.find { - * User::age lt 18 - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/lt/) - * - * @see ltNotNull - */ - @Suppress("INVISIBLE_REFERENCE") - @KtMongoDsl - infix fun <@kotlin.internal.OnlyInputTypes V> KProperty1.lt(value: V) { - this.field.lt(value) - } - - /** - * Selects documents for which this field has a value strictly lesser than [value]. - * - * If [value] is `null`, the operator is not added (all elements are matched). - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int? - * ) - * - * collection.find { - * User::age ltNotNull 10 - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/lt/) - * - * @see lt - * @see eqNotNull Learn more about the 'notNull' variants - */ - @Suppress("INVISIBLE_REFERENCE") - @KtMongoDsl - infix fun <@kotlin.internal.OnlyInputTypes V> Field.ltNotNull(value: V?) { - this { ltNotNull(value) } - } - - /** - * Selects documents for which this field has a value strictly lesser than [value]. - * - * If [value] is `null`, the operator is not added (all elements are matched). - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int? - * ) - * - * collection.find { - * User::age ltNotNull 10 - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/lt/) - * - * @see lt - * @see eqNotNull Learn more about the 'notNull' variants - */ - @Suppress("INVISIBLE_REFERENCE") - @KtMongoDsl - infix fun <@kotlin.internal.OnlyInputTypes V> KProperty1.ltNotNull(value: V?) { - this.field.ltNotNull(value) - } - - /** - * Selects documents for which this field has a value lesser or equal to [value]. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int?, - * ) - * - * collection.find { - * User::age lte 18 - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/lte/) - * - * @see lteNotNull - */ - @Suppress("INVISIBLE_REFERENCE") - @KtMongoDsl - infix fun <@kotlin.internal.OnlyInputTypes V> Field.lte(value: V) { - this { lte(value) } - } - - /** - * Selects documents for which this field has a value lesser or equal to [value]. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int?, - * ) - * - * collection.find { - * User::age lte 18 - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/lte/) - * - * @see lteNotNull - */ - @Suppress("INVISIBLE_REFERENCE") - @KtMongoDsl - infix fun <@kotlin.internal.OnlyInputTypes V> KProperty1.lte(value: V) { - this.field.lte(value) - } - - /** - * Selects documents for which this field has a value lesser or equal to [value]. - * - * If [value] is `null`, the operator is not added (all elements are matched). - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int? - * ) - * - * collection.find { - * User::age lteNotNull 10 - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/lte/) - * - * @see lte - * @see eqNotNull Learn more about the 'notNull' variants - */ - @Suppress("INVISIBLE_REFERENCE") - @KtMongoDsl - infix fun <@kotlin.internal.OnlyInputTypes V> Field.lteNotNull(value: V?) { - this { lteNotNull(value) } - } - - /** - * Selects documents for which this field has a value lesser or equal to [value]. - * - * If [value] is `null`, the operator is not added (all elements are matched). - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int? - * ) - * - * collection.find { - * User::age lteNotNull 10 - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/lte/) - * - * @see lte - * @see eqNotNull Learn more about the 'notNull' variants - */ - @Suppress("INVISIBLE_REFERENCE") - @KtMongoDsl - infix fun <@kotlin.internal.OnlyInputTypes V> KProperty1.lteNotNull(value: V?) { - this.field.lteNotNull(value) - } - - // endregion - // region $in - - /** - * Selects documents for which this field is equal to one of the given [values]. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int?, - * ) - * - * collection.find { - * User::name.isOneOf(listOf("Alfred", "Arthur")) - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/in/) - * - * @see or - * @see eq - */ - @Suppress("INVISIBLE_REFERENCE") - @KtMongoDsl - fun <@kotlin.internal.OnlyInputTypes V> Field.isOneOf(values: List) { - this { isOneOf(values) } - } - - /** - * Selects documents for which this field is equal to one of the given [values]. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int?, - * ) - * - * collection.find { - * User::name.isOneOf(listOf("Alfred", "Arthur")) - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/in/) - * - * @see or - * @see eq - */ - @Suppress("INVISIBLE_REFERENCE") - @KtMongoDsl - fun <@kotlin.internal.OnlyInputTypes V> KProperty1.isOneOf(values: List) { - this.field.isOneOf(values) - } - - /** - * Selects documents for which this field is equal to one of the given [values]. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int?, - * ) - * - * collection.find { - * User::name.isOneOf("Alfred", "Arthur") - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/in/) - * - * @see or - * @see eq - */ - @Suppress("INVISIBLE_REFERENCE") - @KtMongoDsl - fun <@kotlin.internal.OnlyInputTypes V> Field.isOneOf(vararg values: V) { - isOneOf(values.asList()) - } - - /** - * Selects documents for which this field is equal to one of the given [values]. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int?, - * ) - * - * collection.find { - * User::name.isOneOf("Alfred", "Arthur") - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/in/) - * - * @see or - * @see eq - */ - @Suppress("INVISIBLE_REFERENCE") - @KtMongoDsl - fun <@kotlin.internal.OnlyInputTypes V> KProperty1.isOneOf(vararg values: V) { - isOneOf(values.asList()) - } - // endregion // region $elemMatch - /** - * Specify operators on array elements. - * - * ### Example - * - * Find any user who has 12 as one of their favorite numbers. - * - * ```kotlin - * class User( - * val name: String, - * val favoriteNumbers: List - * ) - * - * collection.find { - * User::favoriteNumbers.any eq 12 - * } - * ``` - * - * ### Repeated usages will match different items - * - * Note that if `any` is used multiple times, it may test different items. - * For example, the following request will match the following document: - * ```kotlin - * collection.find { - * User::favoriteNumbers.any gt 2 - * User::favoriteNumbers.any lte 7 - * } - * ``` - * ```json - * { - * "name": "Nicolas", - * "favoriteNumbers": [ 1, 9 ] - * } - * ``` - * Because 1 is less than 7, and 9 is greater than 2, the document is returned. - * - * If you want to apply multiple filters to the same item, use the [any] function. - * - * ### Arrays don't exist in finds! - * - * MongoDB operators do not discriminate between scalars and arrays. - * When an array is encountered, all operators attempt to match on the array itself. - * If the match fails, the operators attempt to match array elements. - * - * It is not possible to mimic this behavior in KtMongo while still keeping type-safety, - * so KtMongo has different operators to filter a collection itself or its elements. - * - * As a consequence, the request: - * ```kotlin - * collection.find { - * User::favoriteNumbers.any eq 5 - * } - * ``` - * will, as expected, match the following document: - * ```json - * { - * favoriteNumbers: [1, 4, 5, 10] - * } - * ``` - * - * It is important to note that it WILL also match this document: - * ```json - * { - * favoriteNumbers: 5 - * } - * ``` - * - * Since this document doesn't conform to the Kotlin declared type `List`, - * it is unlikely that such an element exists, but developers should keep it in mind. - * - * ### External resources - * - * - [Official document](https://www.mongodb.com/docs/manual/tutorial/query-arrays/) - */ - @OptIn(LowLevelApi::class) - @KtMongoDsl - val Field>.any: Field - get() = FieldImpl(path) - - /** - * Specify operators on array elements. - * - * ### Example - * - * Find any user who has 12 as one of their favorite numbers. - * - * ```kotlin - * class User( - * val name: String, - * val favoriteNumbers: List - * ) - * - * collection.find { - * User::favoriteNumbers.any eq 12 - * } - * ``` - * - * ### Repeated usages will match different items - * - * Note that if `any` is used multiple times, it may test different items. - * For example, the following request will match the following document: - * ```kotlin - * collection.find { - * User::favoriteNumbers.any gt 2 - * User::favoriteNumbers.any lte 7 - * } - * ``` - * ```json - * { - * "name": "Nicolas", - * "favoriteNumbers": [ 1, 9 ] - * } - * ``` - * Because 1 is less than 7, and 9 is greater than 2, the document is returned. - * - * If you want to apply multiple filters to the same item, use the [any] function. - * - * ### Arrays don't exist in finds! - * - * MongoDB operators do not discriminate between scalars and arrays. - * When an array is encountered, all operators attempt to match on the array itself. - * If the match fails, the operators attempt to match array elements. - * - * It is not possible to mimic this behavior in KtMongo while still keeping type-safety, - * so KtMongo has different operators to filter a collection itself or its elements. - * - * As a consequence, the request: - * ```kotlin - * collection.find { - * User::favoriteNumbers.any eq 5 - * } - * ``` - * will, as expected, match the following document: - * ```json - * { - * favoriteNumbers: [1, 4, 5, 10] - * } - * ``` - * - * It is important to note that it WILL also match this document: - * ```json - * { - * favoriteNumbers: 5 - * } - * ``` - * - * Since this document doesn't conform to the Kotlin declared type `List`, - * it is unlikely that such an element exists, but developers should keep it in mind. - * - * ### External resources - * - * - [Official document](https://www.mongodb.com/docs/manual/tutorial/query-arrays/) - */ - @OptIn(LowLevelApi::class) - @KtMongoDsl - val KProperty1>.any: Field - get() = FieldImpl(field.path) - - /** - * Combines Kotlin properties into a path usable to point to any item in an array. - * - * ### Example - * - * ```kotlin - * class User( - * val grades: List - * ) - * - * class Grade( - * val name: Int - * ) - * - * collection.find { - * User::grades / Grade::name eq 19 - * } - * ``` - * - * This function is a shorthand for `any`: - * ```kotlin - * collection.find { - * User::grades.any / Gradle::name eq 19 - * } - * ``` - */ - @KtMongoDsl - @JvmName("anyChild") - operator fun Field>.div(other: Field): Field = - this.any.div(other) - - /** - * Combines Kotlin properties into a path usable to point to any item in an array. - * - * ### Example - * - * ```kotlin - * class User( - * val grades: List - * ) - * - * class Grade( - * val name: Int - * ) - * - * collection.find { - * User::grades / Grade::name eq 19 - * } - * ``` - * - * This function is a shorthand for `any`: - * ```kotlin - * collection.find { - * User::grades.any / Gradle::name eq 19 - * } - * ``` - */ - @KtMongoDsl - @JvmName("anyChild") - operator fun KProperty1>.div(other: Field): Field = - this.field.div(other) - - /** - * Combines Kotlin properties into a path usable to point to any item in an array. - * - * ### Example - * - * ```kotlin - * class User( - * val grades: List - * ) - * - * class Grade( - * val name: Int - * ) - * - * collection.find { - * User::grades / Grade::name eq 19 - * } - * ``` - * - * This function is a shorthand for `any`: - * ```kotlin - * collection.find { - * User::grades.any / Gradle::name eq 19 - * } - * ``` - */ - @KtMongoDsl - @JvmName("anyChild") - operator fun Field>.div(other: KProperty1): Field = - this.any.div(other.field) - - /** - * Combines Kotlin properties into a path usable to point to any item in an array. - * - * ### Example - * - * ```kotlin - * class User( - * val grades: List - * ) - * - * class Grade( - * val name: Int - * ) - * - * collection.find { - * User::grades / Grade::name eq 19 - * } - * ``` - * - * This function is a shorthand for `any`: - * ```kotlin - * collection.find { - * User::grades.any / Gradle::name eq 19 - * } - * ``` - */ - @KtMongoDsl - @JvmName("anyChild") - operator fun KProperty1>.div(other: KProperty1): Field = - this.field.any.div(other.field) - - /** - * Specify multiple operators on a single array element. - * - * ### Example - * - * Find students with a grade between 8 and 10, that may be eligible to perform - * an exam a second time. - * - * ```kotlin - * class Student( - * val name: String, - * val grades: List - * ) - * - * collection.find { - * Student::grades.any { - * gte(8) - * lte(10) - * } - * } - * ``` - * - * The following document will match because the grade 9 is in the interval. - * ```json - * { - * "name": "John", - * "grades": [9, 3] - * } - * ``` - * - * The following document will NOT match, because none of the grades are in the interval. - * ```json - * { - * "name": "Lea", - * "grades": [18, 19] - * } - * ``` - * - * If you want to perform multiple checks on different elements of an array, - * see the [any] property. - * - * This function only allows specifying operators on array elements directly. - * To specify operators on sub-fields of array elements, see [anyObject]. - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/elemMatch/) - */ @OptIn(LowLevelApi::class, DangerousMongoApi::class) @KtMongoDsl - fun Field>.any(block: PredicateExpression.() -> Unit) { + override fun Field>.any(block: PredicateExpression.() -> Unit) { accept(ElementMatchExpressionNode(this.path, PredicateExpression(context).apply(block), context)) } - /** - * Specify multiple operators on a single array element. - * - * ### Example - * - * Find students with a grade between 8 and 10, that may be eligible to perform - * an exam a second time. - * - * ```kotlin - * class Student( - * val name: String, - * val grades: List - * ) - * - * collection.find { - * Student::grades.any { - * gte(8) - * lte(10) - * } - * } - * ``` - * - * The following document will match because the grade 9 is in the interval. - * ```json - * { - * "name": "John", - * "grades": [9, 3] - * } - * ``` - * - * The following document will NOT match, because none of the grades are in the interval. - * ```json - * { - * "name": "Lea", - * "grades": [18, 19] - * } - * ``` - * - * If you want to perform multiple checks on different elements of an array, - * see the [any] property. - * - * This function only allows specifying operators on array elements directly. - * To specify operators on sub-fields of array elements, see [anyObject]. - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/elemMatch/) - */ - @KtMongoDsl - fun KProperty1>.any(block: PredicateExpression.() -> Unit) { - this.field.any(block) - } - - /** - * Specify multiple operators on fields of a single array element. - * - * ### Example - * - * Find customers who have a pet that is born this month, as they may be eligible for a discount. - * - * ```kotlin - * class Customer( - * val name: String, - * val pets: List, - * ) - * - * class Pet( - * val name: String, - * val birthMonth: Int - * ) - * - * val currentMonth = 3 - * - * collection.find { - * Customer::pets.anyObject { - * Pet::birthMonth gte currentMonth - * Pet::birthMonth lte (currentMonth + 1) - * } - * } - * ``` - * - * The following document will match: - * ```json - * { - * "name": "Fred", - * "pets": [ - * { - * "name": "Arthur", - * "birthMonth": 5 - * }, - * { - * "name": "Gwen", - * "birthMonth": 3 - * } - * ] - * } - * ``` - * because the pet "Gwen" has a matching birth month. - * - * If you want to perform operators on the elements directly (not on their fields), use - * [any] instead. - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/elemMatch/) - */ @OptIn(LowLevelApi::class, DangerousMongoApi::class) @KtMongoDsl - fun Field>.anyObject(block: FilterExpression.() -> Unit) { + override fun Field>.anyObject(block: FilterOperators.() -> Unit) { accept(ElementMatchExpressionNode(path, FilterExpression(context).apply(block), context)) } - /** - * Specify multiple operators on fields of a single array element. - * - * ### Example - * - * Find customers who have a pet that is born this month, as they may be eligible for a discount. - * - * ```kotlin - * class Customer( - * val name: String, - * val pets: List, - * ) - * - * class Pet( - * val name: String, - * val birthMonth: Int - * ) - * - * val currentMonth = 3 - * - * collection.find { - * Customer::pets.anyObject { - * Pet::birthMonth gte currentMonth - * Pet::birthMonth lte (currentMonth + 1) - * } - * } - * ``` - * - * The following document will match: - * ```json - * { - * "name": "Fred", - * "pets": [ - * { - * "name": "Arthur", - * "birthMonth": 5 - * }, - * { - * "name": "Gwen", - * "birthMonth": 3 - * } - * ] - * } - * ``` - * because the pet "Gwen" has a matching birth month. - * - * If you want to perform operators on the elements directly (not on their fields), use - * [any] instead. - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/elemMatch/) - */ - @OptIn(LowLevelApi::class, DangerousMongoApi::class) - @KtMongoDsl - fun KProperty1>.anyObject(block: FilterExpression.() -> Unit) { - this.field.anyObject(block) - } - @DangerousMongoApi @LowLevelApi private class ElementMatchExpressionNode( @@ -2156,57 +203,12 @@ class FilterExpression( // endregion // region $all - /** - * Selects documents where the value of a field is an array that contains all the specified [values]. - * - * ### Example - * - * ```kotlin - * class User( - * val grades: List - * ) - * - * collection.find { - * User::grades containsAll listOf(2, 3, 7) - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/all/) - */ @OptIn(LowLevelApi::class, DangerousMongoApi::class) @KtMongoDsl - infix fun Field>.containsAll(values: Collection) { + override infix fun Field>.containsAll(values: Collection) { accept(ArrayAllExpressionNode(path, values, context)) } - /** - * Selects documents where the value of a field is an array that contains all the specified [values]. - * - * ### Example - * - * ```kotlin - * class User( - * val grades: List - * ) - * - * collection.find { - * User::grades containsAll listOf(2, 3, 7) - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/all/) - */ - @Suppress("INVISIBLE_REFERENCE") - @OptIn(LowLevelApi::class, DangerousMongoApi::class) - @KtMongoDsl - infix fun <@kotlin.internal.OnlyInputTypes V> KProperty1>.containsAll(values: Collection) { - this.field.containsAll(values) - } - @LowLevelApi private class ArrayAllExpressionNode( val path: Path, diff --git a/dsl/src/commonMain/kotlin/expr/FilterOperators.kt b/dsl/src/commonMain/kotlin/expr/FilterOperators.kt new file mode 100644 index 00000000..e55a29c1 --- /dev/null +++ b/dsl/src/commonMain/kotlin/expr/FilterOperators.kt @@ -0,0 +1,2074 @@ +/* + * Copyright (c) 2024, 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.expr + +import opensavvy.ktmongo.bson.DEPRECATED_IN_BSON_SPEC +import opensavvy.ktmongo.bson.types.BsonType +import opensavvy.ktmongo.dsl.DangerousMongoApi +import opensavvy.ktmongo.dsl.KtMongoDsl +import opensavvy.ktmongo.dsl.LowLevelApi +import opensavvy.ktmongo.dsl.expr.common.CompoundExpression +import opensavvy.ktmongo.dsl.path.Field +import opensavvy.ktmongo.dsl.path.FieldDsl +import opensavvy.ktmongo.dsl.path.FieldImpl +import kotlin.jvm.JvmName +import kotlin.reflect.KProperty1 + +/** + * DSL for MongoDB operators that are used as predicates in conditions. + * + * ### Example + * + * This expression type is available in multiple operators, most commonly `find`: + * ```kotlin + * class User( + * val name: String, + * val age: Int, + * ) + * + * collection.find { + * User::age gte 18 + * } + * ``` + * + * ### Beware of arrays! + * + * MongoDB operators do not discriminate between scalars and arrays. + * When an array is encountered, all operators attempt to match on the array itself. + * If the match fails, the operators attempt to match array elements. + * + * It is not possible to mimic this behavior in KtMongo while still keeping type-safety, + * so operators may behave strangely when arrays are encountered. + * + * Note that if the collection corresponds to the declared Kotlin type, + * these situations can never happen, as the Kotlin type system doesn't allow them to. + * + * When developers attempt to perform an operator on the entire array, + * they should use operators as normal: + * ```kotlin + * class User( + * val name: String, + * val favoriteNumbers: List + * ) + * + * collection.find { + * User::favoriteNumbers eq listOf(1, 2) + * } + * ``` + * Developers should use the request above when they want to match a document similar to: + * ```json + * { + * favoriteNumbers: [1, 2] + * } + * ``` + * The following document will NOT match: + * ```json + * { + * favoriteNumbers: [3] + * } + * ``` + * + * However, due to MongoDB's behavior when encountering arrays, it should be noted + * that the following document WILL match: + * ```json + * { + * favoriteNumbers: [ + * [3], + * [1, 2], + * [7, 2] + * ] + * } + * ``` + * + * To execute an operator on one of the elements of an array, see [any]. + * + * ### Operators + * + * Comparison query: + * - [`$eq`][eq] + * - [`$gt`][gt] + * - [`$gte`][gte] + * - [`$in`][isOneOf] + * - [`$lt`][lt] + * - [`$lte`][lte] + * - [`$ne`][ne] + * + * Logical query: + * - [`$and`][and] + * - [`$not`][not] + * - [`$or`][or] + * + * Element query: + * - [`$exists`][exists] + * - [`$type`][hasType] + * + * Array query: + * - [`$elemMatch`][anyObject] + */ +@KtMongoDsl +interface FilterOperators : CompoundExpression, FieldDsl { + + // region $and, $or + + /** + * Performs a logical `AND` operation on one or more expressions, + * and selects the documents that satisfy *all* the expressions. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String?, + * val age: Int, + * ) + * + * collection.findOne { + * and { + * User::name eq "foo" + * User::age eq 18 + * } + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/and/) + * + * @see or Logical `OR` operation. + */ + @KtMongoDsl + fun and(block: FilterOperators.() -> Unit) + + /** + * Performs a logical `OR` operation on one or more expressions, + * and selects the documents that satisfy *at least one* of the expressions. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String?, + * val age: Int, + * ) + * + * collection.find { + * or { + * User::name eq "foo" + * User::name eq "bar" + * User::age eq 18 + * } + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/or/) + * + * @see and Logical `AND` operation. + */ + @KtMongoDsl + fun or(block: FilterOperators.() -> Unit) + + // endregion + // region Predicate access + + /** + * Targets a single field to execute a [targeted predicate][PredicateExpression]. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String?, + * val age: Int, + * ) + * + * collection.find { + * User::name { + * eq("foo") + * } + * } + * ``` + * + * Note that many operators available this way have a convenience function directly in this class to + * shorten this. For this example, see [eq]: + * + * ```kotlin + * collection.find { + * User::name eq "foo" + * } + * ``` + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + operator fun <@kotlin.internal.OnlyInputTypes V> Field.invoke(block: PredicateExpression.() -> Unit) + + /** + * Targets a single field to execute a [targeted predicate][PredicateExpression]. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String?, + * val age: Int, + * ) + * + * collection.find { + * User::name { + * eq("foo") + * } + * } + * ``` + * + * Note that many operators available this way have a convenience function directly in this class to + * shorten this. For this example, see [eq]: + * + * ```kotlin + * collection.find { + * User::name eq "foo" + * } + * ``` + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + operator fun <@kotlin.internal.OnlyInputTypes V> KProperty1.invoke(block: PredicateExpression.() -> Unit) { + this.field.invoke(block) + } + + // endregion + // region $not + + /** + * Performs a logical `NOT` operation on the specified [expression] and selects the + * documents that *do not* match the expression. This includes the elements + * that do not contain the field. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int, + * ) + * + * collection.find { + * User::age not { + * hasType(BsonType.STRING) + * } + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/not/) + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + infix fun <@kotlin.internal.OnlyInputTypes V> Field.not(expression: PredicateExpression.() -> Unit) { + this { not(expression) } + } + + /** + * Performs a logical `NOT` operation on the specified [expression] and selects the + * documents that *do not* match the expression. This includes the elements + * that do not contain the field. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int, + * ) + * + * collection.find { + * User::age not { + * hasType(BsonType.STRING) + * } + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/not/) + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + infix fun <@kotlin.internal.OnlyInputTypes V> KProperty1.not(expression: PredicateExpression.() -> Unit) { + this.field.not(expression) + } + + // endregion + // region $eq + + /** + * Matches documents where the value of a field equals the [value]. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String?, + * val age: Int, + * ) + * + * collection.find { + * User::name eq "foo" + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/eq/) + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + infix fun <@kotlin.internal.OnlyInputTypes V> Field.eq(value: V) { + this { eq(value) } + } + + /** + * Matches documents where the value of a field equals the [value]. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String?, + * val age: Int, + * ) + * + * collection.find { + * User::name eq "foo" + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/eq/) + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + infix fun <@kotlin.internal.OnlyInputTypes V> KProperty1.eq(value: V) { + this.field.eq(value) + } + + /** + * Matches documents where the value of a field equals [value]. + * + * If [value] is `null`, the operator is not added (all documents are matched). + * + * ### Example + * + * This operator is useful to simplify searches when the criteria are optional. + * For example, instead of writing: + * ```kotlin + * collection.find { + * if (criteria.name != null) + * User::name eq criteria.name + * } + * ``` + * this operator can be used instead: + * ```kotlin + * collection.find { + * User::name eqNotNull criteria.name + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/eq/) + * + * @see eq Equality filter. + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + infix fun <@kotlin.internal.OnlyInputTypes V> Field.eqNotNull(value: V?) { + this { eqNotNull(value) } + } + + /** + * Matches documents where the value of a field equals [value]. + * + * If [value] is `null`, the operator is not added (all documents are matched). + * + * ### Example + * + * This operator is useful to simplify searches when the criteria are optional. + * For example, instead of writing: + * ```kotlin + * collection.find { + * if (criteria.name != null) + * User::name eq criteria.name + * } + * ``` + * this operator can be used instead: + * ```kotlin + * collection.find { + * User::name eqNotNull criteria.name + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/eq/) + * + * @see eq Equality filter. + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + infix fun <@kotlin.internal.OnlyInputTypes V> KProperty1.eqNotNull(value: V?) { + this.field.eqNotNull(value) + } + + // endregion + // region $ne + + /** + * Matches documents where the value of a field does not equal the [value]. + * + * The result includes documents which do not contain the specified field. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String?, + * val age: Int, + * ) + * + * collection.find { + * User::name ne "foo" + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/ne/) + * + * @see eq + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + infix fun <@kotlin.internal.OnlyInputTypes V> Field.ne(value: V) { + this { ne(value) } + } + + /** + * Matches documents where the value of a field does not equal the [value]. + * + * The result includes documents which do not contain the specified field. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String?, + * val age: Int, + * ) + * + * collection.find { + * User::name ne "foo" + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/ne/) + * + * @see eq + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + infix fun <@kotlin.internal.OnlyInputTypes V> KProperty1.ne(value: V) { + this.field.ne(value) + } + + // endregion + // region $exists + + /** + * Matches documents that contain the specified field, including + * values where the field value is `null`. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String?, + * val age: Int, + * ) + * + * collection.find { + * User::age.exists() + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/exists/) + * + * @see doesNotExist Opposite. + * @see isNotNull Identical, but does not match elements where the field is `null`. + */ + @KtMongoDsl + fun Field.exists() { + this { exists() } + } + + /** + * Matches documents that contain the specified field, including + * values where the field value is `null`. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String?, + * val age: Int, + * ) + * + * collection.find { + * User::age.exists() + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/exists/) + * + * @see doesNotExist Opposite. + * @see isNotNull Identical, but does not match elements where the field is `null`. + */ + @KtMongoDsl + fun KProperty1.exists() { + this.field.exists() + } + + /** + * Matches documents that do not contain the specified field. + * Documents where the field if `null` are not matched. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String?, + * val age: Int, + * ) + * + * collection.find { + * User::age.doesNotExist() + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/exists/) + * + * @see exists Opposite. + * @see isNull Only matches documents that are specifically `null`. + */ + @KtMongoDsl + fun Field.doesNotExist() { + this { doesNotExist() } + } + + /** + * Matches documents that do not contain the specified field. + * Documents where the field if `null` are not matched. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String?, + * val age: Int, + * ) + * + * collection.find { + * User::age.doesNotExist() + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/exists/) + * + * @see exists Opposite. + * @see isNull Only matches documents that are specifically `null`. + */ + @KtMongoDsl + fun KProperty1.doesNotExist() { + this.field.doesNotExist() + } + + // endregion + // region $type + + /** + * Selects documents where the value of the field is an instance of the specified BSON [type]. + * + * Querying by data type is useful when dealing with highly unstructured data where data types + * are not predictable. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Any, + * ) + * + * collection.find { + * User::age hasType BsonType.STRING + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/type/) + * + * @see isNull Checks if a value has the type [BsonType.Null]. + * @see isUndefined Checks if a value has the type [BsonType.Undefined]. + */ + @KtMongoDsl + infix fun Field.hasType(type: BsonType) { + this { hasType(type) } + } + + /** + * Selects documents where the value of the field is an instance of the specified BSON [type]. + * + * Querying by data type is useful when dealing with highly unstructured data where data types + * are not predictable. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Any, + * ) + * + * collection.find { + * User::age hasType BsonType.STRING + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/type/) + * + * @see isNull Checks if a value has the type [BsonType.Null]. + * @see isUndefined Checks if a value has the type [BsonType.Undefined]. + */ + @KtMongoDsl + infix fun KProperty1.hasType(type: BsonType) { + this.field.hasType(type) + } + + /** + * Selects documents for which the field is `null`. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int?, + * ) + * + * collection.find { + * User::age.isNull() + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/tutorial/query-for-null-fields/#type-check) + * + * @see doesNotExist Checks if the value is not set. + * @see isNotNull Opposite. + */ + @KtMongoDsl + fun Field.isNull() { + this { isNull() } + } + + /** + * Selects documents for which the field is `null`. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int?, + * ) + * + * collection.find { + * User::age.isNull() + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/tutorial/query-for-null-fields/#type-check) + * + * @see doesNotExist Checks if the value is not set. + * @see isNotNull Opposite. + */ + @KtMongoDsl + fun KProperty1.isNull() { + this.field.isNull() + } + + /** + * Selects documents for which the field is not `null`. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int?, + * ) + * + * collection.find { + * User::age.isNotNull() + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/tutorial/query-for-null-fields/#type-check) + * + * @see isNull Opposite. + */ + @KtMongoDsl + fun Field.isNotNull() { + this { isNotNull() } + } + + /** + * Selects documents for which the field is not `null`. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int?, + * ) + * + * collection.find { + * User::age.isNotNull() + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/tutorial/query-for-null-fields/#type-check) + * + * @see isNull Opposite. + */ + @KtMongoDsl + fun KProperty1.isNotNull() { + this.field.isNotNull() + } + + /** + * Selects documents for which the field is `undefined`. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int?, + * ) + * + * collection.find { + * User::age.isUndefined() + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/tutorial/query-for-null-fields/#type-check) + * + * @see isNotUndefined Opposite. + */ + @Suppress("DEPRECATION") + @Deprecated(DEPRECATED_IN_BSON_SPEC) + @KtMongoDsl + fun Field.isUndefined() { + this { isUndefined() } + } + + /** + * Selects documents for which the field is `undefined`. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int?, + * ) + * + * collection.find { + * User::age.isUndefined() + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/tutorial/query-for-null-fields/#type-check) + * + * @see isNotUndefined Opposite. + */ + @Suppress("DEPRECATION") + @Deprecated(DEPRECATED_IN_BSON_SPEC) + @KtMongoDsl + fun KProperty1.isUndefined() { + this.field.isUndefined() + } + + /** + * Selects documents for which the field is not `undefined`. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int?, + * ) + * + * collection.find { + * User::age.isNotUndefined() + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/tutorial/query-for-null-fields/#type-check) + * + * @see isUndefined Opposite. + */ + @Suppress("DEPRECATION") + @Deprecated(DEPRECATED_IN_BSON_SPEC) + @KtMongoDsl + fun Field.isNotUndefined() { + this { isNotUndefined() } + } + + /** + * Selects documents for which the field is not `undefined`. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int?, + * ) + * + * collection.find { + * User::age.isNotUndefined() + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/tutorial/query-for-null-fields/#type-check) + * + * @see isUndefined Opposite. + */ + @Suppress("DEPRECATION") + @Deprecated(DEPRECATED_IN_BSON_SPEC) + @KtMongoDsl + fun KProperty1.isNotUndefined() { + this.field.isNotUndefined() + } + + // endregion + // region $gt, $gte, $lt, $lte + + /** + * Selects documents for which this field has a value strictly greater than [value]. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int?, + * ) + * + * collection.find { + * User::age gt 18 + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/gt/) + * + * @see gtNotNull + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + infix fun <@kotlin.internal.OnlyInputTypes V> Field.gt(value: V) { + this { gt(value) } + } + + /** + * Selects documents for which this field has a value strictly greater than [value]. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int?, + * ) + * + * collection.find { + * User::age gt 18 + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/gt/) + * + * @see gtNotNull + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + infix fun <@kotlin.internal.OnlyInputTypes V> KProperty1.gt(value: V) { + this.field.gt(value) + } + + /** + * Selects documents for which this field has a value strictly greater than [value]. + * + * If [value] is `null`, the operator is not added (all elements are matched). + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int? + * ) + * + * collection.find { + * User::age gtNotNull 10 + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/gt/) + * + * @see gt + * @see eqNotNull Learn more about the 'notNull' variants + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + infix fun <@kotlin.internal.OnlyInputTypes V> Field.gtNotNull(value: V?) { + this { gtNotNull(value) } + } + + /** + * Selects documents for which this field has a value strictly greater than [value]. + * + * If [value] is `null`, the operator is not added (all elements are matched). + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int? + * ) + * + * collection.find { + * User::age gtNotNull 10 + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/gt/) + * + * @see gt + * @see eqNotNull Learn more about the 'notNull' variants + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + infix fun <@kotlin.internal.OnlyInputTypes V> KProperty1.gtNotNull(value: V?) { + this.field.gtNotNull(value) + } + + /** + * Selects documents for which this field has a value greater or equal to [value]. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int?, + * ) + * + * collection.find { + * User::age gte 18 + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/gte/) + * + * @see gteNotNull + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + infix fun <@kotlin.internal.OnlyInputTypes V> Field.gte(value: V) { + this { gte(value) } + } + + /** + * Selects documents for which this field has a value greater or equal to [value]. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int?, + * ) + * + * collection.find { + * User::age gte 18 + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/gte/) + * + * @see gteNotNull + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + infix fun <@kotlin.internal.OnlyInputTypes V> KProperty1.gte(value: V) { + this.field.gte(value) + } + + /** + * Selects documents for which this field has a value greater or equal to [value]. + * + * If [value] is `null`, the operator is not added (all elements are matched). + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int? + * ) + * + * collection.find { + * User::age gteNotNull 10 + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/gte/) + * + * @see gte + * @see eqNotNull Learn more about the 'notNull' variants + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + infix fun <@kotlin.internal.OnlyInputTypes V> Field.gteNotNull(value: V?) { + this { gteNotNull(value) } + } + + /** + * Selects documents for which this field has a value greater or equal to [value]. + * + * If [value] is `null`, the operator is not added (all elements are matched). + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int? + * ) + * + * collection.find { + * User::age gteNotNull 10 + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/gte/) + * + * @see gte + * @see eqNotNull Learn more about the 'notNull' variants + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + infix fun <@kotlin.internal.OnlyInputTypes V> KProperty1.gteNotNull(value: V?) { + this.field.gteNotNull(value) + } + + /** + * Selects documents for which this field has a value strictly lesser than [value]. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int?, + * ) + * + * collection.find { + * User::age lt 18 + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/lt/) + * + * @see ltNotNull + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + infix fun <@kotlin.internal.OnlyInputTypes V> Field.lt(value: V) { + this { lt(value) } + } + + /** + * Selects documents for which this field has a value strictly lesser than [value]. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int?, + * ) + * + * collection.find { + * User::age lt 18 + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/lt/) + * + * @see ltNotNull + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + infix fun <@kotlin.internal.OnlyInputTypes V> KProperty1.lt(value: V) { + this.field.lt(value) + } + + /** + * Selects documents for which this field has a value strictly lesser than [value]. + * + * If [value] is `null`, the operator is not added (all elements are matched). + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int? + * ) + * + * collection.find { + * User::age ltNotNull 10 + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/lt/) + * + * @see lt + * @see eqNotNull Learn more about the 'notNull' variants + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + infix fun <@kotlin.internal.OnlyInputTypes V> Field.ltNotNull(value: V?) { + this { ltNotNull(value) } + } + + /** + * Selects documents for which this field has a value strictly lesser than [value]. + * + * If [value] is `null`, the operator is not added (all elements are matched). + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int? + * ) + * + * collection.find { + * User::age ltNotNull 10 + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/lt/) + * + * @see lt + * @see eqNotNull Learn more about the 'notNull' variants + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + infix fun <@kotlin.internal.OnlyInputTypes V> KProperty1.ltNotNull(value: V?) { + this.field.ltNotNull(value) + } + + /** + * Selects documents for which this field has a value lesser or equal to [value]. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int?, + * ) + * + * collection.find { + * User::age lte 18 + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/lte/) + * + * @see lteNotNull + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + infix fun <@kotlin.internal.OnlyInputTypes V> Field.lte(value: V) { + this { lte(value) } + } + + /** + * Selects documents for which this field has a value lesser or equal to [value]. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int?, + * ) + * + * collection.find { + * User::age lte 18 + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/lte/) + * + * @see lteNotNull + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + infix fun <@kotlin.internal.OnlyInputTypes V> KProperty1.lte(value: V) { + this.field.lte(value) + } + + /** + * Selects documents for which this field has a value lesser or equal to [value]. + * + * If [value] is `null`, the operator is not added (all elements are matched). + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int? + * ) + * + * collection.find { + * User::age lteNotNull 10 + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/lte/) + * + * @see lte + * @see eqNotNull Learn more about the 'notNull' variants + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + infix fun <@kotlin.internal.OnlyInputTypes V> Field.lteNotNull(value: V?) { + this { lteNotNull(value) } + } + + /** + * Selects documents for which this field has a value lesser or equal to [value]. + * + * If [value] is `null`, the operator is not added (all elements are matched). + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int? + * ) + * + * collection.find { + * User::age lteNotNull 10 + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/lte/) + * + * @see lte + * @see eqNotNull Learn more about the 'notNull' variants + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + infix fun <@kotlin.internal.OnlyInputTypes V> KProperty1.lteNotNull(value: V?) { + this.field.lteNotNull(value) + } + + // endregion + // region $in + + /** + * Selects documents for which this field is equal to one of the given [values]. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int?, + * ) + * + * collection.find { + * User::name.isOneOf(listOf("Alfred", "Arthur")) + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/in/) + * + * @see or + * @see eq + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + fun <@kotlin.internal.OnlyInputTypes V> Field.isOneOf(values: List) { + this { isOneOf(values) } + } + + /** + * Selects documents for which this field is equal to one of the given [values]. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int?, + * ) + * + * collection.find { + * User::name.isOneOf(listOf("Alfred", "Arthur")) + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/in/) + * + * @see or + * @see eq + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + fun <@kotlin.internal.OnlyInputTypes V> KProperty1.isOneOf(values: List) { + this.field.isOneOf(values) + } + + /** + * Selects documents for which this field is equal to one of the given [values]. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int?, + * ) + * + * collection.find { + * User::name.isOneOf("Alfred", "Arthur") + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/in/) + * + * @see or + * @see eq + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + fun <@kotlin.internal.OnlyInputTypes V> Field.isOneOf(vararg values: V) { + isOneOf(values.asList()) + } + + /** + * Selects documents for which this field is equal to one of the given [values]. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int?, + * ) + * + * collection.find { + * User::name.isOneOf("Alfred", "Arthur") + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/in/) + * + * @see or + * @see eq + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + fun <@kotlin.internal.OnlyInputTypes V> KProperty1.isOneOf(vararg values: V) { + isOneOf(values.asList()) + } + + // endregion + // region $elemMatch + + /** + * Specify operators on array elements. + * + * ### Example + * + * Find any user who has 12 as one of their favorite numbers. + * + * ```kotlin + * class User( + * val name: String, + * val favoriteNumbers: List + * ) + * + * collection.find { + * User::favoriteNumbers.any eq 12 + * } + * ``` + * + * ### Repeated usages will match different items + * + * Note that if `any` is used multiple times, it may test different items. + * For example, the following request will match the following document: + * ```kotlin + * collection.find { + * User::favoriteNumbers.any gt 2 + * User::favoriteNumbers.any lte 7 + * } + * ``` + * ```json + * { + * "name": "Nicolas", + * "favoriteNumbers": [ 1, 9 ] + * } + * ``` + * Because 1 is less than 7, and 9 is greater than 2, the document is returned. + * + * If you want to apply multiple filters to the same item, use the [any] function. + * + * ### Arrays don't exist in finds! + * + * MongoDB operators do not discriminate between scalars and arrays. + * When an array is encountered, all operators attempt to match on the array itself. + * If the match fails, the operators attempt to match array elements. + * + * It is not possible to mimic this behavior in KtMongo while still keeping type-safety, + * so KtMongo has different operators to filter a collection itself or its elements. + * + * As a consequence, the request: + * ```kotlin + * collection.find { + * User::favoriteNumbers.any eq 5 + * } + * ``` + * will, as expected, match the following document: + * ```json + * { + * favoriteNumbers: [1, 4, 5, 10] + * } + * ``` + * + * It is important to note that it WILL also match this document: + * ```json + * { + * favoriteNumbers: 5 + * } + * ``` + * + * Since this document doesn't conform to the Kotlin declared type `List`, + * it is unlikely that such an element exists, but developers should keep it in mind. + * + * ### External resources + * + * - [Official document](https://www.mongodb.com/docs/manual/tutorial/query-arrays/) + */ + @OptIn(LowLevelApi::class) + @KtMongoDsl + val Field>.any: Field + get() = FieldImpl(path) + + /** + * Specify operators on array elements. + * + * ### Example + * + * Find any user who has 12 as one of their favorite numbers. + * + * ```kotlin + * class User( + * val name: String, + * val favoriteNumbers: List + * ) + * + * collection.find { + * User::favoriteNumbers.any eq 12 + * } + * ``` + * + * ### Repeated usages will match different items + * + * Note that if `any` is used multiple times, it may test different items. + * For example, the following request will match the following document: + * ```kotlin + * collection.find { + * User::favoriteNumbers.any gt 2 + * User::favoriteNumbers.any lte 7 + * } + * ``` + * ```json + * { + * "name": "Nicolas", + * "favoriteNumbers": [ 1, 9 ] + * } + * ``` + * Because 1 is less than 7, and 9 is greater than 2, the document is returned. + * + * If you want to apply multiple filters to the same item, use the [any] function. + * + * ### Arrays don't exist in finds! + * + * MongoDB operators do not discriminate between scalars and arrays. + * When an array is encountered, all operators attempt to match on the array itself. + * If the match fails, the operators attempt to match array elements. + * + * It is not possible to mimic this behavior in KtMongo while still keeping type-safety, + * so KtMongo has different operators to filter a collection itself or its elements. + * + * As a consequence, the request: + * ```kotlin + * collection.find { + * User::favoriteNumbers.any eq 5 + * } + * ``` + * will, as expected, match the following document: + * ```json + * { + * favoriteNumbers: [1, 4, 5, 10] + * } + * ``` + * + * It is important to note that it WILL also match this document: + * ```json + * { + * favoriteNumbers: 5 + * } + * ``` + * + * Since this document doesn't conform to the Kotlin declared type `List`, + * it is unlikely that such an element exists, but developers should keep it in mind. + * + * ### External resources + * + * - [Official document](https://www.mongodb.com/docs/manual/tutorial/query-arrays/) + */ + @OptIn(LowLevelApi::class) + @KtMongoDsl + val KProperty1>.any: Field + get() = FieldImpl(field.path) + + /** + * Combines Kotlin properties into a path usable to point to any item in an array. + * + * ### Example + * + * ```kotlin + * class User( + * val grades: List + * ) + * + * class Grade( + * val name: Int + * ) + * + * collection.find { + * User::grades / Grade::name eq 19 + * } + * ``` + * + * This function is a shorthand for `any`: + * ```kotlin + * collection.find { + * User::grades.any / Gradle::name eq 19 + * } + * ``` + */ + // DO NOT REIMPLEMENT THIS METHOD, THIS IS A HACK TO AVOID PLATFORM DECLARATION CLASHES, + // IT WILL NOT WORK IF YOU USE ANY OTHER IMPLEMENTATION THAN THE DEFAULT ONE. + @KtMongoDsl + @Suppress("INAPPLICABLE_JVM_NAME") + @JvmName("divAny") + operator fun Field>.div(other: Field): Field = + this.any / other + + /** + * Combines Kotlin properties into a path usable to point to any item in an array. + * + * ### Example + * + * ```kotlin + * class User( + * val grades: List + * ) + * + * class Grade( + * val name: Int + * ) + * + * collection.find { + * User::grades / Grade::name eq 19 + * } + * ``` + * + * This function is a shorthand for `any`: + * ```kotlin + * collection.find { + * User::grades.any / Gradle::name eq 19 + * } + * ``` + */ + // DO NOT REIMPLEMENT THIS METHOD, THIS IS A HACK TO AVOID PLATFORM DECLARATION CLASHES, + // IT WILL NOT WORK IF YOU USE ANY OTHER IMPLEMENTATION THAN THE DEFAULT ONE. + @KtMongoDsl + @Suppress("INAPPLICABLE_JVM_NAME") + @JvmName("divAny") + operator fun KProperty1>.div(other: Field): Field = + this.any / other + + /** + * Combines Kotlin properties into a path usable to point to any item in an array. + * + * ### Example + * + * ```kotlin + * class User( + * val grades: List + * ) + * + * class Grade( + * val name: Int + * ) + * + * collection.find { + * User::grades / Grade::name eq 19 + * } + * ``` + * + * This function is a shorthand for `any`: + * ```kotlin + * collection.find { + * User::grades.any / Gradle::name eq 19 + * } + * ``` + */ + // DO NOT REIMPLEMENT THIS METHOD, THIS IS A HACK TO AVOID PLATFORM DECLARATION CLASHES, + // IT WILL NOT WORK IF YOU USE ANY OTHER IMPLEMENTATION THAN THE DEFAULT ONE. + @KtMongoDsl + @Suppress("INAPPLICABLE_JVM_NAME") + @JvmName("divAny") + operator fun Field>.div(other: KProperty1): Field = + this.any / other + + /** + * Combines Kotlin properties into a path usable to point to any item in an array. + * + * ### Example + * + * ```kotlin + * class User( + * val grades: List + * ) + * + * class Grade( + * val name: Int + * ) + * + * collection.find { + * User::grades / Grade::name eq 19 + * } + * ``` + * + * This function is a shorthand for `any`: + * ```kotlin + * collection.find { + * User::grades.any / Gradle::name eq 19 + * } + * ``` + */ + // DO NOT REIMPLEMENT THIS METHOD, THIS IS A HACK TO AVOID PLATFORM DECLARATION CLASHES, + // IT WILL NOT WORK IF YOU USE ANY OTHER IMPLEMENTATION THAN THE DEFAULT ONE. + @KtMongoDsl + @Suppress("INAPPLICABLE_JVM_NAME") + @JvmName("divAny") + operator fun KProperty1>.div(other: KProperty1): Field = + this.any / other + + /** + * Specify multiple operators on a single array element. + * + * ### Example + * + * Find students with a grade between 8 and 10, that may be eligible to perform + * an exam a second time. + * + * ```kotlin + * class Student( + * val name: String, + * val grades: List + * ) + * + * collection.find { + * Student::grades.any { + * gte(8) + * lte(10) + * } + * } + * ``` + * + * The following document will match because the grade 9 is in the interval. + * ```json + * { + * "name": "John", + * "grades": [9, 3] + * } + * ``` + * + * The following document will NOT match, because none of the grades are in the interval. + * ```json + * { + * "name": "Lea", + * "grades": [18, 19] + * } + * ``` + * + * If you want to perform multiple checks on different elements of an array, + * see the [any] property. + * + * This function only allows specifying operators on array elements directly. + * To specify operators on sub-fields of array elements, see [anyObject]. + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/elemMatch/) + */ + @OptIn(LowLevelApi::class, DangerousMongoApi::class) + @KtMongoDsl + fun Field>.any(block: PredicateExpression.() -> Unit) + + /** + * Specify multiple operators on a single array element. + * + * ### Example + * + * Find students with a grade between 8 and 10, that may be eligible to perform + * an exam a second time. + * + * ```kotlin + * class Student( + * val name: String, + * val grades: List + * ) + * + * collection.find { + * Student::grades.any { + * gte(8) + * lte(10) + * } + * } + * ``` + * + * The following document will match because the grade 9 is in the interval. + * ```json + * { + * "name": "John", + * "grades": [9, 3] + * } + * ``` + * + * The following document will NOT match, because none of the grades are in the interval. + * ```json + * { + * "name": "Lea", + * "grades": [18, 19] + * } + * ``` + * + * If you want to perform multiple checks on different elements of an array, + * see the [any] property. + * + * This function only allows specifying operators on array elements directly. + * To specify operators on sub-fields of array elements, see [anyObject]. + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/elemMatch/) + */ + @KtMongoDsl + fun KProperty1>.any(block: PredicateExpression.() -> Unit) { + this.field.any(block) + } + + /** + * Specify multiple operators on fields of a single array element. + * + * ### Example + * + * Find customers who have a pet that is born this month, as they may be eligible for a discount. + * + * ```kotlin + * class Customer( + * val name: String, + * val pets: List, + * ) + * + * class Pet( + * val name: String, + * val birthMonth: Int + * ) + * + * val currentMonth = 3 + * + * collection.find { + * Customer::pets.anyObject { + * Pet::birthMonth gte currentMonth + * Pet::birthMonth lte (currentMonth + 1) + * } + * } + * ``` + * + * The following document will match: + * ```json + * { + * "name": "Fred", + * "pets": [ + * { + * "name": "Arthur", + * "birthMonth": 5 + * }, + * { + * "name": "Gwen", + * "birthMonth": 3 + * } + * ] + * } + * ``` + * because the pet "Gwen" has a matching birth month. + * + * If you want to perform operators on the elements directly (not on their fields), use + * [any] instead. + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/elemMatch/) + */ + @OptIn(LowLevelApi::class, DangerousMongoApi::class) + @KtMongoDsl + fun Field>.anyObject(block: FilterOperators.() -> Unit) + + /** + * Specify multiple operators on fields of a single array element. + * + * ### Example + * + * Find customers who have a pet that is born this month, as they may be eligible for a discount. + * + * ```kotlin + * class Customer( + * val name: String, + * val pets: List, + * ) + * + * class Pet( + * val name: String, + * val birthMonth: Int + * ) + * + * val currentMonth = 3 + * + * collection.find { + * Customer::pets.anyObject { + * Pet::birthMonth gte currentMonth + * Pet::birthMonth lte (currentMonth + 1) + * } + * } + * ``` + * + * The following document will match: + * ```json + * { + * "name": "Fred", + * "pets": [ + * { + * "name": "Arthur", + * "birthMonth": 5 + * }, + * { + * "name": "Gwen", + * "birthMonth": 3 + * } + * ] + * } + * ``` + * because the pet "Gwen" has a matching birth month. + * + * If you want to perform operators on the elements directly (not on their fields), use + * [any] instead. + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/elemMatch/) + */ + @OptIn(LowLevelApi::class, DangerousMongoApi::class) + @KtMongoDsl + fun KProperty1>.anyObject(block: FilterOperators.() -> Unit) { + this.field.anyObject(block) + } + + // endregion + // region $all + + /** + * Selects documents where the value of a field is an array that contains all the specified [values]. + * + * ### Example + * + * ```kotlin + * class User( + * val grades: List + * ) + * + * collection.find { + * User::grades containsAll listOf(2, 3, 7) + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/all/) + */ + @KtMongoDsl + infix fun Field>.containsAll(values: Collection) + + /** + * Selects documents where the value of a field is an array that contains all the specified [values]. + * + * ### Example + * + * ```kotlin + * class User( + * val grades: List + * ) + * + * collection.find { + * User::grades containsAll listOf(2, 3, 7) + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/all/) + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + infix fun <@kotlin.internal.OnlyInputTypes V> KProperty1>.containsAll(values: Collection) { + this.field.containsAll(values) + } + +} diff --git a/dsl/src/commonTest/kotlin/expr/filter/FilterUtils.kt b/dsl/src/commonTest/kotlin/expr/filter/FilterUtils.kt index 3e222d38..138333a0 100644 --- a/dsl/src/commonTest/kotlin/expr/filter/FilterUtils.kt +++ b/dsl/src/commonTest/kotlin/expr/filter/FilterUtils.kt @@ -19,6 +19,7 @@ package opensavvy.ktmongo.dsl.expr.filter import opensavvy.ktmongo.bson.types.ObjectId import opensavvy.ktmongo.dsl.KtMongoDsl import opensavvy.ktmongo.dsl.expr.FilterExpression +import opensavvy.ktmongo.dsl.expr.FilterOperators import opensavvy.ktmongo.dsl.expr.testContext val eq = "\$eq" @@ -51,5 +52,5 @@ class User( ) @KtMongoDsl -fun filter(block: FilterExpression.() -> Unit): String = +fun filter(block: FilterOperators.() -> Unit): String = FilterExpression(testContext()).apply(block).toString() -- 2.51.2 From 1d996f6b2d9a5ca54807594f7d64f9e047636909 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Tue, 5 Nov 2024 23:22:20 +0100 Subject: [PATCH 03/10] feat(dsl): Extract all predicate operators to the PredicateOperators interface --- .../kotlin/expr/FilterExpression.kt | 4 +- .../commonMain/kotlin/expr/FilterOperators.kt | 16 +- .../kotlin/expr/PredicateExpression.kt | 637 +--------------- .../kotlin/expr/PredicateOperators.kt | 696 ++++++++++++++++++ .../kotlin/expr/common/Expression.kt | 4 +- .../kotlin/expr/PredicateExpressionTest.kt | 2 +- 6 files changed, 722 insertions(+), 637 deletions(-) create mode 100644 dsl/src/commonMain/kotlin/expr/PredicateOperators.kt diff --git a/dsl/src/commonMain/kotlin/expr/FilterExpression.kt b/dsl/src/commonMain/kotlin/expr/FilterExpression.kt index e78c6c9e..f1c9ee92 100644 --- a/dsl/src/commonMain/kotlin/expr/FilterExpression.kt +++ b/dsl/src/commonMain/kotlin/expr/FilterExpression.kt @@ -142,7 +142,7 @@ class FilterExpression( @OptIn(LowLevelApi::class, DangerousMongoApi::class) @Suppress("INVISIBLE_REFERENCE") @KtMongoDsl - override operator fun <@kotlin.internal.OnlyInputTypes V> Field.invoke(block: PredicateExpression.() -> Unit) { + override operator fun <@kotlin.internal.OnlyInputTypes V> Field.invoke(block: PredicateOperators.() -> Unit) { accept(PredicateInFilterExpression(path, PredicateExpression(context).apply(block), context)) } @@ -169,7 +169,7 @@ class FilterExpression( @OptIn(LowLevelApi::class, DangerousMongoApi::class) @KtMongoDsl - override fun Field>.any(block: PredicateExpression.() -> Unit) { + override fun Field>.any(block: PredicateOperators.() -> Unit) { accept(ElementMatchExpressionNode(this.path, PredicateExpression(context).apply(block), context)) } diff --git a/dsl/src/commonMain/kotlin/expr/FilterOperators.kt b/dsl/src/commonMain/kotlin/expr/FilterOperators.kt index e55a29c1..8b188866 100644 --- a/dsl/src/commonMain/kotlin/expr/FilterOperators.kt +++ b/dsl/src/commonMain/kotlin/expr/FilterOperators.kt @@ -187,7 +187,7 @@ interface FilterOperators : CompoundExpression, FieldDsl { // region Predicate access /** - * Targets a single field to execute a [targeted predicate][PredicateExpression]. + * Targets a single field to execute a [targeted predicate][PredicateOperators]. * * ### Example * @@ -215,10 +215,10 @@ interface FilterOperators : CompoundExpression, FieldDsl { */ @Suppress("INVISIBLE_REFERENCE") @KtMongoDsl - operator fun <@kotlin.internal.OnlyInputTypes V> Field.invoke(block: PredicateExpression.() -> Unit) + operator fun <@kotlin.internal.OnlyInputTypes V> Field.invoke(block: PredicateOperators.() -> Unit) /** - * Targets a single field to execute a [targeted predicate][PredicateExpression]. + * Targets a single field to execute a [targeted predicate][PredicateOperators]. * * ### Example * @@ -246,7 +246,7 @@ interface FilterOperators : CompoundExpression, FieldDsl { */ @Suppress("INVISIBLE_REFERENCE") @KtMongoDsl - operator fun <@kotlin.internal.OnlyInputTypes V> KProperty1.invoke(block: PredicateExpression.() -> Unit) { + operator fun <@kotlin.internal.OnlyInputTypes V> KProperty1.invoke(block: PredicateOperators.() -> Unit) { this.field.invoke(block) } @@ -279,7 +279,7 @@ interface FilterOperators : CompoundExpression, FieldDsl { */ @Suppress("INVISIBLE_REFERENCE") @KtMongoDsl - infix fun <@kotlin.internal.OnlyInputTypes V> Field.not(expression: PredicateExpression.() -> Unit) { + infix fun <@kotlin.internal.OnlyInputTypes V> Field.not(expression: PredicateOperators.() -> Unit) { this { not(expression) } } @@ -309,7 +309,7 @@ interface FilterOperators : CompoundExpression, FieldDsl { */ @Suppress("INVISIBLE_REFERENCE") @KtMongoDsl - infix fun <@kotlin.internal.OnlyInputTypes V> KProperty1.not(expression: PredicateExpression.() -> Unit) { + infix fun <@kotlin.internal.OnlyInputTypes V> KProperty1.not(expression: PredicateOperators.() -> Unit) { this.field.not(expression) } @@ -1850,7 +1850,7 @@ interface FilterOperators : CompoundExpression, FieldDsl { */ @OptIn(LowLevelApi::class, DangerousMongoApi::class) @KtMongoDsl - fun Field>.any(block: PredicateExpression.() -> Unit) + fun Field>.any(block: PredicateOperators.() -> Unit) /** * Specify multiple operators on a single array element. @@ -1901,7 +1901,7 @@ interface FilterOperators : CompoundExpression, FieldDsl { * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/elemMatch/) */ @KtMongoDsl - fun KProperty1>.any(block: PredicateExpression.() -> Unit) { + fun KProperty1>.any(block: PredicateOperators.() -> Unit) { this.field.any(block) } diff --git a/dsl/src/commonMain/kotlin/expr/PredicateExpression.kt b/dsl/src/commonMain/kotlin/expr/PredicateExpression.kt index 3dbb4de4..7943c896 100644 --- a/dsl/src/commonMain/kotlin/expr/PredicateExpression.kt +++ b/dsl/src/commonMain/kotlin/expr/PredicateExpression.kt @@ -18,7 +18,6 @@ package opensavvy.ktmongo.dsl.expr import opensavvy.ktmongo.bson.BsonContext import opensavvy.ktmongo.bson.BsonFieldWriter -import opensavvy.ktmongo.bson.DEPRECATED_IN_BSON_SPEC import opensavvy.ktmongo.bson.types.BsonType import opensavvy.ktmongo.dsl.DangerousMongoApi import opensavvy.ktmongo.dsl.KtMongoDsl @@ -27,38 +26,12 @@ import opensavvy.ktmongo.dsl.expr.common.AbstractCompoundExpression import opensavvy.ktmongo.dsl.expr.common.AbstractExpression /** - * DSL for MongoDB operators that are used as predicates in conditions in a context where the targeted field is already - * specified. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String?, - * val age: Int, - * ) - * - * collection.find { - * User::name { //(1) - * eq("foo") - * } - * } - * ``` - * - * 1. By referring to a specific property, we obtain a [PredicateExpression] that we can use - * to declare many operators on that property. - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/) - * - * @param T The type on which this predicate applies. - * For example, if the selected field is of type `String`, then `T` is `String`. + * Implementation of [PredicateOperators]. */ @KtMongoDsl class PredicateExpression( context: BsonContext, -) : AbstractCompoundExpression(context) { +) : AbstractCompoundExpression(context), PredicateOperators { // region Low-level operations @@ -68,33 +41,9 @@ class PredicateExpression( // endregion // region $eq - /** - * Matches documents where the value of a field equals the [value]. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String?, - * val age: Int, - * ) - * - * collection.find { - * User::name { - * eq("foo") - * } - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/eq/) - * - * @see FilterExpression.eq Shorthand. - */ @OptIn(LowLevelApi::class, DangerousMongoApi::class) @KtMongoDsl - fun eq(value: T) { + override fun eq(value: T) { accept(EqualityExpressionNode(value, context)) } @@ -109,76 +58,12 @@ class PredicateExpression( } } - /** - * Matches documents where the value of a field equals [value]. - * - * If [value] is `null`, the operator is not added (all documents are matched). - * - * ### Example - * - * This operator is useful to simplify searches when the criteria is optional. - * For example, instead of writing: - * ```kotlin - * collection.find { - * User::name { - * if (criteria.name != null) - * eq(criteria.name) - * } - * } - * ``` - * this operator can be used instead: - * ```kotlin - * collection.find { - * User::name { - * eqNotNull(criteria.name) - * } - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/eq/) - * - * @see FilterExpression.eqNotNull Shorthand. - * @see eq Equality filter. - */ - @KtMongoDsl - fun eqNotNull(value: T?) { - if (value != null) eq(value) - } - // endregion // region $ne - /** - * Matches documents where the value of a field does not equal the [value]. - * - * The result includes documents which do not contain the specified field. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String?, - * val age: Int, - * ) - * - * collection.find { - * User::name { - * ne("foo") - * } - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/ne/) - * - * @see FilterExpression.ne Shorthand. - */ @OptIn(LowLevelApi::class, DangerousMongoApi::class) @KtMongoDsl - fun ne(value: T) { + override fun ne(value: T) { accept(InequalityExpressionNode(value, context)) } @@ -196,36 +81,9 @@ class PredicateExpression( // endregion // region $exists - /** - * Matches documents that contain the specified field, including - * values where the field value is `null`. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String?, - * val age: Int, - * ) - * - * collection.find { - * User::name { - * exists() - * } - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/exists/) - * - * @see FilterExpression.exists Shorthand. - * @see doesNotExist Opposite. - * @see isNotNull Identical, but does not match elements where the field is `null`. - */ @OptIn(LowLevelApi::class, DangerousMongoApi::class) @KtMongoDsl - fun exists() { + override fun exists() { accept(ExistsPredicateExpressionNode(true, context)) } @@ -240,74 +98,18 @@ class PredicateExpression( } } - /** - * Matches documents that do not contain the specified field. - * Documents where the field if `null` are counted as existing. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String?, - * val age: Int, - * ) - * - * collection.find { - * User::name { - * doesNotExist() - * } - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/exists/) - * - * @see FilterExpression.doesNotExist Shorthand. - * @see exists Opposite. - * @see isNull Only matches elements that are specifically `null`. - */ @OptIn(LowLevelApi::class, DangerousMongoApi::class) @KtMongoDsl - fun doesNotExist() { + override fun doesNotExist() { accept(ExistsPredicateExpressionNode(false, context)) } // endregion // region $type - /** - * Selects documents where the value of the field is an instance of the specified BSON [type]. - * - * Querying by data type is useful when dealing with highly unstructured data where data types - * are not predictable. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Any, - * ) - * - * collection.find { - * User::age { - * type(BsonType.STRING) - * } - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/type/) - * - * @see FilterExpression.hasType Shorthand. - * @see isNull Checks if a value has the type [BsonType.NULL]. - * @see isUndefined Checks if a value has the type [BsonType.UNDEFINED]. - */ @OptIn(LowLevelApi::class, DangerousMongoApi::class) @KtMongoDsl - fun hasType(type: BsonType) { + override fun hasType(type: BsonType) { accept(TypePredicateExpressionNode(type, context)) } @@ -325,37 +127,9 @@ class PredicateExpression( // endregion // region $not - /** - * Performs a logical `NOT` operation on the specified [expression] and selects the - * documents that *do not* match the expression. This includes the elements - * that do not contain the field. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int, - * ) - * - * collection.find { - * User::age { - * not { - * hasType(BsonType.STRING) - * } - * } - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/not/) - * - * @see FilterExpression.not Shorthand. - */ @OptIn(LowLevelApi::class, DangerousMongoApi::class) @KtMongoDsl - fun not(expression: PredicateExpression.() -> Unit) { + override fun not(expression: PredicateOperators.() -> Unit) { accept(NotPredicateExpressionNode(PredicateExpression(context).apply(expression), context)) } @@ -379,151 +153,12 @@ class PredicateExpression( } } - // endregion - // region Nullability - - /** - * Selects documents for which the field is `null`. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int?, - * ) - * - * collection.find { - * User::age { isNull() } - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/tutorial/query-for-null-fields/#type-check) - * - * @see FilterExpression.isNull Shorthand. - * @see doesNotExist Checks if the value is not set. - * @see isNotNull Opposite. - */ - @KtMongoDsl - fun isNull() = - hasType(BsonType.Null) - - /** - * Selects documents for which the field is not `null`. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int?, - * ) - * - * collection.find { - * User::age { isNotNull() } - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/tutorial/query-for-null-fields/#type-check) - * - * @see FilterExpression.isNotNull Shorthand. - * @see isNull Opposite. - */ - @KtMongoDsl - fun isNotNull() = - not { isNull() } - - /** - * Selects documents for which the field is `undefined`. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int?, - * ) - * - * collection.find { - * User::age { isUndefined() } - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/tutorial/query-for-null-fields/#type-check) - * - * @see FilterExpression.isUndefined Shorthand. - * @see isNotUndefined Opposite. - */ - @KtMongoDsl - @Suppress("DeprecatedCallableAddReplaceWith", "DEPRECATION") - @Deprecated(DEPRECATED_IN_BSON_SPEC) - fun isUndefined() = - hasType(BsonType.Undefined) - - /** - * Selects documents for which the field is not `undefined`. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int?, - * ) - * - * collection.find { - * User::age { isNotUndefined() } - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/tutorial/query-for-null-fields/#type-check) - * - * @see FilterExpression.isNotUndefined Shorthand. - * @see isUndefined Opposite. - */ - @KtMongoDsl - @Suppress("DeprecatedCallableAddReplaceWith", "DEPRECATION") - @Deprecated(DEPRECATED_IN_BSON_SPEC) - fun isNotUndefined() = - not { isUndefined() } - // endregion // region $gt, $gte, $lt, $lte - /** - * Selects documents for which this field has a value strictly greater than [value]. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int?, - * ) - * - * collection.find { - * User::age { gt(18) } - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/gt/) - * - * @see FilterExpression.gt - * @see gtNotNull - */ @OptIn(LowLevelApi::class, DangerousMongoApi::class) @KtMongoDsl - fun gt(value: T) { + override fun gt(value: T) { accept(GtPredicateExpressionNode(value, context)) } @@ -539,63 +174,9 @@ class PredicateExpression( } } - /** - * Selects documents for which this field has a value strictly greater than [value]. - * - * If [value] is `null`, the operator is not added (all elements are matched). - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int? - * ) - * - * collection.find { - * User::age { gtNotNull(18) } - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/gt/) - * - * @see FilterExpression.gtNotNull - * @see eqNotNull Learn more about the 'notNull' variants - */ - @KtMongoDsl - fun gtNotNull(value: T?) { - if (value != null) - gt(value) - } - - /** - * Selects documents for which this field has a value greater or equal to [value]. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int?, - * ) - * - * collection.find { - * User::age { gte(18) } - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/gte/) - * - * @see FilterExpression.gte - * @see gteNotNull - */ @OptIn(LowLevelApi::class, DangerousMongoApi::class) @KtMongoDsl - fun gte(value: T) { + override fun gte(value: T) { accept(GtePredicateExpressionNode(value, context)) } @@ -611,63 +192,9 @@ class PredicateExpression( } } - /** - * Selects documents for which this field has a value greater or equal to [value]. - * - * If [value] is `null`, the operator is not added (all elements are matched). - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int? - * ) - * - * collection.find { - * User::age { gteNotNull(18) } - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/gte/) - * - * @see FilterExpression.gteNotNull - * @see eqNotNull Learn more about the 'notNull' variants - */ - @KtMongoDsl - fun gteNotNull(value: T?) { - if (value != null) - gte(value) - } - - /** - * Selects documents for which this field has a value strictly lesser than [value]. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int?, - * ) - * - * collection.find { - * User::age { lt(18) } - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/lt/) - * - * @see FilterExpression.lt - * @see ltNotNull - */ @OptIn(LowLevelApi::class, DangerousMongoApi::class) @KtMongoDsl - fun lt(value: T) { + override fun lt(value: T) { accept(LtPredicateExpressionNode(value, context)) } @@ -683,63 +210,9 @@ class PredicateExpression( } } - /** - * Selects documents for which this field has a value strictly lesser than [value]. - * - * If [value] is `null`, the operator is not added (all elements are matched). - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int? - * ) - * - * collection.find { - * User::age { ltNotNull(18) } - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/lt/) - * - * @see FilterExpression.ltNotNull - * @see lqNotNull Learn more about the 'notNull' variants - */ - @KtMongoDsl - fun ltNotNull(value: T?) { - if (value != null) - lt(value) - } - - /** - * Selects documents for which this field has a value lesser or equal to [value]. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int?, - * ) - * - * collection.find { - * User::age { lte(18) } - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/lte/) - * - * @see FilterExpression.lte - * @see lteNotNull - */ @OptIn(LowLevelApi::class, DangerousMongoApi::class) @KtMongoDsl - fun lte(value: T) { + override fun lte(value: T) { accept(LtePredicateExpressionNode(value, context)) } @@ -755,67 +228,12 @@ class PredicateExpression( } } - /** - * Selects documents for which this field has a value lesser or equal to [value]. - * - * If [value] is `null`, the operator is not added (all elements are matched). - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int? - * ) - * - * collection.find { - * User::age { lteNotNull(18) } - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/lte/) - * - * @see FilterExpression.lteNotNull - * @see eqNotNull Learn more about the 'notNull' variants - */ - @KtMongoDsl - fun lteNotNull(value: T?) { - if (value != null) - lte(value) - } - // endregion // region $in - /** - * Selects documents for which this field is equal to one of the given [values]. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int?, - * ) - * - * collection.find { - * User::name { - * isOneOf(listOf("Alfred", "Arthur")) - * } - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/in/) - * - * @see FilterExpression.isOneOf - */ @OptIn(LowLevelApi::class, DangerousMongoApi::class) @KtMongoDsl - fun isOneOf(values: Collection) { + override fun isOneOf(values: Collection) { accept(OneOfPredicateExpressionNode(values, context)) } @@ -834,34 +252,5 @@ class PredicateExpression( } } - /** - * Selects documents for which this field is equal to one of the given [values]. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int?, - * ) - * - * collection.find { - * User::name { - * isOneOf("Alfred", "Arthur") - * } - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/in/) - * - * @see FilterExpression.isOneOf - */ - @KtMongoDsl - fun isOneOf(vararg values: T) { - isOneOf(values.asList()) - } - // endregion } diff --git a/dsl/src/commonMain/kotlin/expr/PredicateOperators.kt b/dsl/src/commonMain/kotlin/expr/PredicateOperators.kt new file mode 100644 index 00000000..d61c41d3 --- /dev/null +++ b/dsl/src/commonMain/kotlin/expr/PredicateOperators.kt @@ -0,0 +1,696 @@ +/* + * Copyright (c) 2024, 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.expr + +import opensavvy.ktmongo.bson.DEPRECATED_IN_BSON_SPEC +import opensavvy.ktmongo.bson.types.BsonType +import opensavvy.ktmongo.dsl.KtMongoDsl +import opensavvy.ktmongo.dsl.expr.common.CompoundExpression +import opensavvy.ktmongo.dsl.path.FieldDsl + +/** + * DSL for MongoDB operators that are used as predicates in conditions in a context where the targeted field is already + * specified. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String?, + * val age: Int, + * ) + * + * collection.find { + * User::name { //(1) + * eq("foo") + * } + * } + * ``` + * + * 1. By referring to a specific property, we obtain a [PredicateOperators] that we can use + * to declare many operators on that property. + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/) + * + * @param T The type on which this predicate applies. + * For example, if the selected field is of type `String`, then `T` is `String`. + */ +@KtMongoDsl +interface PredicateOperators : CompoundExpression, FieldDsl { + + // region $eq + + /** + * Matches documents where the value of a field equals the [value]. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String?, + * val age: Int, + * ) + * + * collection.find { + * User::name { + * eq("foo") + * } + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/eq/) + * + * @see FilterExpression.eq Shorthand. + */ + @KtMongoDsl + fun eq(value: T) + + /** + * Matches documents where the value of a field equals [value]. + * + * If [value] is `null`, the operator is not added (all documents are matched). + * + * ### Example + * + * This operator is useful to simplify searches when the criteria is optional. + * For example, instead of writing: + * ```kotlin + * collection.find { + * User::name { + * if (criteria.name != null) + * eq(criteria.name) + * } + * } + * ``` + * this operator can be used instead: + * ```kotlin + * collection.find { + * User::name { + * eqNotNull(criteria.name) + * } + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/eq/) + * + * @see FilterExpression.eqNotNull Shorthand. + * @see eq Equality filter. + */ + @KtMongoDsl + fun eqNotNull(value: T?) { + if (value != null) eq(value) + } + + // endregion + // region $ne + + /** + * Matches documents where the value of a field does not equal the [value]. + * + * The result includes documents which do not contain the specified field. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String?, + * val age: Int, + * ) + * + * collection.find { + * User::name { + * ne("foo") + * } + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/ne/) + * + * @see FilterExpression.ne Shorthand. + */ + @KtMongoDsl + fun ne(value: T) + + // endregion + // region $exists + + /** + * Matches documents that contain the specified field, including + * values where the field value is `null`. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String?, + * val age: Int, + * ) + * + * collection.find { + * User::name { + * exists() + * } + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/exists/) + * + * @see FilterExpression.exists Shorthand. + * @see doesNotExist Opposite. + * @see isNotNull Identical, but does not match elements where the field is `null`. + */ + @KtMongoDsl + fun exists() + + /** + * Matches documents that do not contain the specified field. + * Documents where the field if `null` are counted as existing. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String?, + * val age: Int, + * ) + * + * collection.find { + * User::name { + * doesNotExist() + * } + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/exists/) + * + * @see FilterExpression.doesNotExist Shorthand. + * @see exists Opposite. + * @see isNull Only matches elements that are specifically `null`. + */ + @KtMongoDsl + fun doesNotExist() + + // endregion + // region $type + + /** + * Selects documents where the value of the field is an instance of the specified BSON [type]. + * + * Querying by data type is useful when dealing with highly unstructured data where data types + * are not predictable. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Any, + * ) + * + * collection.find { + * User::age { + * type(BsonType.STRING) + * } + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/type/) + * + * @see FilterExpression.hasType Shorthand. + * @see isNull Checks if a value has the type [BsonType.Null]. + * @see isUndefined Checks if a value has the type [BsonType.Undefined]. + */ + @KtMongoDsl + fun hasType(type: BsonType) + + // endregion + // region $not + + /** + * Performs a logical `NOT` operation on the specified [expression] and selects the + * documents that *do not* match the expression. This includes the elements + * that do not contain the field. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int, + * ) + * + * collection.find { + * User::age { + * not { + * hasType(BsonType.STRING) + * } + * } + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/not/) + * + * @see FilterExpression.not Shorthand. + */ + @KtMongoDsl + fun not(expression: PredicateOperators.() -> Unit) + + // endregion + // region Nullability + + /** + * Selects documents for which the field is `null`. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int?, + * ) + * + * collection.find { + * User::age { isNull() } + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/tutorial/query-for-null-fields/#type-check) + * + * @see FilterExpression.isNull Shorthand. + * @see doesNotExist Checks if the value is not set. + * @see isNotNull Opposite. + */ + @KtMongoDsl + fun isNull() = + hasType(BsonType.Null) + + /** + * Selects documents for which the field is not `null`. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int?, + * ) + * + * collection.find { + * User::age { isNotNull() } + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/tutorial/query-for-null-fields/#type-check) + * + * @see FilterExpression.isNotNull Shorthand. + * @see isNull Opposite. + */ + @KtMongoDsl + fun isNotNull() = + not { isNull() } + + /** + * Selects documents for which the field is `undefined`. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int?, + * ) + * + * collection.find { + * User::age { isUndefined() } + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/tutorial/query-for-null-fields/#type-check) + * + * @see FilterExpression.isUndefined Shorthand. + * @see isNotUndefined Opposite. + */ + @KtMongoDsl + @Suppress("DeprecatedCallableAddReplaceWith", "DEPRECATION") + @Deprecated(DEPRECATED_IN_BSON_SPEC) + fun isUndefined() = + hasType(BsonType.Undefined) + + /** + * Selects documents for which the field is not `undefined`. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int?, + * ) + * + * collection.find { + * User::age { isNotUndefined() } + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/tutorial/query-for-null-fields/#type-check) + * + * @see FilterExpression.isNotUndefined Shorthand. + * @see isUndefined Opposite. + */ + @KtMongoDsl + @Suppress("DeprecatedCallableAddReplaceWith", "DEPRECATION") + @Deprecated(DEPRECATED_IN_BSON_SPEC) + fun isNotUndefined() = + not { isUndefined() } + + // endregion + // region $gt, $gte, $lt, $lte + + /** + * Selects documents for which this field has a value strictly greater than [value]. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int?, + * ) + * + * collection.find { + * User::age { gt(18) } + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/gt/) + * + * @see FilterExpression.gt + * @see gtNotNull + */ + @KtMongoDsl + fun gt(value: T) + + /** + * Selects documents for which this field has a value strictly greater than [value]. + * + * If [value] is `null`, the operator is not added (all elements are matched). + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int? + * ) + * + * collection.find { + * User::age { gtNotNull(18) } + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/gt/) + * + * @see FilterExpression.gtNotNull + * @see eqNotNull Learn more about the 'notNull' variants + */ + @KtMongoDsl + fun gtNotNull(value: T?) { + if (value != null) + gt(value) + } + + /** + * Selects documents for which this field has a value greater or equal to [value]. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int?, + * ) + * + * collection.find { + * User::age { gte(18) } + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/gte/) + * + * @see FilterExpression.gte + * @see gteNotNull + */ + @KtMongoDsl + fun gte(value: T) + + /** + * Selects documents for which this field has a value greater or equal to [value]. + * + * If [value] is `null`, the operator is not added (all elements are matched). + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int? + * ) + * + * collection.find { + * User::age { gteNotNull(18) } + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/gte/) + * + * @see FilterExpression.gteNotNull + * @see eqNotNull Learn more about the 'notNull' variants + */ + @KtMongoDsl + fun gteNotNull(value: T?) { + if (value != null) + gte(value) + } + + /** + * Selects documents for which this field has a value strictly lesser than [value]. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int?, + * ) + * + * collection.find { + * User::age { lt(18) } + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/lt/) + * + * @see FilterExpression.lt + * @see ltNotNull + */ + @KtMongoDsl + fun lt(value: T) + + /** + * Selects documents for which this field has a value strictly lesser than [value]. + * + * If [value] is `null`, the operator is not added (all elements are matched). + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int? + * ) + * + * collection.find { + * User::age { ltNotNull(18) } + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/lt/) + * + * @see FilterExpression.ltNotNull + * @see lqNotNull Learn more about the 'notNull' variants + */ + @KtMongoDsl + fun ltNotNull(value: T?) { + if (value != null) + lt(value) + } + + /** + * Selects documents for which this field has a value lesser or equal to [value]. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int?, + * ) + * + * collection.find { + * User::age { lte(18) } + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/lte/) + * + * @see FilterExpression.lte + * @see lteNotNull + */ + @KtMongoDsl + fun lte(value: T) + + /** + * Selects documents for which this field has a value lesser or equal to [value]. + * + * If [value] is `null`, the operator is not added (all elements are matched). + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int? + * ) + * + * collection.find { + * User::age { lteNotNull(18) } + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/lte/) + * + * @see FilterExpression.lteNotNull + * @see eqNotNull Learn more about the 'notNull' variants + */ + @KtMongoDsl + fun lteNotNull(value: T?) { + if (value != null) + lte(value) + } + + // endregion + // region $in + + /** + * Selects documents for which this field is equal to one of the given [values]. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int?, + * ) + * + * collection.find { + * User::name { + * isOneOf(listOf("Alfred", "Arthur")) + * } + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/in/) + * + * @see FilterExpression.isOneOf + */ + @KtMongoDsl + fun isOneOf(values: Collection) + + /** + * Selects documents for which this field is equal to one of the given [values]. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int?, + * ) + * + * collection.find { + * User::name { + * isOneOf("Alfred", "Arthur") + * } + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/in/) + * + * @see FilterExpression.isOneOf + */ + @KtMongoDsl + fun isOneOf(vararg values: T) { + isOneOf(values.asList()) + } + + // endregion + +} diff --git a/dsl/src/commonMain/kotlin/expr/common/Expression.kt b/dsl/src/commonMain/kotlin/expr/common/Expression.kt index ded8ca05..71cbb2a8 100644 --- a/dsl/src/commonMain/kotlin/expr/common/Expression.kt +++ b/dsl/src/commonMain/kotlin/expr/common/Expression.kt @@ -20,7 +20,7 @@ import opensavvy.ktmongo.bson.BsonContext import opensavvy.ktmongo.bson.BsonFieldWriter import opensavvy.ktmongo.bson.buildBsonDocument import opensavvy.ktmongo.dsl.LowLevelApi -import opensavvy.ktmongo.dsl.expr.PredicateExpression +import opensavvy.ktmongo.dsl.expr.PredicateOperators import opensavvy.ktmongo.dsl.tree.AbstractNode import opensavvy.ktmongo.dsl.tree.Node @@ -124,7 +124,7 @@ interface Expression : Node { * } * ``` * - * Of course, the operator described above is already made available: [PredicateExpression.hasType]. + * Of course, the operator described above is already made available: [PredicateOperators.hasType]. * * **Note that if your operator accepts a variable number of sub-expressions (e.g. `$and`), you must ensure that it works for any * number of expressions, including 1 and 0.** See [simplify]. diff --git a/dsl/src/commonTest/kotlin/expr/PredicateExpressionTest.kt b/dsl/src/commonTest/kotlin/expr/PredicateExpressionTest.kt index e95d5a78..d95851da 100644 --- a/dsl/src/commonTest/kotlin/expr/PredicateExpressionTest.kt +++ b/dsl/src/commonTest/kotlin/expr/PredicateExpressionTest.kt @@ -26,7 +26,7 @@ import opensavvy.prepared.runner.kotest.PreparedSpec class PredicateExpressionTest : PreparedSpec({ @KtMongoDsl - fun predicate(block: PredicateExpression.() -> Unit): String { + fun predicate(block: PredicateOperators.() -> Unit): String { val expr = PredicateExpression(testContext()) .apply(block) -- 2.51.2 From 89f98305b54519fa69130916d5d9dd1847be5759 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Tue, 5 Nov 2024 23:34:17 +0100 Subject: [PATCH 04/10] feat(dsl): Extract all update operators to the UpdateOperators and UpsertOperators interfaces --- dsl/README.md | 3 +- .../kotlin/expr/UpdateExpression.kt | 394 +-------------- .../commonMain/kotlin/expr/UpdateOperators.kt | 452 ++++++++++++++++++ .../kotlin/expr/update/FieldUpdateTest.kt | 6 +- .../kotlin/expr/update/UpdateUtils.kt | 10 +- 5 files changed, 470 insertions(+), 395 deletions(-) create mode 100644 dsl/src/commonMain/kotlin/expr/UpdateOperators.kt diff --git a/dsl/README.md b/dsl/README.md index 824b0ede..207ecb65 100644 --- a/dsl/README.md +++ b/dsl/README.md @@ -29,7 +29,8 @@ Operators are organized by the context in which they are available in. For examp Instances of these classes are usually provided by the driver as part of its functions. - [Filter operators][opensavvy.ktmongo.dsl.expr.FilterOperators] -- [Update operators][opensavvy.ktmongo.dsl.expr.UpdateExpression] +- [Update operators][opensavvy.ktmongo.dsl.expr.UpdateOperators] +- [Upsert operators][opensavvy.ktmongo.dsl.expr.UpsertOperators] To create a custom operator (for example because it isn't part of the library yet), see [AbstractExpression][opensavvy.ktmongo.dsl.expr.common.AbstractExpression]. diff --git a/dsl/src/commonMain/kotlin/expr/UpdateExpression.kt b/dsl/src/commonMain/kotlin/expr/UpdateExpression.kt index d333da76..d8d69424 100644 --- a/dsl/src/commonMain/kotlin/expr/UpdateExpression.kt +++ b/dsl/src/commonMain/kotlin/expr/UpdateExpression.kt @@ -29,52 +29,15 @@ import opensavvy.ktmongo.dsl.path.Field import opensavvy.ktmongo.dsl.path.FieldDsl import opensavvy.ktmongo.dsl.path.Path import kotlin.reflect.KClass -import kotlin.reflect.KProperty1 /** - * DSL for MongoDB operators that are used to update existing values (does *not* include aggregation operators). - * - * ### Example - * - * This expression type is available on multiple operations, most commonly `update`: - * ```kotlin - * class User( - * val name: String, - * val age: Int, - * ) - * - * collection.update( - * filter = { - * User::name eq "Bob" - * }, - * update = { - * User::age set 18 - * } - * ) - * ``` - * - * ### Operators - * - * Fields: - * - [`$inc`][inc] - * - [`$rename`][renameTo] - * - [`$set`][set] - * - [`$setOnInsert`][setOnInsert] - * - [`$unset`][unset] - * - * Arrays: - * - [`$[]`][FieldDsl.get] - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/) - * - * @see FilterExpression Filters + * Implementation of [UpdateOperators]. */ @KtMongoDsl class UpdateExpression( context: BsonContext, ) : AbstractCompoundExpression(context), + UpsertOperators, FieldDsl { // region Low-level operations @@ -121,68 +84,13 @@ class UpdateExpression( // endregion // region $set - /** - * Replaces the value of a field with the specified [value]. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String?, - * val age: Int, - * ) - * - * collection.filter { - * User::name eq "foo" - * }.updateMany { - * User::age set 18 - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/set/) - * - * @see setOnInsert Only set if a new document is created. - */ @OptIn(LowLevelApi::class, DangerousMongoApi::class) @Suppress("INVISIBLE_REFERENCE") @KtMongoDsl - infix fun <@kotlin.internal.OnlyInputTypes V> Field.set(value: V) { + override infix fun <@kotlin.internal.OnlyInputTypes V> Field.set(value: V) { accept(SetExpressionNode(listOf(this.path to value), context)) } - /** - * Replaces the value of a field with the specified [value]. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String?, - * val age: Int, - * ) - * - * collection.filter { - * User::name eq "foo" - * }.updateMany { - * User::age set 18 - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/set/) - * - * @see setOnInsert Only set if a new document is created. - */ - @OptIn(LowLevelApi::class, DangerousMongoApi::class) - @Suppress("INVISIBLE_REFERENCE") - @KtMongoDsl - infix fun <@kotlin.internal.OnlyInputTypes V> KProperty1.set(value: V) { - this.field.set(value) - } - @LowLevelApi private class SetExpressionNode( val mappings: List>, @@ -204,78 +112,13 @@ class UpdateExpression( // endregion // region $setOnInsert - /** - * If an upsert operation results in an insert of a document, - * then this operator assigns the specified [value] to the field. - * If the update operation does not result in an insert, this operator does nothing. - * - * If used in an update operation that isn't an upsert, no document can be inserted, - * and thus this operator never does anything. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String?, - * val age: Int, - * ) - * - * collection.filter { - * User::name eq "foo" - * }.upsertOne { - * User::age setOnInsert 18 - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/setOnInsert/) - * - * @see set Always set the value. - */ @OptIn(LowLevelApi::class, DangerousMongoApi::class) @Suppress("INVISIBLE_REFERENCE") @KtMongoDsl - infix fun <@kotlin.internal.OnlyInputTypes V> Field.setOnInsert(value: V) { + override infix fun <@kotlin.internal.OnlyInputTypes V> Field.setOnInsert(value: V) { accept(SetOnInsertExpressionNode(listOf(this.path to value), context)) } - /** - * If an upsert operation results in an insert of a document, - * then this operator assigns the specified [value] to the field. - * If the update operation does not result in an insert, this operator does nothing. - * - * If used in an update operation that isn't an upsert, no document can be inserted, - * and thus this operator never does anything. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String?, - * val age: Int, - * ) - * - * collection.filter { - * User::name eq "foo" - * }.upsertOne { - * User::age setOnInsert 18 - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/setOnInsert/) - * - * @see set Always set the value. - */ - @OptIn(LowLevelApi::class, DangerousMongoApi::class) - @Suppress("INVISIBLE_REFERENCE") - @KtMongoDsl - infix fun <@kotlin.internal.OnlyInputTypes V> KProperty1.setOnInsert(value: V) { - this.field.setOnInsert(value) - } - @LowLevelApi private class SetOnInsertExpressionNode( val mappings: List>, @@ -296,76 +139,13 @@ class UpdateExpression( // endregion // region $inc - /** - * 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 inc 1 - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/inc/) - */ @OptIn(LowLevelApi::class, DangerousMongoApi::class) @Suppress("INVISIBLE_REFERENCE") @KtMongoDsl - infix fun <@kotlin.internal.OnlyInputTypes V : Number> Field.inc(amount: V) { + override infix fun <@kotlin.internal.OnlyInputTypes V : Number> Field.inc(amount: V) { accept(IncrementExpressionNode(listOf(this.path to amount), context)) } - /** - * 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 inc 1 - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/inc/) - */ - @OptIn(LowLevelApi::class, DangerousMongoApi::class) - @Suppress("INVISIBLE_REFERENCE") - @KtMongoDsl - infix fun <@kotlin.internal.OnlyInputTypes V : Number> KProperty1.inc(amount: V) { - this.field.inc(amount) - } - @LowLevelApi private class IncrementExpressionNode( val mappings: List>, @@ -386,68 +166,13 @@ class UpdateExpression( // endregion // region $unset - /** - * Deletes a field. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int, - * val alive: Boolean, - * ) - * - * collection.filter { - * User::name eq "Luke Skywalker" - * }.updateOne { - * User::age.unset() - * User::alive set false - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/unset/) - */ @OptIn(LowLevelApi::class, DangerousMongoApi::class) @Suppress("INVISIBLE_REFERENCE") @KtMongoDsl - fun <@kotlin.internal.OnlyInputTypes V> Field.unset() { + override fun <@kotlin.internal.OnlyInputTypes V> Field.unset() { accept(UnsetExpressionNode(listOf(this.path), context)) } - /** - * Deletes a field. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int, - * val alive: Boolean, - * ) - * - * collection.filter { - * User::name eq "Luke Skywalker" - * }.updateOne { - * User::age.unset() - * User::alive set false - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/unset/) - */ - @OptIn(LowLevelApi::class, DangerousMongoApi::class) - @Suppress("INVISIBLE_REFERENCE") - @KtMongoDsl - fun <@kotlin.internal.OnlyInputTypes V> KProperty1.unset() { - this.field.unset() - } - @LowLevelApi private class UnsetExpressionNode( val fields: List, @@ -468,118 +193,13 @@ class UpdateExpression( // endregion // region $rename - /** - * Renames a field. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int, - * val ageOld: Int, - * ) - * - * collection.updateMany { - * User::ageOld renameTo User::age - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/rename/) - */ @OptIn(LowLevelApi::class, DangerousMongoApi::class) @Suppress("INVISIBLE_REFERENCE") @KtMongoDsl - infix fun <@kotlin.internal.OnlyInputTypes V> Field.renameTo(newName: Field) { + override infix fun <@kotlin.internal.OnlyInputTypes V> Field.renameTo(newName: Field) { accept(RenameExpressionNode(listOf(this.path to newName.path), context)) } - /** - * Renames a field. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int, - * val ageOld: Int, - * ) - * - * collection.updateMany { - * User::ageOld renameTo User::age - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/rename/) - */ - @OptIn(LowLevelApi::class, DangerousMongoApi::class) - @Suppress("INVISIBLE_REFERENCE") - @KtMongoDsl - infix fun <@kotlin.internal.OnlyInputTypes V> KProperty1.renameTo(newName: Field) { - this.field.renameTo(newName) - } - - /** - * Renames a field. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int, - * val ageOld: Int, - * ) - * - * collection.updateMany { - * User::ageOld renameTo User::age - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/rename/) - */ - @OptIn(LowLevelApi::class, DangerousMongoApi::class) - @Suppress("INVISIBLE_REFERENCE") - @KtMongoDsl - infix fun <@kotlin.internal.OnlyInputTypes V> Field.renameTo(newName: KProperty1) { - this.renameTo(newName.field) - } - - /** - * Renames a field. - * - * ### Example - * - * ```kotlin - * class User( - * val name: String, - * val age: Int, - * val ageOld: Int, - * ) - * - * collection.updateMany { - * User::ageOld renameTo User::age - * } - * ``` - * - * ### External resources - * - * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/rename/) - */ - @OptIn(LowLevelApi::class, DangerousMongoApi::class) - @Suppress("INVISIBLE_REFERENCE") - @KtMongoDsl - infix fun <@kotlin.internal.OnlyInputTypes V> KProperty1.renameTo(newName: KProperty1) { - this.renameTo(newName.field) - } - @LowLevelApi private class RenameExpressionNode( val fields: List>, diff --git a/dsl/src/commonMain/kotlin/expr/UpdateOperators.kt b/dsl/src/commonMain/kotlin/expr/UpdateOperators.kt new file mode 100644 index 00000000..cef57d8a --- /dev/null +++ b/dsl/src/commonMain/kotlin/expr/UpdateOperators.kt @@ -0,0 +1,452 @@ +/* + * Copyright (c) 2024, 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.expr + +import opensavvy.ktmongo.dsl.DangerousMongoApi +import opensavvy.ktmongo.dsl.KtMongoDsl +import opensavvy.ktmongo.dsl.LowLevelApi +import opensavvy.ktmongo.dsl.expr.common.CompoundExpression +import opensavvy.ktmongo.dsl.path.Field +import opensavvy.ktmongo.dsl.path.FieldDsl +import kotlin.reflect.KProperty1 + +/** + * DSL for MongoDB operators that are used to update existing values (does *not* include aggregation operators). + * + * ### Example + * + * This expression type is available on multiple operations, most commonly `update`: + * ```kotlin + * class User( + * val name: String, + * val age: Int, + * ) + * + * collection.update( + * filter = { + * User::name eq "Bob" + * }, + * update = { + * User::age set 18 + * } + * ) + * ``` + * + * ### Operators + * + * On regular fields: + * - [`$inc`][inc] + * - [`$rename`][renameTo] + * - [`$set`][set] + * - [`$setOnInsert`][UpsertOperators.setOnInsert] (only for upserts) + * - [`$unset`][unset] + * + * On arrays: + * - [`$[]`][FieldDsl.get] + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/) + * + * @see FilterExpression Filters + */ +@KtMongoDsl +interface UpdateOperators : CompoundExpression, FieldDsl { + + // region $set + + /** + * Replaces the value of a field with the specified [value]. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String?, + * val age: Int, + * ) + * + * collection.filter { + * User::name eq "foo" + * }.updateMany { + * User::age set 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 + infix fun <@kotlin.internal.OnlyInputTypes V> Field.set(value: V) + + /** + * Replaces the value of a field with the specified [value]. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String?, + * val age: Int, + * ) + * + * collection.filter { + * User::name eq "foo" + * }.updateMany { + * User::age set 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. + */ + @OptIn(LowLevelApi::class, DangerousMongoApi::class) + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + infix fun <@kotlin.internal.OnlyInputTypes V> KProperty1.set(value: V) { + this.field.set(value) + } + + // endregion + // region $inc + + /** + * 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 inc 1 + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/inc/) + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + infix fun <@kotlin.internal.OnlyInputTypes V : Number> Field.inc(amount: V) + + /** + * 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 inc 1 + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/inc/) + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + infix fun <@kotlin.internal.OnlyInputTypes V : Number> KProperty1.inc(amount: V) { + this.field.inc(amount) + } + + // endregion + // region $unset + + /** + * Deletes a field. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int, + * val alive: Boolean, + * ) + * + * collection.filter { + * User::name eq "Luke Skywalker" + * }.updateOne { + * User::age.unset() + * User::alive set false + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/unset/) + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + fun <@kotlin.internal.OnlyInputTypes V> Field.unset() + + /** + * Deletes a field. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int, + * val alive: Boolean, + * ) + * + * collection.filter { + * User::name eq "Luke Skywalker" + * }.updateOne { + * User::age.unset() + * User::alive set false + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/unset/) + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + fun <@kotlin.internal.OnlyInputTypes V> KProperty1.unset() { + this.field.unset() + } + + // endregion + // region $rename + + /** + * Renames a field. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int, + * val ageOld: Int, + * ) + * + * collection.updateMany { + * User::ageOld renameTo User::age + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/rename/) + */ + @OptIn(LowLevelApi::class, DangerousMongoApi::class) + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + infix fun <@kotlin.internal.OnlyInputTypes V> Field.renameTo(newName: Field) + + /** + * Renames a field. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int, + * val ageOld: Int, + * ) + * + * collection.updateMany { + * User::ageOld renameTo User::age + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/rename/) + */ + @OptIn(LowLevelApi::class, DangerousMongoApi::class) + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + infix fun <@kotlin.internal.OnlyInputTypes V> KProperty1.renameTo(newName: Field) { + this.field.renameTo(newName) + } + + /** + * Renames a field. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int, + * val ageOld: Int, + * ) + * + * collection.updateMany { + * User::ageOld renameTo User::age + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/rename/) + */ + @OptIn(LowLevelApi::class, DangerousMongoApi::class) + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + infix fun <@kotlin.internal.OnlyInputTypes V> Field.renameTo(newName: KProperty1) { + this.renameTo(newName.field) + } + + /** + * Renames a field. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String, + * val age: Int, + * val ageOld: Int, + * ) + * + * collection.updateMany { + * User::ageOld renameTo User::age + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/rename/) + */ + @OptIn(LowLevelApi::class, DangerousMongoApi::class) + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + infix fun <@kotlin.internal.OnlyInputTypes V> KProperty1.renameTo(newName: KProperty1) { + this.renameTo(newName.field) + } + + // endregion + +} + +/** + * DSL for MongoDB operators that are used to update existing values, creating new documents if none exist (does *not* include aggregation operators). + * + * This interface is a variant of [UpdateOperators] used in upsert operations. See [UpdateOperators] for more information. + */ +@KtMongoDsl +interface UpsertOperators : UpdateOperators { + + // region $setOnInsert + + /** + * If an upsert operation results in an insert of a document, + * then this operator assigns the specified [value] to the field. + * If the update operation does not result in an insert, this operator does nothing. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String?, + * val age: Int, + * ) + * + * collection.filter { + * User::name eq "foo" + * }.upsertOne { + * User::age setOnInsert 18 + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/setOnInsert/) + * + * @see set Always set the value. + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + infix fun <@kotlin.internal.OnlyInputTypes V> Field.setOnInsert(value: V) + + /** + * If an upsert operation results in an insert of a document, + * then this operator assigns the specified [value] to the field. + * If the update operation does not result in an insert, this operator does nothing. + * + * ### Example + * + * ```kotlin + * class User( + * val name: String?, + * val age: Int, + * ) + * + * collection.filter { + * User::name eq "foo" + * }.upsertOne { + * User::age setOnInsert 18 + * } + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/update/setOnInsert/) + * + * @see set Always set the value. + */ + @Suppress("INVISIBLE_REFERENCE") + @KtMongoDsl + infix fun <@kotlin.internal.OnlyInputTypes V> KProperty1.setOnInsert(value: V) { + this.field.setOnInsert(value) + } + + // endregion + +} diff --git a/dsl/src/commonTest/kotlin/expr/update/FieldUpdateTest.kt b/dsl/src/commonTest/kotlin/expr/update/FieldUpdateTest.kt index 77fd6695..89c9ecc6 100644 --- a/dsl/src/commonTest/kotlin/expr/update/FieldUpdateTest.kt +++ b/dsl/src/commonTest/kotlin/expr/update/FieldUpdateTest.kt @@ -62,7 +62,7 @@ class FieldUpdateTest : PreparedSpec({ suite("Operator $setOnInsert") { test("Single field") { - update { + upsert { User::age setOnInsert 18 } shouldBeBson """ { @@ -74,7 +74,7 @@ class FieldUpdateTest : PreparedSpec({ } test("Nested field") { - update { + upsert { User::bestFriend / Friend::name setOnInsert "foo" } shouldBeBson """ { @@ -86,7 +86,7 @@ class FieldUpdateTest : PreparedSpec({ } test("Multiple fields") { - update { + upsert { User::age setOnInsert 18 User::name setOnInsert "foo" } shouldBeBson """ diff --git a/dsl/src/commonTest/kotlin/expr/update/UpdateUtils.kt b/dsl/src/commonTest/kotlin/expr/update/UpdateUtils.kt index d291a7bd..8e43bb69 100644 --- a/dsl/src/commonTest/kotlin/expr/update/UpdateUtils.kt +++ b/dsl/src/commonTest/kotlin/expr/update/UpdateUtils.kt @@ -18,9 +18,7 @@ package opensavvy.ktmongo.dsl.expr.update import opensavvy.ktmongo.bson.types.ObjectId import opensavvy.ktmongo.dsl.KtMongoDsl -import opensavvy.ktmongo.dsl.expr.UpdateExpression -import opensavvy.ktmongo.dsl.expr.shouldBeBson -import opensavvy.ktmongo.dsl.expr.testContext +import opensavvy.ktmongo.dsl.expr.* import opensavvy.prepared.runner.kotest.PreparedSpec val set = "\$set" @@ -45,7 +43,11 @@ class User( ) @KtMongoDsl -fun update(block: UpdateExpression.() -> Unit): String = +fun update(block: UpdateOperators.() -> Unit): String = + UpdateExpression(testContext()).apply(block).toString() + +@KtMongoDsl +fun upsert(block: UpsertOperators.() -> Unit): String = UpdateExpression(testContext()).apply(block).toString() class EmptyUpdateTest : PreparedSpec({ -- 2.51.2 From 89ee4acab1f107c4cb00ed1f7dcd26bd69b65633 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Wed, 6 Nov 2024 20:42:59 +0100 Subject: [PATCH 05/10] feat(dsl): Simplify Node by removing self-types --- .../kotlin/expr/common/Expression.kt | 22 +++++++++--- .../commonMain/kotlin/tree/CompoundNode.kt | 2 +- dsl/src/commonMain/kotlin/tree/Node.kt | 35 ++++--------------- 3 files changed, 24 insertions(+), 35 deletions(-) diff --git a/dsl/src/commonMain/kotlin/expr/common/Expression.kt b/dsl/src/commonMain/kotlin/expr/common/Expression.kt index 71cbb2a8..21616d35 100644 --- a/dsl/src/commonMain/kotlin/expr/common/Expression.kt +++ b/dsl/src/commonMain/kotlin/expr/common/Expression.kt @@ -21,8 +21,8 @@ import opensavvy.ktmongo.bson.BsonFieldWriter import opensavvy.ktmongo.bson.buildBsonDocument import opensavvy.ktmongo.dsl.LowLevelApi import opensavvy.ktmongo.dsl.expr.PredicateOperators -import opensavvy.ktmongo.dsl.tree.AbstractNode import opensavvy.ktmongo.dsl.tree.Node +import opensavvy.ktmongo.dsl.tree.NodeImpl /** * A node in the BSON AST. @@ -42,7 +42,7 @@ import opensavvy.ktmongo.dsl.tree.Node * * Use [toString][Any.toString] to view the JSON representation of this expression. */ -interface Expression : Node { +interface Expression : Node { /** * The context used to generate this expression. @@ -65,7 +65,7 @@ interface Expression : Node { * Returns `null` when the current expression was simplified into a no-op (= it does nothing). */ @LowLevelApi - override fun simplify(): Expression? + fun simplify(): Expression? /** * Writes the result of [simplifying][simplify] this expression into [writer]. @@ -135,9 +135,21 @@ interface Expression : Node { * operator you create so they can benefit from future fixes. Again, **an improperly-written operator may allow data * corruption or leaking**. */ -abstract class AbstractExpression( +abstract class AbstractExpression private constructor( @property:LowLevelApi override val context: BsonContext, -) : AbstractNode(), Expression { + private val node: NodeImpl, +) : Node by node, Expression { + + constructor(context: BsonContext) : this(context, NodeImpl()) + + /** + * `true` if [freeze] has been called. Can never become `false` again. + * + * If this value is `true`, this node should reject any attempt to mutate it. + * It is the responsibility of the implementor to satisfy this invariant. + */ + protected val frozen: Boolean + get() = node.frozen /** * Called when this operator should be written to a [writer]. diff --git a/dsl/src/commonMain/kotlin/tree/CompoundNode.kt b/dsl/src/commonMain/kotlin/tree/CompoundNode.kt index a91a11fc..f8121819 100644 --- a/dsl/src/commonMain/kotlin/tree/CompoundNode.kt +++ b/dsl/src/commonMain/kotlin/tree/CompoundNode.kt @@ -32,7 +32,7 @@ import opensavvy.ktmongo.dsl.LowLevelApi * Instead, this node should be considered as representing the children itself, as a single unit. * Subtypes may decide to provide such a feature, however. */ -interface CompoundNode> { +interface CompoundNode { /** * Adds a new [Node] into the current node. diff --git a/dsl/src/commonMain/kotlin/tree/Node.kt b/dsl/src/commonMain/kotlin/tree/Node.kt index 2c9a16de..1ca5f32c 100644 --- a/dsl/src/commonMain/kotlin/tree/Node.kt +++ b/dsl/src/commonMain/kotlin/tree/Node.kt @@ -27,7 +27,6 @@ import opensavvy.ktmongo.dsl.LowLevelApi * * Trees are expected to be built bottom-up: a node is always built before its parents. * Once a node has been [accepted][CompoundNode.accept] by a parent, it [freezes][freeze] (becomes forever immutable). - * Before the node is accepted, however, it gets a chance to [simplify] itself. * The same node may be added to multiple parent nodes. * * This schemes ensures that trees are always as simplified as possible: we are always building a single node at a time, @@ -38,15 +37,8 @@ import opensavvy.ktmongo.dsl.LowLevelApi * - Nodes that group other nodes into a single larger node. * * The former category implements this interface, whereas the latter implements [CompoundNode]. - * - * ### Implementing this interface - * - * See [AbstractNode]. - * - * @param Self The type of node returned by the [simplify] methods. - * In most cases, it should be an interface that implements [Node] and is implemented by the current subtype. */ -interface Node> { +interface Node { /** * Makes this node immutable. @@ -59,29 +51,15 @@ interface Node> { @LowLevelApi fun freeze() - /** - * Returns a simplified (but equivalent) node to the current node. - * - * This function is always called before this node is added to a parent node; - * the result value is added in its stead after being [frozen][freeze]. - * To learn more about this process, see [Node]. - * - * The simplest default implementation is to return `this`. - * - * @return `null` when the current node was simplified into nothingness (i.e. it does nothing). - */ - @LowLevelApi - fun simplify(): Self? - } /** * Helper to implement [Node]. * - * This class takes care of handling [freezing][freeze]. - * Implementors should ensure to check the value of [frozen] before accepting any mutation. + * Should generally be used to implement [Node] by delegation. + * Users should ensure to check the value of [frozen] before accepting any mutation. */ -abstract class AbstractNode> : Node { +internal class NodeImpl : Node { /** * `true` if [freeze] has been called. Can never become `false` again. @@ -89,12 +67,11 @@ abstract class AbstractNode> : Node { * If this value is `true`, this node should reject any attempt to mutate it. * It is the responsibility of the implementor to satisfy this invariant. */ - protected var frozen: Boolean = false + var frozen: Boolean = false private set @LowLevelApi - final override fun freeze() { + override fun freeze() { frozen = true } - } -- 2.51.2 From 017807a9238c62d610b0a4ede7e5cef1b0606bd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Wed, 6 Nov 2024 21:28:48 +0100 Subject: [PATCH 06/10] feat(dsl): Stabilize Options, implement LimitOption --- .../commonMain/kotlin/options/CountOptions.kt | 10 +- .../kotlin/options/common/LimitOption.kt | 53 +++++-- .../kotlin/options/common/Options.kt | 130 +++++++++++++++++- .../commonTest/kotlin/options/OptionTest.kt | 39 ++++++ 4 files changed, 210 insertions(+), 22 deletions(-) create mode 100644 dsl/src/commonTest/kotlin/options/OptionTest.kt diff --git a/dsl/src/commonMain/kotlin/options/CountOptions.kt b/dsl/src/commonMain/kotlin/options/CountOptions.kt index 13702d80..7f452d47 100644 --- a/dsl/src/commonMain/kotlin/options/CountOptions.kt +++ b/dsl/src/commonMain/kotlin/options/CountOptions.kt @@ -17,11 +17,15 @@ package opensavvy.ktmongo.dsl.options import opensavvy.ktmongo.bson.BsonContext -import opensavvy.ktmongo.dsl.expr.common.AbstractCompoundExpression -import opensavvy.ktmongo.dsl.options.common.LimitOption +import opensavvy.ktmongo.dsl.LowLevelApi import opensavvy.ktmongo.dsl.options.common.Options +import opensavvy.ktmongo.dsl.options.common.OptionsHolder +import opensavvy.ktmongo.dsl.options.common.WithLimit /** * The options for a `collection.count` operation. */ -class CountOptions(context: BsonContext) : AbstractCompoundExpression(context), Options, LimitOption +@OptIn(LowLevelApi::class) +class CountOptions(context: BsonContext) : + Options by OptionsHolder(context), + WithLimit diff --git a/dsl/src/commonMain/kotlin/options/common/LimitOption.kt b/dsl/src/commonMain/kotlin/options/common/LimitOption.kt index 3d36d087..32e26194 100644 --- a/dsl/src/commonMain/kotlin/options/common/LimitOption.kt +++ b/dsl/src/commonMain/kotlin/options/common/LimitOption.kt @@ -21,17 +21,43 @@ import opensavvy.ktmongo.bson.BsonFieldWriter import opensavvy.ktmongo.dsl.DangerousMongoApi import opensavvy.ktmongo.dsl.LowLevelApi import opensavvy.ktmongo.dsl.expr.common.AbstractExpression -import opensavvy.ktmongo.dsl.expr.common.CompoundExpression + +/** + * Maximum number of elements analyzed by this operation. + * + * For more information, see [WithLimit]. + */ +class LimitOption( + val limit: Long, + context: BsonContext, +) : AbstractExpression(context), Option { + + override val value: Long + get() = limit + + @LowLevelApi + override fun write(writer: BsonFieldWriter) { + writer.writeInt64("limit", limit) + } +} /** * Limits the number of elements returned by a query. * * See [limit]. */ -interface LimitOption : CompoundExpression { +interface WithLimit : Options { /** * The maximum number of matching documents to return. + * + * ```kotlin + * collections.count { + * options { + * limit(99) + * } + * } + * ``` */ fun limit(limit: Int) { limit(limit.toLong()) @@ -39,21 +65,20 @@ interface LimitOption : CompoundExpression { /** * The maximum number of matching documents to return. + * + * ```kotlin + * collections.count { + * options { + * limit(99L) + * } + * } + * ``` + * + * Note that not all drivers support specifying a limit larger than an `Int`. */ @OptIn(DangerousMongoApi::class, LowLevelApi::class) fun limit(limit: Long) { - accept(LimitOptionExpression(limit, context)) - } - - private class LimitOptionExpression( - private val limit: Long, - context: BsonContext, - ) : AbstractExpression(context) { - - @LowLevelApi - override fun write(writer: BsonFieldWriter) { - writer.writeInt64("limit", limit) - } + accept(LimitOption(limit, context)) } } diff --git a/dsl/src/commonMain/kotlin/options/common/Options.kt b/dsl/src/commonMain/kotlin/options/common/Options.kt index 80816cf6..777b9e18 100644 --- a/dsl/src/commonMain/kotlin/options/common/Options.kt +++ b/dsl/src/commonMain/kotlin/options/common/Options.kt @@ -16,12 +16,132 @@ package opensavvy.ktmongo.dsl.options.common -import opensavvy.ktmongo.dsl.expr.common.CompoundExpression +import opensavvy.ktmongo.bson.BsonContext +import opensavvy.ktmongo.dsl.DangerousMongoApi +import opensavvy.ktmongo.dsl.KtMongoDsl +import opensavvy.ktmongo.dsl.LowLevelApi +import opensavvy.ktmongo.dsl.expr.common.AbstractCompoundExpression +import opensavvy.ktmongo.dsl.expr.common.Expression +import opensavvy.ktmongo.dsl.models.Count +import opensavvy.ktmongo.dsl.options.CountOptions +import opensavvy.ktmongo.dsl.tree.CompoundNode /** - * Parent interface for all option types. + * Additional parameters that are passed to MongoDB operations. * - * Option types are used to pass additional arguments to MongoDB operations. - * See the different implementations for more information. + * Options are usually configured with the `options {}` DSL in an operation. + * For example, if we want to know how many notifications a user has, but can only display "99" because of UI size + * constraints, we can use the following request: + * ```kotlin + * notifications.count { + * Notification::ownedBy eq currentUser + * + * options { + * limit(99) + * } + * } + * ``` + * + * The order in which the filter and the options are declared is irrelevant; this request is strictly equivalent: + * ```kotlin + * notifications.count { + * options { + * limit(99) + * } + * + * Notification::ownedBy eq currentUser + * } + * ``` + * + * However, if the same option is specified multiple times, only the very last one applies: + * ```kotlin + * notifications.count { + * options { + * limit(99) + * limit(10) + * } + * } + * ``` + * will only count at most 10 elements. + * + * ### Accessing the current value + * + * See [Options], [value] and [option]. + * + * ### Implementing this interface + * + * Implementations of this interface must be careful to respect the contract of [Expression], in particular about + * the [toString] representation. + * + * Option implementations must be immutable. If the user wants to change an option, they can specify it a second time + * (which will override the previous one). + * + * @param Value The [value] stored by this option. + * For example, [LimitOption] stores an integer. + */ +interface Option : Expression { + + /** + * The value stored by this option, as specified by the user. + * + * Learn more about options: [Option]. + * + * To access the value of a specific option, see [option]. + */ + val value: Value +} + +/** + * Parent interface for all option containers. + * + * Option containers are types that declare a set of options. They are usually tied to a specific MongoDB operation + * via a model. + * + * For example, for options related to the [Count] model, see [CountOptions]. + */ +@KtMongoDsl +interface Options : Expression, CompoundNode> { + + /** + * The full list of options set on this container. + * + * Specific options are usually searched using the [option] extension. + */ + @LowLevelApi + val allOptions: List> + + /** + * JSON representation of this option. + */ + override fun toString(): String // Specified explicitly to force implementation by the 'by' keyword +} + +internal class OptionsHolder(context: BsonContext) : AbstractCompoundExpression(context), Options { + @LowLevelApi + @DangerousMongoApi + override fun accept(node: Option<*>) { + this.accept(node as Expression) + } + + @OptIn(LowLevelApi::class) + override val allOptions: List> + get() = children.filterIsInstance>() +} + +/** + * Accesses the value of a given [Option]. + * + * For example, if we have a helper function that sets some default options, and we want to know what maximum `limit` it + * set, we can use: + * ```kotlin + * collection.count { + * // … + * + * options { + * println(option()) // Will print an integer + * } + * } + * ``` */ -interface Options : CompoundExpression +@LowLevelApi +inline fun , V> Options.option(): V? = (allOptions.findLast { it is O } as O?)?.value diff --git a/dsl/src/commonTest/kotlin/options/OptionTest.kt b/dsl/src/commonTest/kotlin/options/OptionTest.kt new file mode 100644 index 00000000..2f184299 --- /dev/null +++ b/dsl/src/commonTest/kotlin/options/OptionTest.kt @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2024, 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.options + +import opensavvy.ktmongo.dsl.LowLevelApi +import opensavvy.ktmongo.dsl.expr.shouldBeBson +import opensavvy.ktmongo.dsl.expr.testContext +import opensavvy.ktmongo.dsl.options.common.LimitOption +import opensavvy.ktmongo.dsl.options.common.option +import opensavvy.prepared.runner.kotest.PreparedSpec + +@LowLevelApi +class OptionTest : PreparedSpec({ + + test("Validate the system") { + val countOptions = CountOptions(testContext()) + countOptions.toString() shouldBeBson "{}" + + countOptions.limit(99) + countOptions.toString() shouldBeBson "{\"limit\": 99}" + + check(countOptions.option() == 99L) + } + +}) -- 2.51.2 From 5dbe8e5e3bcb0a15687aa8892dde3fbc0d7c8aaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Wed, 6 Nov 2024 21:36:14 +0100 Subject: [PATCH 07/10] feat(driver-sync): Replace expressions by operators --- .../commonMain/kotlin/FilteredCollection.kt | 21 ++++++++++--------- .../kotlin/operations/CountOperations.kt | 4 ++-- .../kotlin/operations/FindOperations.kt | 5 +++-- .../kotlin/operations/UpdateOperations.kt | 21 ++++++++++--------- .../src/jvmMain/kotlin/JvmMongoCollection.kt | 15 +++++++------ 5 files changed, 34 insertions(+), 32 deletions(-) diff --git a/driver-sync/src/commonMain/kotlin/FilteredCollection.kt b/driver-sync/src/commonMain/kotlin/FilteredCollection.kt index 60ef531a..2009b302 100644 --- a/driver-sync/src/commonMain/kotlin/FilteredCollection.kt +++ b/driver-sync/src/commonMain/kotlin/FilteredCollection.kt @@ -18,18 +18,19 @@ package opensavvy.ktmongo.sync import opensavvy.ktmongo.bson.BsonContext import opensavvy.ktmongo.dsl.LowLevelApi -import opensavvy.ktmongo.dsl.expr.FilterExpression -import opensavvy.ktmongo.dsl.expr.UpdateExpression +import opensavvy.ktmongo.dsl.expr.FilterOperators +import opensavvy.ktmongo.dsl.expr.UpdateOperators +import opensavvy.ktmongo.dsl.expr.UpsertOperators private class FilteredCollection( private val upstream: MongoCollection, - private val globalFilter: FilterExpression.() -> Unit, + private val globalFilter: FilterOperators.() -> Unit, ) : MongoCollection { override fun find(): MongoIterable = upstream.find(globalFilter) - override fun find(predicate: FilterExpression.() -> Unit): MongoIterable = + override fun find(predicate: FilterOperators.() -> Unit): MongoIterable = upstream.find { globalFilter() predicate() @@ -42,7 +43,7 @@ private class FilteredCollection( override fun count(): Long = upstream.count(globalFilter) - override fun count(predicate: FilterExpression.() -> Unit): Long = + override fun count(predicate: FilterOperators.() -> Unit): Long = upstream.count { globalFilter() predicate() @@ -51,7 +52,7 @@ private class FilteredCollection( override fun countEstimated(): Long = count() - override fun updateMany(filter: FilterExpression.() -> Unit, update: UpdateExpression.() -> Unit) = + override fun updateMany(filter: FilterOperators.() -> Unit, update: UpdateOperators.() -> Unit) = upstream.updateMany( filter = { globalFilter() @@ -60,7 +61,7 @@ private class FilteredCollection( update = update, ) - override fun updateOne(filter: FilterExpression.() -> Unit, update: UpdateExpression.() -> Unit) = + override fun updateOne(filter: FilterOperators.() -> Unit, update: UpdateOperators.() -> Unit) = upstream.updateOne( filter = { globalFilter() @@ -69,7 +70,7 @@ private class FilteredCollection( update = update, ) - override fun upsertOne(filter: FilterExpression.() -> Unit, update: UpdateExpression.() -> Unit) = + override fun upsertOne(filter: FilterOperators.() -> Unit, update: UpsertOperators.() -> Unit) = upstream.upsertOne( filter = { globalFilter() @@ -78,7 +79,7 @@ private class FilteredCollection( update = update, ) - override fun findOneAndUpdate(filter: FilterExpression.() -> Unit, update: UpdateExpression.() -> Unit): Document? = + override fun findOneAndUpdate(filter: FilterOperators.() -> Unit, update: UpdateOperators.() -> Unit): Document? = upstream.findOneAndUpdate( filter = { globalFilter() @@ -116,5 +117,5 @@ private class FilteredCollection( * activeOrders.find() // Only returns orders that are not logically deleted * ``` */ -fun MongoCollection.filter(filter: FilterExpression.() -> Unit): MongoCollection = +fun MongoCollection.filter(filter: FilterOperators.() -> Unit): MongoCollection = FilteredCollection(this, filter) diff --git a/driver-sync/src/commonMain/kotlin/operations/CountOperations.kt b/driver-sync/src/commonMain/kotlin/operations/CountOperations.kt index f7eff71d..41feb675 100644 --- a/driver-sync/src/commonMain/kotlin/operations/CountOperations.kt +++ b/driver-sync/src/commonMain/kotlin/operations/CountOperations.kt @@ -16,7 +16,7 @@ package opensavvy.ktmongo.sync.operations -import opensavvy.ktmongo.dsl.expr.FilterExpression +import opensavvy.ktmongo.dsl.expr.FilterOperators /** * Interface grouping MongoDB operations relating to counting documents. @@ -56,7 +56,7 @@ interface CountOperations : BaseOperations { * - [Official documentation](https://www.mongodb.com/docs/manual/reference/method/db.collection.countDocuments/) */ fun count( - predicate: FilterExpression.() -> Unit + predicate: FilterOperators.() -> Unit ): Long /** diff --git a/driver-sync/src/commonMain/kotlin/operations/FindOperations.kt b/driver-sync/src/commonMain/kotlin/operations/FindOperations.kt index 4cd23510..e5aa7e25 100644 --- a/driver-sync/src/commonMain/kotlin/operations/FindOperations.kt +++ b/driver-sync/src/commonMain/kotlin/operations/FindOperations.kt @@ -17,6 +17,7 @@ package opensavvy.ktmongo.sync.operations import opensavvy.ktmongo.dsl.expr.FilterExpression +import opensavvy.ktmongo.dsl.expr.FilterOperators import opensavvy.ktmongo.sync.MongoIterable /** @@ -58,7 +59,7 @@ interface FindOperations : BaseOperations { * * @see findOne When only one result is expected. */ - fun find(predicate: FilterExpression.() -> Unit): MongoIterable + fun find(predicate: FilterOperators.() -> Unit): MongoIterable /** * Finds a document in this collection that satisfies [predicate]. @@ -84,7 +85,7 @@ interface FindOperations : BaseOperations { * * @see find When multiple results are expected. */ - fun findOne(predicate: FilterExpression.() -> Unit): Document? = + fun findOne(predicate: FilterOperators.() -> Unit): Document? = find(predicate).firstOrNull() } diff --git a/driver-sync/src/commonMain/kotlin/operations/UpdateOperations.kt b/driver-sync/src/commonMain/kotlin/operations/UpdateOperations.kt index a6f1f823..35682d1e 100644 --- a/driver-sync/src/commonMain/kotlin/operations/UpdateOperations.kt +++ b/driver-sync/src/commonMain/kotlin/operations/UpdateOperations.kt @@ -16,8 +16,9 @@ package opensavvy.ktmongo.sync.operations -import opensavvy.ktmongo.dsl.expr.FilterExpression -import opensavvy.ktmongo.dsl.expr.UpdateExpression +import opensavvy.ktmongo.dsl.expr.FilterOperators +import opensavvy.ktmongo.dsl.expr.UpdateOperators +import opensavvy.ktmongo.dsl.expr.UpsertOperators import opensavvy.ktmongo.sync.MongoCollection import opensavvy.ktmongo.sync.filter @@ -69,8 +70,8 @@ interface UpdateOperations : BaseOperations { * @see updateOne */ fun updateMany( - filter: FilterExpression.() -> Unit = {}, - update: UpdateExpression.() -> Unit, + filter: FilterOperators.() -> Unit = {}, + update: UpdateOperators.() -> Unit, ) /** @@ -119,8 +120,8 @@ interface UpdateOperations : BaseOperations { * @see findOneAndUpdate Also returns the result of the update. */ fun updateOne( - filter: FilterExpression.() -> Unit = {}, - update: UpdateExpression.() -> Unit, + filter: FilterOperators.() -> Unit = {}, + update: UpdateOperators.() -> Unit, ) /** @@ -172,8 +173,8 @@ interface UpdateOperations : BaseOperations { * @see updateOne */ fun upsertOne( - filter: FilterExpression.() -> Unit = {}, - update: UpdateExpression.() -> Unit, + filter: FilterOperators.() -> Unit = {}, + update: UpsertOperators.() -> Unit, ) /** @@ -220,8 +221,8 @@ interface UpdateOperations : BaseOperations { * @see updateOne Do not return the value. */ fun findOneAndUpdate( - filter: FilterExpression.() -> Unit = {}, - update: UpdateExpression.() -> Unit, + filter: FilterOperators.() -> Unit = {}, + update: UpdateOperators.() -> Unit, ): Document? } diff --git a/driver-sync/src/jvmMain/kotlin/JvmMongoCollection.kt b/driver-sync/src/jvmMain/kotlin/JvmMongoCollection.kt index f6dd4ceb..682a9d0b 100644 --- a/driver-sync/src/jvmMain/kotlin/JvmMongoCollection.kt +++ b/driver-sync/src/jvmMain/kotlin/JvmMongoCollection.kt @@ -20,8 +20,7 @@ import com.mongodb.client.model.UpdateOptions import opensavvy.ktmongo.bson.BsonContext import opensavvy.ktmongo.bson.buildBsonDocument import opensavvy.ktmongo.dsl.LowLevelApi -import opensavvy.ktmongo.dsl.expr.FilterExpression -import opensavvy.ktmongo.dsl.expr.UpdateExpression +import opensavvy.ktmongo.dsl.expr.* import opensavvy.ktmongo.dsl.expr.common.AbstractCompoundExpression import org.bson.BsonDocument @@ -49,7 +48,7 @@ class JvmMongoCollection internal constructor( JvmMongoIterable(inner.find()) @OptIn(LowLevelApi::class) - override fun find(predicate: FilterExpression.() -> Unit): JvmMongoIterable { + override fun find(predicate: FilterOperators.() -> Unit): JvmMongoIterable { val filter = FilterExpression(context) .apply(predicate) .toBsonDocument() @@ -64,7 +63,7 @@ class JvmMongoCollection internal constructor( inner.countDocuments() @OptIn(LowLevelApi::class) - override fun count(predicate: FilterExpression.() -> Unit): Long { + override fun count(predicate: FilterOperators.() -> Unit): Long { val filter = FilterExpression(context) .apply(predicate) .toBsonDocument() @@ -79,7 +78,7 @@ class JvmMongoCollection internal constructor( // region Update @OptIn(LowLevelApi::class) - override fun updateMany(filter: FilterExpression.() -> Unit, update: UpdateExpression.() -> Unit) { + override fun updateMany(filter: FilterOperators.() -> Unit, update: UpdateOperators.() -> Unit) { val filter = FilterExpression(context) .apply(filter) .toBsonDocument() @@ -92,7 +91,7 @@ class JvmMongoCollection internal constructor( } @OptIn(LowLevelApi::class) - override fun updateOne(filter: FilterExpression.() -> Unit, update: UpdateExpression.() -> Unit) { + override fun updateOne(filter: FilterOperators.() -> Unit, update: UpdateOperators.() -> Unit) { val filter = FilterExpression(context) .apply(filter) .toBsonDocument() @@ -105,7 +104,7 @@ class JvmMongoCollection internal constructor( } @OptIn(LowLevelApi::class) - override fun upsertOne(filter: FilterExpression.() -> Unit, update: UpdateExpression.() -> Unit) { + override fun upsertOne(filter: FilterOperators.() -> Unit, update: UpsertOperators.() -> Unit) { val filter = FilterExpression(context) .apply(filter) .toBsonDocument() @@ -118,7 +117,7 @@ class JvmMongoCollection internal constructor( } @OptIn(LowLevelApi::class) - override fun findOneAndUpdate(filter: FilterExpression.() -> Unit, update: UpdateExpression.() -> Unit): Document? { + override fun findOneAndUpdate(filter: FilterOperators.() -> Unit, update: UpdateOperators.() -> Unit): Document? { val filter = FilterExpression(context) .apply(filter) .toBsonDocument() -- 2.51.2 From 71b1fec583f2e7bc0f579812902fc822c0960bb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Wed, 6 Nov 2024 21:44:37 +0100 Subject: [PATCH 08/10] feat(driver-coroutines): Replace expressions by operators --- .../commonMain/kotlin/FilteredCollection.kt | 21 ++++++++++--------- .../kotlin/operations/CountOperations.kt | 4 ++-- .../kotlin/operations/FindOperations.kt | 5 +++-- .../kotlin/operations/UpdateOperations.kt | 21 ++++++++++--------- .../src/jvmMain/kotlin/JvmMongoCollection.kt | 15 +++++++------ 5 files changed, 34 insertions(+), 32 deletions(-) diff --git a/driver-coroutines/src/commonMain/kotlin/FilteredCollection.kt b/driver-coroutines/src/commonMain/kotlin/FilteredCollection.kt index 68b1f038..371b8934 100644 --- a/driver-coroutines/src/commonMain/kotlin/FilteredCollection.kt +++ b/driver-coroutines/src/commonMain/kotlin/FilteredCollection.kt @@ -18,18 +18,19 @@ package opensavvy.ktmongo.coroutines import opensavvy.ktmongo.bson.BsonContext import opensavvy.ktmongo.dsl.LowLevelApi -import opensavvy.ktmongo.dsl.expr.FilterExpression -import opensavvy.ktmongo.dsl.expr.UpdateExpression +import opensavvy.ktmongo.dsl.expr.FilterOperators +import opensavvy.ktmongo.dsl.expr.UpdateOperators +import opensavvy.ktmongo.dsl.expr.UpsertOperators private class FilteredCollection( private val upstream: MongoCollection, - private val globalFilter: FilterExpression.() -> Unit, + private val globalFilter: FilterOperators.() -> Unit, ) : MongoCollection { override fun find(): MongoIterable = upstream.find(globalFilter) - override fun find(predicate: FilterExpression.() -> Unit): MongoIterable = + override fun find(predicate: FilterOperators.() -> Unit): MongoIterable = upstream.find { globalFilter() predicate() @@ -42,7 +43,7 @@ private class FilteredCollection( override suspend fun count(): Long = upstream.count(globalFilter) - override suspend fun count(predicate: FilterExpression.() -> Unit): Long = + override suspend fun count(predicate: FilterOperators.() -> Unit): Long = upstream.count { globalFilter() predicate() @@ -51,7 +52,7 @@ private class FilteredCollection( override suspend fun countEstimated(): Long = count() - override suspend fun updateMany(filter: FilterExpression.() -> Unit, update: UpdateExpression.() -> Unit) = + override suspend fun updateMany(filter: FilterOperators.() -> Unit, update: UpdateOperators.() -> Unit) = upstream.updateMany( filter = { globalFilter() @@ -60,7 +61,7 @@ private class FilteredCollection( update = update, ) - override suspend fun updateOne(filter: FilterExpression.() -> Unit, update: UpdateExpression.() -> Unit) = + override suspend fun updateOne(filter: FilterOperators.() -> Unit, update: UpdateOperators.() -> Unit) = upstream.updateOne( filter = { globalFilter() @@ -69,7 +70,7 @@ private class FilteredCollection( update = update, ) - override suspend fun upsertOne(filter: FilterExpression.() -> Unit, update: UpdateExpression.() -> Unit) = + override suspend fun upsertOne(filter: FilterOperators.() -> Unit, update: UpsertOperators.() -> Unit) = upstream.upsertOne( filter = { globalFilter() @@ -78,7 +79,7 @@ private class FilteredCollection( update = update, ) - override suspend fun findOneAndUpdate(filter: FilterExpression.() -> Unit, update: UpdateExpression.() -> Unit): Document? = + override suspend fun findOneAndUpdate(filter: FilterOperators.() -> Unit, update: UpdateOperators.() -> Unit): Document? = upstream.findOneAndUpdate( filter = { globalFilter() @@ -116,5 +117,5 @@ private class FilteredCollection( * activeOrders.find() // Only returns orders that are not logically deleted * ``` */ -fun MongoCollection.filter(filter: FilterExpression.() -> Unit): MongoCollection = +fun MongoCollection.filter(filter: FilterOperators.() -> Unit): MongoCollection = FilteredCollection(this, filter) diff --git a/driver-coroutines/src/commonMain/kotlin/operations/CountOperations.kt b/driver-coroutines/src/commonMain/kotlin/operations/CountOperations.kt index 88859173..a8665cf4 100644 --- a/driver-coroutines/src/commonMain/kotlin/operations/CountOperations.kt +++ b/driver-coroutines/src/commonMain/kotlin/operations/CountOperations.kt @@ -16,7 +16,7 @@ package opensavvy.ktmongo.coroutines.operations -import opensavvy.ktmongo.dsl.expr.FilterExpression +import opensavvy.ktmongo.dsl.expr.FilterOperators /** * Interface grouping MongoDB operations relating to counting documents. @@ -56,7 +56,7 @@ interface CountOperations : BaseOperations { * - [Official documentation](https://www.mongodb.com/docs/manual/reference/method/db.collection.countDocuments/) */ suspend fun count( - predicate: FilterExpression.() -> Unit + predicate: FilterOperators.() -> Unit ): Long /** diff --git a/driver-coroutines/src/commonMain/kotlin/operations/FindOperations.kt b/driver-coroutines/src/commonMain/kotlin/operations/FindOperations.kt index 41665786..c8480147 100644 --- a/driver-coroutines/src/commonMain/kotlin/operations/FindOperations.kt +++ b/driver-coroutines/src/commonMain/kotlin/operations/FindOperations.kt @@ -18,6 +18,7 @@ package opensavvy.ktmongo.coroutines.operations import opensavvy.ktmongo.coroutines.MongoIterable import opensavvy.ktmongo.dsl.expr.FilterExpression +import opensavvy.ktmongo.dsl.expr.FilterOperators /** * Interface grouping MongoDB operations allowing to search for information. @@ -58,7 +59,7 @@ interface FindOperations : BaseOperations { * * @see findOne When only one result is expected. */ - fun find(predicate: FilterExpression.() -> Unit): MongoIterable + fun find(predicate: FilterOperators.() -> Unit): MongoIterable /** * Finds a document in this collection that satisfies [predicate]. @@ -84,7 +85,7 @@ interface FindOperations : BaseOperations { * * @see find When multiple results are expected. */ - suspend fun findOne(predicate: FilterExpression.() -> Unit): Document? = + suspend fun findOne(predicate: FilterOperators.() -> Unit): Document? = find(predicate).firstOrNull() } diff --git a/driver-coroutines/src/commonMain/kotlin/operations/UpdateOperations.kt b/driver-coroutines/src/commonMain/kotlin/operations/UpdateOperations.kt index e88840aa..53314172 100644 --- a/driver-coroutines/src/commonMain/kotlin/operations/UpdateOperations.kt +++ b/driver-coroutines/src/commonMain/kotlin/operations/UpdateOperations.kt @@ -18,8 +18,9 @@ package opensavvy.ktmongo.coroutines.operations import opensavvy.ktmongo.coroutines.MongoCollection import opensavvy.ktmongo.coroutines.filter -import opensavvy.ktmongo.dsl.expr.FilterExpression -import opensavvy.ktmongo.dsl.expr.UpdateExpression +import opensavvy.ktmongo.dsl.expr.FilterOperators +import opensavvy.ktmongo.dsl.expr.UpdateOperators +import opensavvy.ktmongo.dsl.expr.UpsertOperators /** * Interface grouping MongoDB operations allowing to update existing information. @@ -69,8 +70,8 @@ interface UpdateOperations : BaseOperations { * @see updateOne */ suspend fun updateMany( - filter: FilterExpression.() -> Unit = {}, - update: UpdateExpression.() -> Unit, + filter: FilterOperators.() -> Unit = {}, + update: UpdateOperators.() -> Unit, ) /** @@ -119,8 +120,8 @@ interface UpdateOperations : BaseOperations { * @see findOneAndUpdate Also returns the result of the update. */ suspend fun updateOne( - filter: FilterExpression.() -> Unit = {}, - update: UpdateExpression.() -> Unit, + filter: FilterOperators.() -> Unit = {}, + update: UpdateOperators.() -> Unit, ) /** @@ -172,8 +173,8 @@ interface UpdateOperations : BaseOperations { * @see updateOne */ suspend fun upsertOne( - filter: FilterExpression.() -> Unit = {}, - update: UpdateExpression.() -> Unit, + filter: FilterOperators.() -> Unit = {}, + update: UpsertOperators.() -> Unit, ) /** @@ -220,8 +221,8 @@ interface UpdateOperations : BaseOperations { * @see updateOne Do not return the value. */ suspend fun findOneAndUpdate( - filter: FilterExpression.() -> Unit = {}, - update: UpdateExpression.() -> Unit, + filter: FilterOperators.() -> Unit = {}, + update: UpdateOperators.() -> Unit, ): Document? } diff --git a/driver-coroutines/src/jvmMain/kotlin/JvmMongoCollection.kt b/driver-coroutines/src/jvmMain/kotlin/JvmMongoCollection.kt index 54fb82e7..58c31518 100644 --- a/driver-coroutines/src/jvmMain/kotlin/JvmMongoCollection.kt +++ b/driver-coroutines/src/jvmMain/kotlin/JvmMongoCollection.kt @@ -20,8 +20,7 @@ import com.mongodb.client.model.UpdateOptions import opensavvy.ktmongo.bson.BsonContext import opensavvy.ktmongo.bson.buildBsonDocument import opensavvy.ktmongo.dsl.LowLevelApi -import opensavvy.ktmongo.dsl.expr.FilterExpression -import opensavvy.ktmongo.dsl.expr.UpdateExpression +import opensavvy.ktmongo.dsl.expr.* import opensavvy.ktmongo.dsl.expr.common.AbstractCompoundExpression import org.bson.BsonDocument @@ -49,7 +48,7 @@ class JvmMongoCollection internal constructor( JvmMongoIterable(inner.find()) @OptIn(LowLevelApi::class) - override fun find(predicate: FilterExpression.() -> Unit): JvmMongoIterable { + override fun find(predicate: FilterOperators.() -> Unit): JvmMongoIterable { val filter = FilterExpression(context) .apply(predicate) .toBsonDocument() @@ -64,7 +63,7 @@ class JvmMongoCollection internal constructor( inner.countDocuments() @OptIn(LowLevelApi::class) - override suspend fun count(predicate: FilterExpression.() -> Unit): Long { + override suspend fun count(predicate: FilterOperators.() -> Unit): Long { val filter = FilterExpression(context) .apply(predicate) .toBsonDocument() @@ -79,7 +78,7 @@ class JvmMongoCollection internal constructor( // region Update @OptIn(LowLevelApi::class) - override suspend fun updateMany(filter: FilterExpression.() -> Unit, update: UpdateExpression.() -> Unit) { + override suspend fun updateMany(filter: FilterOperators.() -> Unit, update: UpdateOperators.() -> Unit) { val filter = FilterExpression(context) .apply(filter) .toBsonDocument() @@ -92,7 +91,7 @@ class JvmMongoCollection internal constructor( } @OptIn(LowLevelApi::class) - override suspend fun updateOne(filter: FilterExpression.() -> Unit, update: UpdateExpression.() -> Unit) { + override suspend fun updateOne(filter: FilterOperators.() -> Unit, update: UpdateOperators.() -> Unit) { val filter = FilterExpression(context) .apply(filter) .toBsonDocument() @@ -105,7 +104,7 @@ class JvmMongoCollection internal constructor( } @OptIn(LowLevelApi::class) - override suspend fun upsertOne(filter: FilterExpression.() -> Unit, update: UpdateExpression.() -> Unit) { + override suspend fun upsertOne(filter: FilterOperators.() -> Unit, update: UpsertOperators.() -> Unit) { val filter = FilterExpression(context) .apply(filter) .toBsonDocument() @@ -118,7 +117,7 @@ class JvmMongoCollection internal constructor( } @OptIn(LowLevelApi::class) - override suspend fun findOneAndUpdate(filter: FilterExpression.() -> Unit, update: UpdateExpression.() -> Unit): Document? { + override suspend fun findOneAndUpdate(filter: FilterOperators.() -> Unit, update: UpdateOperators.() -> Unit): Document? { val filter = FilterExpression(context) .apply(filter) .toBsonDocument() -- 2.51.2 From ea53d6bbbeb7f9372639f49df544779224c00fb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Wed, 6 Nov 2024 21:53:12 +0100 Subject: [PATCH 09/10] feat: Create the Count model --- .../commonMain/kotlin/FilteredCollection.kt | 3 +- .../kotlin/operations/CountOperations.kt | 4 +- .../src/jvmMain/kotlin/JvmMongoCollection.kt | 20 ++++--- .../commonMain/kotlin/FilteredCollection.kt | 3 +- .../kotlin/operations/CountOperations.kt | 4 +- .../src/jvmMain/kotlin/JvmMongoCollection.kt | 20 ++++--- dsl/src/commonMain/kotlin/models/Count.kt | 53 +++++++++++++++++++ 7 files changed, 89 insertions(+), 18 deletions(-) create mode 100644 dsl/src/commonMain/kotlin/models/Count.kt diff --git a/driver-coroutines/src/commonMain/kotlin/FilteredCollection.kt b/driver-coroutines/src/commonMain/kotlin/FilteredCollection.kt index 371b8934..c0435de5 100644 --- a/driver-coroutines/src/commonMain/kotlin/FilteredCollection.kt +++ b/driver-coroutines/src/commonMain/kotlin/FilteredCollection.kt @@ -21,6 +21,7 @@ import opensavvy.ktmongo.dsl.LowLevelApi import opensavvy.ktmongo.dsl.expr.FilterOperators import opensavvy.ktmongo.dsl.expr.UpdateOperators import opensavvy.ktmongo.dsl.expr.UpsertOperators +import opensavvy.ktmongo.dsl.models.Count private class FilteredCollection( private val upstream: MongoCollection, @@ -43,7 +44,7 @@ private class FilteredCollection( override suspend fun count(): Long = upstream.count(globalFilter) - override suspend fun count(predicate: FilterOperators.() -> Unit): Long = + override suspend fun count(predicate: Count.() -> Unit): Long = upstream.count { globalFilter() predicate() diff --git a/driver-coroutines/src/commonMain/kotlin/operations/CountOperations.kt b/driver-coroutines/src/commonMain/kotlin/operations/CountOperations.kt index a8665cf4..72169743 100644 --- a/driver-coroutines/src/commonMain/kotlin/operations/CountOperations.kt +++ b/driver-coroutines/src/commonMain/kotlin/operations/CountOperations.kt @@ -16,7 +16,7 @@ package opensavvy.ktmongo.coroutines.operations -import opensavvy.ktmongo.dsl.expr.FilterOperators +import opensavvy.ktmongo.dsl.models.Count /** * Interface grouping MongoDB operations relating to counting documents. @@ -56,7 +56,7 @@ interface CountOperations : BaseOperations { * - [Official documentation](https://www.mongodb.com/docs/manual/reference/method/db.collection.countDocuments/) */ suspend fun count( - predicate: FilterOperators.() -> Unit + predicate: Count.() -> Unit ): Long /** diff --git a/driver-coroutines/src/jvmMain/kotlin/JvmMongoCollection.kt b/driver-coroutines/src/jvmMain/kotlin/JvmMongoCollection.kt index 58c31518..079e4e4e 100644 --- a/driver-coroutines/src/jvmMain/kotlin/JvmMongoCollection.kt +++ b/driver-coroutines/src/jvmMain/kotlin/JvmMongoCollection.kt @@ -21,7 +21,11 @@ import opensavvy.ktmongo.bson.BsonContext import opensavvy.ktmongo.bson.buildBsonDocument import opensavvy.ktmongo.dsl.LowLevelApi import opensavvy.ktmongo.dsl.expr.* -import opensavvy.ktmongo.dsl.expr.common.AbstractCompoundExpression +import opensavvy.ktmongo.dsl.expr.common.Expression +import opensavvy.ktmongo.dsl.models.Count +import opensavvy.ktmongo.dsl.options.CountOptions +import opensavvy.ktmongo.dsl.options.common.LimitOption +import opensavvy.ktmongo.dsl.options.common.option import org.bson.BsonDocument /** @@ -63,12 +67,16 @@ class JvmMongoCollection internal constructor( inner.countDocuments() @OptIn(LowLevelApi::class) - override suspend fun count(predicate: FilterOperators.() -> Unit): Long { - val filter = FilterExpression(context) + override suspend fun count(predicate: Count.() -> Unit): Long { + val options = CountOptions(context) + val model = Count(context, options) .apply(predicate) - .toBsonDocument() - return inner.countDocuments(filter) + return inner.countDocuments( + model.toBsonDocument(), + com.mongodb.client.model.CountOptions() + .limit(options.option()?.toInt() ?: 0) + ) } override suspend fun countEstimated(): Long = @@ -134,7 +142,7 @@ class JvmMongoCollection internal constructor( } @OptIn(LowLevelApi::class) -private fun AbstractCompoundExpression.toBsonDocument(): BsonDocument = +private fun Expression.toBsonDocument(): BsonDocument = buildBsonDocument { writeTo(this) } diff --git a/driver-sync/src/commonMain/kotlin/FilteredCollection.kt b/driver-sync/src/commonMain/kotlin/FilteredCollection.kt index 2009b302..77d23778 100644 --- a/driver-sync/src/commonMain/kotlin/FilteredCollection.kt +++ b/driver-sync/src/commonMain/kotlin/FilteredCollection.kt @@ -21,6 +21,7 @@ import opensavvy.ktmongo.dsl.LowLevelApi import opensavvy.ktmongo.dsl.expr.FilterOperators import opensavvy.ktmongo.dsl.expr.UpdateOperators import opensavvy.ktmongo.dsl.expr.UpsertOperators +import opensavvy.ktmongo.dsl.models.Count private class FilteredCollection( private val upstream: MongoCollection, @@ -43,7 +44,7 @@ private class FilteredCollection( override fun count(): Long = upstream.count(globalFilter) - override fun count(predicate: FilterOperators.() -> Unit): Long = + override fun count(predicate: Count.() -> Unit): Long = upstream.count { globalFilter() predicate() diff --git a/driver-sync/src/commonMain/kotlin/operations/CountOperations.kt b/driver-sync/src/commonMain/kotlin/operations/CountOperations.kt index 41feb675..4ce55937 100644 --- a/driver-sync/src/commonMain/kotlin/operations/CountOperations.kt +++ b/driver-sync/src/commonMain/kotlin/operations/CountOperations.kt @@ -16,7 +16,7 @@ package opensavvy.ktmongo.sync.operations -import opensavvy.ktmongo.dsl.expr.FilterOperators +import opensavvy.ktmongo.dsl.models.Count /** * Interface grouping MongoDB operations relating to counting documents. @@ -56,7 +56,7 @@ interface CountOperations : BaseOperations { * - [Official documentation](https://www.mongodb.com/docs/manual/reference/method/db.collection.countDocuments/) */ fun count( - predicate: FilterOperators.() -> Unit + predicate: Count.() -> Unit ): Long /** diff --git a/driver-sync/src/jvmMain/kotlin/JvmMongoCollection.kt b/driver-sync/src/jvmMain/kotlin/JvmMongoCollection.kt index 682a9d0b..c754763e 100644 --- a/driver-sync/src/jvmMain/kotlin/JvmMongoCollection.kt +++ b/driver-sync/src/jvmMain/kotlin/JvmMongoCollection.kt @@ -21,7 +21,11 @@ import opensavvy.ktmongo.bson.BsonContext import opensavvy.ktmongo.bson.buildBsonDocument import opensavvy.ktmongo.dsl.LowLevelApi import opensavvy.ktmongo.dsl.expr.* -import opensavvy.ktmongo.dsl.expr.common.AbstractCompoundExpression +import opensavvy.ktmongo.dsl.expr.common.Expression +import opensavvy.ktmongo.dsl.models.Count +import opensavvy.ktmongo.dsl.options.CountOptions +import opensavvy.ktmongo.dsl.options.common.LimitOption +import opensavvy.ktmongo.dsl.options.common.option import org.bson.BsonDocument /** @@ -63,12 +67,16 @@ class JvmMongoCollection internal constructor( inner.countDocuments() @OptIn(LowLevelApi::class) - override fun count(predicate: FilterOperators.() -> Unit): Long { - val filter = FilterExpression(context) + override fun count(predicate: Count.() -> Unit): Long { + val options = CountOptions(context) + val model = Count(context, options) .apply(predicate) - .toBsonDocument() - return inner.countDocuments(filter) + return inner.countDocuments( + model.toBsonDocument(), + com.mongodb.client.model.CountOptions() + .limit(options.option()?.toInt() ?: 0) + ) } override fun countEstimated(): Long = @@ -134,7 +142,7 @@ class JvmMongoCollection internal constructor( } @OptIn(LowLevelApi::class) -private fun AbstractCompoundExpression.toBsonDocument(): BsonDocument = +private fun Expression.toBsonDocument(): BsonDocument = buildBsonDocument { writeTo(this) } diff --git a/dsl/src/commonMain/kotlin/models/Count.kt b/dsl/src/commonMain/kotlin/models/Count.kt new file mode 100644 index 00000000..1cb633b1 --- /dev/null +++ b/dsl/src/commonMain/kotlin/models/Count.kt @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2024, 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.models + +import opensavvy.ktmongo.bson.BsonContext +import opensavvy.ktmongo.dsl.KtMongoDsl +import opensavvy.ktmongo.dsl.expr.FilterExpression +import opensavvy.ktmongo.dsl.expr.FilterOperators +import opensavvy.ktmongo.dsl.options.CountOptions + +/** + * Counting a number of documents in a collection. + * + * ### Example + * + * ```kotlin + * users.count { + * options { + * limit(99) + * } + * + * User::age lt 18 + * } + * ``` + * + * @see FilterOperators Filter operators + * @see CountOptions Options + */ +@KtMongoDsl +class Count( + context: BsonContext, + val options: CountOptions, +) : FilterOperators by FilterExpression(context) { + + @KtMongoDsl + fun options(block: CountOptions.() -> Unit) { + options.block() + } +} -- 2.51.2 From b6bcae0537789fce515c6bb8bb16e723fa780e4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Wed, 6 Nov 2024 22:00:58 +0100 Subject: [PATCH 10/10] refactor(dsl): Move the option generation to :dsl --- .../src/jvmMain/kotlin/JvmMongoCollection.kt | 6 ++--- .../src/jvmMain/kotlin/JvmMongoCollection.kt | 6 ++--- dsl/build.gradle.kts | 4 +++ dsl/src/jvmMain/kotlin/Marker.kt | 17 +++++++++++++ .../kotlin/options/CountOptions.jvm.kt | 25 +++++++++++++++++++ gradle/libs.versions.toml | 1 + 6 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 dsl/src/jvmMain/kotlin/Marker.kt create mode 100644 dsl/src/jvmMain/kotlin/options/CountOptions.jvm.kt diff --git a/driver-coroutines/src/jvmMain/kotlin/JvmMongoCollection.kt b/driver-coroutines/src/jvmMain/kotlin/JvmMongoCollection.kt index 079e4e4e..241410c5 100644 --- a/driver-coroutines/src/jvmMain/kotlin/JvmMongoCollection.kt +++ b/driver-coroutines/src/jvmMain/kotlin/JvmMongoCollection.kt @@ -24,8 +24,7 @@ import opensavvy.ktmongo.dsl.expr.* import opensavvy.ktmongo.dsl.expr.common.Expression import opensavvy.ktmongo.dsl.models.Count import opensavvy.ktmongo.dsl.options.CountOptions -import opensavvy.ktmongo.dsl.options.common.LimitOption -import opensavvy.ktmongo.dsl.options.common.option +import opensavvy.ktmongo.dsl.options.toJava import org.bson.BsonDocument /** @@ -74,8 +73,7 @@ class JvmMongoCollection internal constructor( return inner.countDocuments( model.toBsonDocument(), - com.mongodb.client.model.CountOptions() - .limit(options.option()?.toInt() ?: 0) + options.toJava(), ) } diff --git a/driver-sync/src/jvmMain/kotlin/JvmMongoCollection.kt b/driver-sync/src/jvmMain/kotlin/JvmMongoCollection.kt index c754763e..300dcc91 100644 --- a/driver-sync/src/jvmMain/kotlin/JvmMongoCollection.kt +++ b/driver-sync/src/jvmMain/kotlin/JvmMongoCollection.kt @@ -24,8 +24,7 @@ import opensavvy.ktmongo.dsl.expr.* import opensavvy.ktmongo.dsl.expr.common.Expression import opensavvy.ktmongo.dsl.models.Count import opensavvy.ktmongo.dsl.options.CountOptions -import opensavvy.ktmongo.dsl.options.common.LimitOption -import opensavvy.ktmongo.dsl.options.common.option +import opensavvy.ktmongo.dsl.options.toJava import org.bson.BsonDocument /** @@ -74,8 +73,7 @@ class JvmMongoCollection internal constructor( return inner.countDocuments( model.toBsonDocument(), - com.mongodb.client.model.CountOptions() - .limit(options.option()?.toInt() ?: 0) + options.toJava(), ) } diff --git a/dsl/build.gradle.kts b/dsl/build.gradle.kts index 1fc953b2..e3bc39e8 100644 --- a/dsl/build.gradle.kts +++ b/dsl/build.gradle.kts @@ -30,6 +30,10 @@ kotlin { api(projects.annotations) } + sourceSets.jvmMain.dependencies { + api(libs.mongodb.core.jvm) + } + sourceSets.commonTest.dependencies { implementation(libs.prepared) implementation(opensavvyConventions.aligned.kotlin.test) diff --git a/dsl/src/jvmMain/kotlin/Marker.kt b/dsl/src/jvmMain/kotlin/Marker.kt new file mode 100644 index 00000000..78149a84 --- /dev/null +++ b/dsl/src/jvmMain/kotlin/Marker.kt @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2024, 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 diff --git a/dsl/src/jvmMain/kotlin/options/CountOptions.jvm.kt b/dsl/src/jvmMain/kotlin/options/CountOptions.jvm.kt new file mode 100644 index 00000000..978779c6 --- /dev/null +++ b/dsl/src/jvmMain/kotlin/options/CountOptions.jvm.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2024, 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.options + +import opensavvy.ktmongo.dsl.LowLevelApi +import opensavvy.ktmongo.dsl.options.common.LimitOption +import opensavvy.ktmongo.dsl.options.common.option + +@LowLevelApi +fun CountOptions<*>.toJava(): com.mongodb.client.model.CountOptions = com.mongodb.client.model.CountOptions() + .limit(option()?.toInt() ?: 0) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c8968f13..34b0e730 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -15,6 +15,7 @@ kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", prepared = { module = "dev.opensavvy.prepared:runner-kotest", version.ref = "prepared" } mongodb-bson-jvm = { module = "org.mongodb:bson", version.ref = "mongodb-bson-jvm" } +mongodb-core-jvm = { module = "org.mongodb:mongodb-driver-core", version.ref = "mongodb-driver" } mongodb-coroutines-jvm = { module = "org.mongodb:mongodb-driver-kotlin-coroutine", version.ref = "mongodb-driver" } mongodb-sync-jvm = { module = "org.mongodb:mongodb-driver-kotlin-sync", version.ref = "mongodb-driver" }