Skip to content
This repository has been archived by the owner on Oct 2, 2024. It is now read-only.

Draft: advanced updates #26

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions demo/src/main/kotlin/Main.kt
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
package fr.qsh.ktmongo.demo

import com.mongodb.kotlin.client.MongoClient
import fr.qsh.ktmongo.dsl.path.div
import fr.qsh.ktmongo.sync.asKtMongo
import fr.qsh.ktmongo.sync.filter

data class Jedi(
val name: String,
val age: Int,
val level: Int,
val friends: List<Friend>,
)

data class Friend(
val name: String,
)

fun main() {
Expand All @@ -30,4 +36,8 @@ fun main() {
Jedi::age set 19
Jedi::level inc 1
}

collection.find {
Jedi::friends.items() / Friend::name eq "Foo"
}
}
76 changes: 76 additions & 0 deletions dsl/src/main/kotlin/expr/FilterExpression.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import fr.qsh.ktmongo.dsl.KtMongoDsl
import fr.qsh.ktmongo.dsl.LowLevelApi
import fr.qsh.ktmongo.dsl.expr.common.CompoundExpression
import fr.qsh.ktmongo.dsl.expr.common.Expression
import fr.qsh.ktmongo.dsl.path.PropertyPath
import fr.qsh.ktmongo.dsl.path.path
import fr.qsh.ktmongo.dsl.writeArray
import fr.qsh.ktmongo.dsl.writeDocument
Expand Down Expand Up @@ -280,6 +281,9 @@ class FilterExpression<T>(
* ### External resources
*
* - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/eq/)
*
* @see eqNotNull To only filter when the value is non-null.
* @see contains To make an equality check on one of the elements of a collection.
*/
@KtMongoDsl
infix fun <@OnlyInputTypes V> KProperty1<T, V>.eq(value: V) {
Expand Down Expand Up @@ -351,6 +355,78 @@ class FilterExpression<T>(
this { ne(value) }
}

// endregion
// region Predicates on array elements

/**
* Allows to declare filters on the items of the specified field.
*
* ### Example
*
* This example will return all users who have at least one grade above 10:
* ```kotlin
* class User(
* val name: String,
* val grades: List<Int>,
* )
*
* collection.find {
* User::grades.items() gte 10
* }
* ```
*
* ### Behavior with non-array fields
*
* TODO
*
* ### Using multiple criteria
*
* @see fr.qsh.ktmongo.dsl.path.get Refer to a specific item by its index.
*/
@OptIn(LowLevelApi::class)
fun <@OnlyInputTypes V> KProperty1<T, Collection<V>>.items(): KProperty1<T, V> =
PropertyPath(
path = this.path(),
backingProperty = this,
)

/**
* Matches documents where one of the items in the specified field is [value].
*
* ### Example
*
* ```kotlin
* class User(
* val name: String,
* val addresses: List<String>,
* )
*
* collection.find {
* User::addresses contains "Some address"
* }
* ```
*
* All documents for which one of the `addresses` is equal to "Some address" are returned.
*
* ### Behavior with non-array fields
*
* MongoDB doesn't make a difference between "checking if one of the elements of an array matches the predicate" and "checking if the field matches the predicate".
*
* In the previous example, if a document had a field named `addresses` that was a `String` (**not** a `List<String>`), and its value was "Some address", it would be returned as well.
*
* ### External resources
*
* - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/eq/#array-element-equals-a-value)
*
* @see eq To make an equality check on the array itself, instead of one its elements.
* @see items Perform other kinds of filters on one of the items of an array.
* @see UpdateExpression.matched Update the element item matched by this function.
*/
@KtMongoDsl
infix fun <@OnlyInputTypes V> KProperty1<T, Collection<V>>.contains(value: V) {
this.items() eq value
}

// endregion
// region $exists

Expand Down
7 changes: 7 additions & 0 deletions dsl/src/main/kotlin/expr/PredicateExpression.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ import org.bson.codecs.configuration.CodecRegistry
* DSL for MongoDB operators that are used as predicates in conditions in a context where the targeted field is already
* specified.
*/
// TODO: PredicateExpression should allow further property nesting
// {
// "foo": {
// "$gte": 10,
// "bar": 11
// }
// }
@KtMongoDsl
class PredicateExpression<T>(
codec: CodecRegistry,
Expand Down
37 changes: 35 additions & 2 deletions dsl/src/main/kotlin/expr/UpdateExpression.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ import fr.qsh.ktmongo.dsl.LowLevelApi
import fr.qsh.ktmongo.dsl.expr.common.CompoundExpression
import fr.qsh.ktmongo.dsl.expr.common.Expression
import fr.qsh.ktmongo.dsl.expr.common.acceptAll
import fr.qsh.ktmongo.dsl.path.Path
import fr.qsh.ktmongo.dsl.path.path
import fr.qsh.ktmongo.dsl.path.*
import fr.qsh.ktmongo.dsl.writeDocument
import fr.qsh.ktmongo.dsl.writeObject
import org.bson.BsonWriter
Expand Down Expand Up @@ -324,6 +323,40 @@ class UpdateExpression<T>(
}
}

// endregion
// region Array indexing operators: $, $[]

/**
* Selects the item matched by [contains][FilterExpression.contains].
*
* ### Example
*
* ```kotlin
* class User(
* val name: String,
* val friends: List<Friend>,
* )
*
* class Friend(
* val name: String,
* val score: Int,
* )
*
* collection.filter {
* User::name eq "Foo"
* User::friends contains {
* Friend:: TODO
* }
* }
* ```
*/
@OptIn(LowLevelApi::class)
fun <T0, T1> KProperty1<T0, Collection<T1>>.matched(): KProperty1<T0, T1> =
PropertyPath(
path = this.path() + PathSegment.Positional,
backingProperty = this,
)

// endregion

companion object {
Expand Down
2 changes: 1 addition & 1 deletion dsl/src/main/kotlin/path/PropertyPath.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import kotlin.reflect.*
*/
@LowLevelApi
@Suppress("NO_REFLECTION_IN_CLASS_PATH") // None of the functions are called by our code. The caller is responsible for fixing this.
private class PropertyPath<RootParent, Value>(
internal class PropertyPath<RootParent, Value>(
/**
* The path of this property.
*
Expand Down
44 changes: 39 additions & 5 deletions dsl/src/test/kotlin/expr/FilterExpressionTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ class FilterExpressionTest : FunSpec({
val type = "\$type"
val not = "\$not"
val isOneOf = "\$in"
val gt = "\$gt"
val gte = "\$gte"
val lt = "\$lt"
val lte = "\$lte"

context("Operator $eq") {
test("Integer") {
Expand Down Expand Up @@ -376,11 +380,6 @@ class FilterExpressionTest : FunSpec({
}

context("Comparison operators") {
val gt = "\$gt"
val gte = "\$gte"
val lt = "\$lt"
val lte = "\$lte"

test("int $gt") {
filter {
User::age gt 12
Expand Down Expand Up @@ -429,4 +428,39 @@ class FilterExpressionTest : FunSpec({
""".trimIndent()
}
}

context("Array operators") {
class Grades(
val userId: String,
val grades: List<Int>,
)

test("Search for a specific grade") {
filter {
Grades::grades contains 12
} shouldBeBson """
{
"grades": {
"$eq": 12
}
}
""".trimIndent()
}

test("Search for a set of grades") {
filter {
Grades::grades contains {
gt(10)
lte(12)
}
} shouldBeBson """
{
"grades": {
"$gt": 10,
"$lte": 12
}
}
""".trimIndent()
}
}
})
Loading