diff --git a/dsl/src/commonMain/kotlin/path/Path.kt b/dsl/src/commonMain/kotlin/path/Path.kt index 7117446e..a5236c26 100644 --- a/dsl/src/commonMain/kotlin/path/Path.kt +++ b/dsl/src/commonMain/kotlin/path/Path.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, OpenSavvy, 4SH and contributors. + * 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. @@ -48,7 +48,7 @@ sealed class PathSegment { */ @LowLevelApi data class Indexed(val index: Int) : PathSegment() { - override fun toString() = "\$$index" + override fun toString() = "$index" } /** diff --git a/dsl/src/commonTest/kotlin/expr/update/FieldUpdateTest.kt b/dsl/src/commonTest/kotlin/expr/update/FieldUpdateTest.kt index 89c9ecc6..4a548a9e 100644 --- a/dsl/src/commonTest/kotlin/expr/update/FieldUpdateTest.kt +++ b/dsl/src/commonTest/kotlin/expr/update/FieldUpdateTest.kt @@ -201,7 +201,7 @@ class FieldUpdateTest : PreparedSpec({ { "$rename": { "bestFriend.name": "name", - "friends.$0.name": "friends.$1.name" + "friends.0.name": "friends.1.name" } } """.trimIndent() diff --git a/dsl/src/commonTest/kotlin/path/FieldTest.kt b/dsl/src/commonTest/kotlin/path/FieldTest.kt index 57706d92..7297449d 100644 --- a/dsl/src/commonTest/kotlin/path/FieldTest.kt +++ b/dsl/src/commonTest/kotlin/path/FieldTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, OpenSavvy, 4SH and contributors. + * 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. @@ -66,13 +66,13 @@ class FieldTest : PreparedSpec({ suite("Indexed access") { test("Indexed object") { with(TestFieldDsl()) { - User::friends[0] shouldHavePath "friends.$0" + User::friends[0] shouldHavePath "friends.0" } } test("Indexed nested field") { with(TestFieldDsl()) { - User::friends[0] / Friend::name shouldHavePath "friends.$0.name" + User::friends[0] / Friend::name shouldHavePath "friends.0.name" } } } diff --git a/dsl/src/commonTest/kotlin/path/PathTest.kt b/dsl/src/commonTest/kotlin/path/PathTest.kt index 07c9f516..8df36ad6 100644 --- a/dsl/src/commonTest/kotlin/path/PathTest.kt +++ b/dsl/src/commonTest/kotlin/path/PathTest.kt @@ -18,6 +18,7 @@ package opensavvy.ktmongo.dsl.path import opensavvy.ktmongo.dsl.LowLevelApi import opensavvy.ktmongo.dsl.path.PathSegment.* +import opensavvy.ktmongo.dsl.path.PathSegment.Field import opensavvy.prepared.runner.kotest.PreparedSpec @OptIn(LowLevelApi::class) @@ -36,7 +37,7 @@ class PathTest : PreparedSpec({ } test("Indexed") { - check((Path("test") / Indexed(3) / Field("bar")).toString() == "test.$3.bar") + check((Path("test") / Indexed(3) / Field("bar")).toString() == "test.3.bar") } test("Positional") { -- 2.51.2 From 04f01b8791588f35928fd24799dbe2bdf1e3cf97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Sat, 30 Nov 2024 17:26:45 +0100 Subject: [PATCH 2/3] feat(dsl): Create the filter operators isEmpty and isNotEmpty --- .../commonMain/kotlin/expr/FilterOperators.kt | 114 +++++++++++++++++- .../kotlin/expr/filter/ArrayFilterTest.kt | 24 ++++ test/src/commonTest/kotlin/ArraysTest.kt | 59 +++++++++ 3 files changed, 194 insertions(+), 3 deletions(-) create mode 100644 test/src/commonTest/kotlin/ArraysTest.kt diff --git a/dsl/src/commonMain/kotlin/expr/FilterOperators.kt b/dsl/src/commonMain/kotlin/expr/FilterOperators.kt index 53b3732c..1e023870 100644 --- a/dsl/src/commonMain/kotlin/expr/FilterOperators.kt +++ b/dsl/src/commonMain/kotlin/expr/FilterOperators.kt @@ -22,9 +22,7 @@ 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 opensavvy.ktmongo.dsl.path.* import kotlin.jvm.JvmName import kotlin.reflect.KProperty1 @@ -621,6 +619,116 @@ interface FilterOperators : CompoundExpression, FieldDsl { this.field.doesNotExist() } + /** + * Matches documents in which an array is empty or absent. + * + * ### Example + * + * Return all users that have no grades (either an empty array, or the `grades` field is absent): + * + * ```kotlin + * class User( + * val name: String?, + * val grades: List + * ) + * + * collection.find { + * User::grades.isEmpty() + * } + * ``` + * + * @see exists + * @see isNull + * @see isNotEmpty + */ + @OptIn(LowLevelApi::class) + @KtMongoDsl + fun Field>.isEmpty() { + FieldImpl(path / PathSegment.Indexed(0)).doesNotExist() + } + + /** + * Matches documents in which an array is empty or absent. + * + * ### Example + * + * Return all users that have no grades (either an empty array, or the `grades` field is absent): + * + * ```kotlin + * class User( + * val name: String?, + * val grades: List + * ) + * + * collection.find { + * User::grades.isEmpty() + * } + * ``` + * + * @see exists + * @see isNull + * @see isNotEmpty + */ + @KtMongoDsl + fun KProperty1>.isEmpty() { + this.field.isEmpty() + } + + /** + * Matches documents in which an array is not empty. + * + * ### Example + * + * Return all users that have one or more grades. + * + * ```kotlin + * class User( + * val name: String?, + * val grades: List + * ) + * + * collection.find { + * User::grades.isNotEmpty() + * } + * ``` + * + * @see exists + * @see isNotNull + * @see isEmpty + */ + @OptIn(LowLevelApi::class) + @KtMongoDsl + fun Field>.isNotEmpty() { + FieldImpl(path / PathSegment.Indexed(0)).exists() + } + + /** + * Matches documents in which an array is not empty. + * + * ### Example + * + * Return all users that have one or more grades. + * + * ```kotlin + * class User( + * val name: String?, + * val grades: List + * ) + * + * collection.find { + * User::grades.isNotEmpty() + * } + * ``` + * + * @see exists + * @see isNotNull + * @see isEmpty + */ + @KtMongoDsl + fun KProperty1>.isNotEmpty() { + this.field.isNotEmpty() + } + // endregion // region $type diff --git a/dsl/src/commonTest/kotlin/expr/filter/ArrayFilterTest.kt b/dsl/src/commonTest/kotlin/expr/filter/ArrayFilterTest.kt index 636f9899..304eebdb 100644 --- a/dsl/src/commonTest/kotlin/expr/filter/ArrayFilterTest.kt +++ b/dsl/src/commonTest/kotlin/expr/filter/ArrayFilterTest.kt @@ -187,4 +187,28 @@ class ArrayFilterTest : PreparedSpec({ } """.trimIndent() } + + test("isEmpty") { + filter { + User::grades.isEmpty() + } shouldBeBson """ + { + "grades.0": { + "$exists": false + } + } + """.trimIndent() + } + + test("isNotEmpty") { + filter { + User::grades.isNotEmpty() + } shouldBeBson """ + { + "grades.0": { + "$exists": true + } + } + """.trimIndent() + } }) diff --git a/test/src/commonTest/kotlin/ArraysTest.kt b/test/src/commonTest/kotlin/ArraysTest.kt new file mode 100644 index 00000000..6b3b9603 --- /dev/null +++ b/test/src/commonTest/kotlin/ArraysTest.kt @@ -0,0 +1,59 @@ +/* + * 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.sync + +import kotlinx.serialization.Serializable +import opensavvy.ktmongo.test.testCollection +import opensavvy.prepared.runner.kotest.PreparedSpec + +class ArraysTest : PreparedSpec({ + @Serializable + data class User( + val name: String, + val grades: List = emptyList(), + val friends: List = emptyList(), + ) + + val users by testCollection("arrays") + + suite("Not empty array") { + val cases = mapOf( + "Array has two elements" to User("Bob", grades = listOf(1, 2)), + "Array has one element" to User("Bob", grades = listOf(1)) + ) + + for ((case, user) in cases) test(case) { + users().insertOne(user) + + check(users().findOne { User::grades.isNotEmpty() } == user) + } + } + + suite("Empty array") { + val cases = mapOf( + "Array is not present" to User("Marcel"), + "Array is empty" to User("Marcel", grades = emptyList()), + ) + + for ((case, user) in cases) test(case) { + users().insertOne(user) + + check(users().findOne { User::grades.isEmpty() } == user) + } + } + +}) -- 2.51.2 From 253be9da94d7d89337c88d75e2ea49e262bcd4ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Sat, 30 Nov 2024 17:28:17 +0100 Subject: [PATCH 3/3] build(idea): Update the MongoDB driver --- .idea/dataSources.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml index a5136fc1..44869e3b 100644 --- a/.idea/dataSources.xml +++ b/.idea/dataSources.xml @@ -2,7 +2,7 @@ - mongo + mongo.4 true com.dbschema.MongoJdbcDriver mongodb://localhost:27017