diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 4e4a4efef2b4bbc71f0eece7a52e27dfc0d3d06e..da4bcfeef39a5ba31c4579a5ea571b8d2f577632 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -63,8 +63,17 @@ workflow:
# endregion
# region Check
+.with-mongo:
+ parallel:
+ matrix:
+ - mongodb: [ mongo:6.0.19, mongo:7.0.15, mongo:8.0.3 ]
+
+ services:
+ - name: $mongodb
+ alias: mongo
+
check[jvm]:
- extends: [ .kotlin-jvm ]
+ extends: [ .kotlin-jvm, .with-mongo ]
stage: test
script:
diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml
new file mode 100644
index 0000000000000000000000000000000000000000..a5136fc14f795ad843b61bea0d64c23286d994e9
--- /dev/null
+++ b/.idea/dataSources.xml
@@ -0,0 +1,17 @@
+
+
+
+
+ mongo
+ true
+ com.dbschema.MongoJdbcDriver
+ mongodb://localhost:27017
+
+
+
+
+
+ $ProjectFileDir$
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/Check.xml b/.idea/runConfigurations/Check.xml
index 3b214a8cc4ea6e4b9eb2fe0653173139293c3985..bab2591d65799b4da361aaf4d93be508610996fc 100644
--- a/.idea/runConfigurations/Check.xml
+++ b/.idea/runConfigurations/Check.xml
@@ -19,6 +19,8 @@
true
false
true
-
+
+
+
-
\ No newline at end of file
+
diff --git a/.idea/runConfigurations/MongoDB.xml b/.idea/runConfigurations/MongoDB.xml
new file mode 100644
index 0000000000000000000000000000000000000000..bb7f2e6f8c662e6ae52544237ea94214270a939c
--- /dev/null
+++ b/.idea/runConfigurations/MongoDB.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/startup.xml b/.idea/startup.xml
new file mode 100644
index 0000000000000000000000000000000000000000..5994d1a8e4cd369c0607e68b99264fe126dbe5f2
--- /dev/null
+++ b/.idea/startup.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/bson/src/commonMain/kotlin/Bson.kt b/bson/src/commonMain/kotlin/Bson.kt
index 11630067c289d4ebf18737ea0d3b4627e0623960..9c7a546386ba6611d09f9f4e3e99c8104f6757b3 100644
--- a/bson/src/commonMain/kotlin/Bson.kt
+++ b/bson/src/commonMain/kotlin/Bson.kt
@@ -19,8 +19,6 @@ package opensavvy.ktmongo.bson
/**
* A BSON document.
*
- * A BSON value *always* has a root document.
- *
* To create instances of this class, see [buildBsonDocument].
*/
expect class Bson {
@@ -30,3 +28,16 @@ expect class Bson {
*/
override fun toString(): String
}
+
+/**
+ * A BSON array.
+ *
+ * To create instances of this class, see [buildBsonArray].
+ */
+expect class BsonArray {
+
+ /**
+ * JSON representation of this [BsonArray] object, as a [String].
+ */
+ override fun toString(): String
+}
diff --git a/bson/src/commonMain/kotlin/BsonWriter.kt b/bson/src/commonMain/kotlin/BsonWriter.kt
index 3a0360b59ff5ff543cbdfb4a7e048e4d73cc7ea3..5479a79cad3aabc561d4ff7eb233520dc22b7367 100644
--- a/bson/src/commonMain/kotlin/BsonWriter.kt
+++ b/bson/src/commonMain/kotlin/BsonWriter.kt
@@ -23,6 +23,13 @@ import opensavvy.ktmongo.dsl.LowLevelApi
@DslMarker
annotation class BsonWriterDsl
+/**
+ * Parent interface for type parameters that can accept either [BsonValueWriter] or [BsonFieldWriter].
+ */
+@LowLevelApi
+@BsonWriterDsl
+sealed interface AnyBsonWriter
+
/**
* Generator of BSON values.
*
@@ -31,11 +38,11 @@ annotation class BsonWriterDsl
*
* To write fields in a BSON document, see [BsonFieldWriter].
*
- * Instances of this interface are commonly obtained by calling the [buildBsonDocument] function.
+ * Instances of this interface are commonly obtained by calling the [buildBsonArray] function.
*/
@LowLevelApi
@BsonWriterDsl
-interface BsonValueWriter {
+interface BsonValueWriter : AnyBsonWriter {
@LowLevelApi fun writeBoolean(value: Boolean)
@LowLevelApi fun writeDouble(value: Double)
@LowLevelApi fun writeInt32(value: Int)
@@ -87,7 +94,7 @@ interface BsonValueWriter {
*/
@LowLevelApi
@BsonWriterDsl
-interface BsonFieldWriter {
+interface BsonFieldWriter : AnyBsonWriter {
@LowLevelApi fun write(name: String, block: BsonValueWriter.() -> Unit)
@LowLevelApi fun writeBoolean(name: String, value: Boolean)
@@ -168,3 +175,32 @@ interface BsonFieldWriter {
*/
@LowLevelApi
expect fun buildBsonDocument(block: BsonFieldWriter.() -> Unit): Bson
+
+/**
+ * Instantiates a new [Bson] array.
+ *
+ * ### Example
+ *
+ * To create the following BSON array:
+ * ```bson
+ * [
+ * 12,
+ * null,
+ * {
+ * "name": "Barry"
+ * }
+ * ]
+ * ```
+ * use the code:
+ * ```kotlin
+ * buildBsonArray {
+ * writeInt32(12)
+ * writeNull()
+ * writeDocument {
+ * writeString("name", "Barry")
+ * }
+ * }
+ * ```
+ */
+@LowLevelApi
+expect fun buildBsonArray(block: BsonValueWriter.() -> Unit): BsonArray
diff --git a/bson/src/jsMain/kotlin/Bson.js.kt b/bson/src/jsMain/kotlin/Bson.js.kt
index 77862ee42bf5ffc22bb229462163d9258e91cf31..268d97fe89ac4f1bec4da19761d1a81a9bbe15ec 100644
--- a/bson/src/jsMain/kotlin/Bson.js.kt
+++ b/bson/src/jsMain/kotlin/Bson.js.kt
@@ -19,3 +19,7 @@ package opensavvy.ktmongo.bson
actual class Bson {
// TODO: implement the BSON type on top of the NPM 'bson' library
}
+
+actual class BsonArray {
+ // TODO: implement the BSON array type on top of the NPM 'bson' library
+}
diff --git a/bson/src/jsMain/kotlin/BsonWriter.js.kt b/bson/src/jsMain/kotlin/BsonWriter.js.kt
index dec54634a9ba76623346a01b466873b761b7a961..68abe5483942f84cc6a26fa35a7ed3a42173209d 100644
--- a/bson/src/jsMain/kotlin/BsonWriter.js.kt
+++ b/bson/src/jsMain/kotlin/BsonWriter.js.kt
@@ -20,3 +20,8 @@ actual fun buildBsonDocument(block: BsonFieldWriter.() -> Unit): Bson {
console.error("buildBsonDocument is not implemented on this platform")
return Bson()
}
+
+actual fun buildBsonArray(block: BsonValueWriter.() -> Unit): BsonArray {
+ console.error("buildBsonArray is not implemented on this platform")
+ return BsonArray()
+}
diff --git a/bson/src/jvmMain/kotlin/Bson.jvm.kt b/bson/src/jvmMain/kotlin/Bson.jvm.kt
index ea2d8fa2bf9ddc94cd280910566baf895768b132..6227ee4b3d41eb6bb6e1eed368da417930855987 100644
--- a/bson/src/jvmMain/kotlin/Bson.jvm.kt
+++ b/bson/src/jvmMain/kotlin/Bson.jvm.kt
@@ -16,6 +16,9 @@
package opensavvy.ktmongo.bson
+import org.bson.BsonArray
import org.bson.BsonDocument
actual typealias Bson = BsonDocument
+
+actual typealias BsonArray = BsonArray
diff --git a/bson/src/jvmMain/kotlin/BsonWriter.jvm.kt b/bson/src/jvmMain/kotlin/BsonWriter.jvm.kt
index d25313be21dfab87579896ca066cf32944451b1e..5261562fd0e7756414d101ccec8a6e9424b5ff7d 100644
--- a/bson/src/jvmMain/kotlin/BsonWriter.jvm.kt
+++ b/bson/src/jvmMain/kotlin/BsonWriter.jvm.kt
@@ -235,3 +235,124 @@ actual fun buildBsonDocument(block: BsonFieldWriter.() -> Unit): Bson {
return document
}
+
+@LowLevelApi
+private class JavaRootArrayWriter(
+ private val array: BsonArray,
+) : BsonValueWriter {
+ @LowLevelApi
+ override fun writeBoolean(value: Boolean) {
+ array.add(BsonBoolean(value))
+ }
+
+ @LowLevelApi
+ override fun writeDouble(value: Double) {
+ array.add(BsonDouble(value))
+ }
+
+ @LowLevelApi
+ override fun writeInt32(value: Int) {
+ array.add(BsonInt32(value))
+ }
+
+ @LowLevelApi
+ override fun writeInt64(value: Long) {
+ array.add(BsonInt64(value))
+ }
+
+ @LowLevelApi
+ override fun writeDecimal128(value: Decimal128) {
+ array.add(BsonDecimal128(value))
+ }
+
+ @LowLevelApi
+ override fun writeDateTime(value: Long) {
+ array.add(BsonDateTime(value))
+ }
+
+ @LowLevelApi
+ override fun writeNull() {
+ array.add(BsonNull())
+ }
+
+ @LowLevelApi
+ override fun writeObjectId(value: ObjectId) {
+ array.add(BsonObjectId(value))
+ }
+
+ @LowLevelApi
+ override fun writeRegularExpression(pattern: String, options: String) {
+ array.add(BsonRegularExpression(pattern, options))
+ }
+
+ @LowLevelApi
+ override fun writeString(value: String) {
+ array.add(BsonString(value))
+ }
+
+ @LowLevelApi
+ override fun writeTimestamp(value: Long) {
+ array.add(BsonTimestamp(value))
+ }
+
+ @LowLevelApi
+ override fun writeSymbol(value: String) {
+ array.add(BsonSymbol(value))
+ }
+
+ @LowLevelApi
+ override fun writeUndefined() {
+ array.add(BsonUndefined())
+ }
+
+ @LowLevelApi
+ override fun writeDBPointer(namespace: String, id: ObjectId) {
+ array.add(BsonDbPointer(namespace, id))
+ }
+
+ @LowLevelApi
+ override fun writeJavaScriptWithScope(code: String) {
+ array.add(BsonJavaScript(code))
+ }
+
+ @LowLevelApi
+ override fun writeBinaryData(type: Byte, data: ByteArray) {
+ array.add(BsonBinary(type, data))
+ }
+
+ @LowLevelApi
+ override fun writeJavaScript(code: String) {
+ array.add(BsonJavaScript(code))
+ }
+
+ @LowLevelApi
+ override fun writeDocument(block: BsonFieldWriter.() -> Unit) {
+ array.add(buildBsonDocument(block))
+ }
+
+ @LowLevelApi
+ override fun writeArray(block: BsonValueWriter.() -> Unit) {
+ array.add(buildBsonArray(block))
+ }
+
+ @LowLevelApi
+ override fun writeObjectSafe(obj: T, context: BsonContext) {
+ val document = BsonDocument()
+
+ BsonDocumentWriter(document).use { writer ->
+ JavaBsonWriter(writer).writeObjectSafe(obj, context)
+ }
+
+ array.add(document)
+ }
+
+}
+
+@LowLevelApi
+actual fun buildBsonArray(block: BsonValueWriter.() -> Unit): BsonArray {
+ val array = BsonArray()
+
+ JavaRootArrayWriter(array).block()
+
+ return array
+}
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
new file mode 100644
index 0000000000000000000000000000000000000000..abbc08a8f338d1135ada25b4dfde1f16be3c62dc
--- /dev/null
+++ b/docker/docker-compose.yml
@@ -0,0 +1,7 @@
+version: "3.6"
+
+services:
+ mongo:
+ image: "mongo:8.0.1"
+ ports:
+ - "27017:27017"
diff --git a/driver-coroutines/src/commonMain/kotlin/FilteredCollection.kt b/driver-coroutines/src/commonMain/kotlin/FilteredCollection.kt
new file mode 100644
index 0000000000000000000000000000000000000000..68b1f0381ac033dd1e6977b949b3392b615a6014
--- /dev/null
+++ b/driver-coroutines/src/commonMain/kotlin/FilteredCollection.kt
@@ -0,0 +1,120 @@
+/*
+ * 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.coroutines
+
+import opensavvy.ktmongo.bson.BsonContext
+import opensavvy.ktmongo.dsl.LowLevelApi
+import opensavvy.ktmongo.dsl.expr.FilterExpression
+import opensavvy.ktmongo.dsl.expr.UpdateExpression
+
+private class FilteredCollection(
+ private val upstream: MongoCollection,
+ private val globalFilter: FilterExpression.() -> Unit,
+) : MongoCollection {
+
+ override fun find(): MongoIterable =
+ upstream.find(globalFilter)
+
+ override fun find(predicate: FilterExpression.() -> Unit): MongoIterable =
+ upstream.find {
+ globalFilter()
+ predicate()
+ }
+
+ @LowLevelApi
+ override val context: BsonContext
+ get() = upstream.context
+
+ override suspend fun count(): Long =
+ upstream.count(globalFilter)
+
+ override suspend fun count(predicate: FilterExpression.() -> Unit): Long =
+ upstream.count {
+ globalFilter()
+ predicate()
+ }
+
+ override suspend fun countEstimated(): Long =
+ count()
+
+ override suspend fun updateMany(filter: FilterExpression.() -> Unit, update: UpdateExpression.() -> Unit) =
+ upstream.updateMany(
+ filter = {
+ globalFilter()
+ filter()
+ },
+ update = update,
+ )
+
+ override suspend fun updateOne(filter: FilterExpression.() -> Unit, update: UpdateExpression.() -> Unit) =
+ upstream.updateOne(
+ filter = {
+ globalFilter()
+ filter()
+ },
+ update = update,
+ )
+
+ override suspend fun upsertOne(filter: FilterExpression.() -> Unit, update: UpdateExpression.() -> Unit) =
+ upstream.upsertOne(
+ filter = {
+ globalFilter()
+ filter()
+ },
+ update = update,
+ )
+
+ override suspend fun findOneAndUpdate(filter: FilterExpression.() -> Unit, update: UpdateExpression.() -> Unit): Document? =
+ upstream.findOneAndUpdate(
+ filter = {
+ globalFilter()
+ filter()
+ },
+ update = update,
+ )
+}
+
+/**
+ * Returns a filtered collection that only contains the elements that match [filter].
+ *
+ * This function creates a logical view of the collection: by itself, this function does nothing, and MongoDB is never
+ * aware of the existence of this logical view. However, operations invoked on the returned collection will only affect
+ * elements from the original that match the [filter].
+ *
+ * Unlike actual MongoDB views, which are read-only, collections returned by this function can also be used for write operations.
+ *
+ * ### Example
+ *
+ * A typical usage of this function is to reuse filters for multiple operations.
+ * For example, if you have a concept of logical deletion, this function can be used to hide deleted values.
+ *
+ * ```kotlin
+ * class Order(
+ * val id: String,
+ * val date: Instant,
+ * val deleted: Boolean,
+ * )
+ *
+ * val allOrders = database.getCollection("orders").asKtMongo()
+ * val activeOrders = allOrders.filter { Order::deleted ne true }
+ *
+ * allOrders.find() // Returns all orders, deleted or not
+ * activeOrders.find() // Only returns orders that are not logically deleted
+ * ```
+ */
+fun MongoCollection.filter(filter: FilterExpression.() -> Unit): MongoCollection =
+ FilteredCollection(this, filter)
diff --git a/driver-coroutines/src/commonMain/kotlin/operations/UpdateOperations.kt b/driver-coroutines/src/commonMain/kotlin/operations/UpdateOperations.kt
index 7065aadb29d8e6ca9757ff9603cb47680b03a039..e88840aa95c40d271f756e86309836272d9262af 100644
--- a/driver-coroutines/src/commonMain/kotlin/operations/UpdateOperations.kt
+++ b/driver-coroutines/src/commonMain/kotlin/operations/UpdateOperations.kt
@@ -16,6 +16,8 @@
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
@@ -45,6 +47,19 @@ interface UpdateOperations : BaseOperations {
* )
* ```
*
+ * ### Using filtered collections
+ *
+ * The following code is equivalent:
+ * ```kotlin
+ * collection.filter {
+ * User::name eq "Patrick"
+ * }.updateMany {
+ * User::age set 15
+ * }
+ * ```
+ *
+ * To learn more, see [filter][MongoCollection.filter].
+ *
* ### External resources
*
* - [Official documentation](https://www.mongodb.com/docs/manual/reference/command/update/)
@@ -81,6 +96,19 @@ interface UpdateOperations : BaseOperations {
* )
* ```
*
+ * ### Using filtered collections
+ *
+ * The following code is equivalent:
+ * ```kotlin
+ * collection.filter {
+ * User::name eq "Patrick"
+ * }.updateOne {
+ * User::age set 15
+ * }
+ * ```
+ *
+ * To learn more, see [filter][MongoCollection.filter].
+ *
* ### External resources
*
* - [Official documentation](https://www.mongodb.com/docs/manual/reference/command/update/)
@@ -123,6 +151,19 @@ interface UpdateOperations : BaseOperations {
* If a document exists that has the `name` of "Patrick", its age is set to 15.
* If none exists, a document with `name` "Patrick" and `age` 15 is created.
*
+ * ### Using filtered collections
+ *
+ * The following code is equivalent:
+ * ```kotlin
+ * collection.filter {
+ * User::name eq "Patrick"
+ * }.upsertOne {
+ * User::age set 15
+ * }
+ * ```
+ *
+ * To learn more, see [filter][MongoCollection.filter].
+ *
* ### External resources
*
* - [The update operation](https://www.mongodb.com/docs/manual/reference/command/update/)
@@ -156,6 +197,19 @@ interface UpdateOperations : BaseOperations {
* )
* ```
*
+ * ### Using filtered collections
+ *
+ * The following code is equivalent:
+ * ```kotlin
+ * collection.filter {
+ * User::name eq "Patrick"
+ * }.findOneAndUpdate {
+ * User::age set 15
+ * }
+ * ```
+ *
+ * To learn more, see [filter][MongoCollection.filter].
+ *
* ### External resources
*
* - [Official documentation](https://www.mongodb.com/docs/manual/reference/command/findAndModify/)
diff --git a/driver-coroutines/src/jvmMain/kotlin/JvmMongoCollection.kt b/driver-coroutines/src/jvmMain/kotlin/JvmMongoCollection.kt
index 54fb82e720dcf129f33f13a1860aa66a77334320..f64b37b4aefb4c6302123f607d93af923ab4a1d1 100644
--- a/driver-coroutines/src/jvmMain/kotlin/JvmMongoCollection.kt
+++ b/driver-coroutines/src/jvmMain/kotlin/JvmMongoCollection.kt
@@ -22,7 +22,7 @@ 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.common.AbstractCompoundExpression
+import opensavvy.ktmongo.dsl.expr.common.AbstractCompoundDocumentExpression
import org.bson.BsonDocument
/**
@@ -135,7 +135,7 @@ class JvmMongoCollection internal constructor(
}
@OptIn(LowLevelApi::class)
-private fun AbstractCompoundExpression.toBsonDocument(): BsonDocument =
+private fun AbstractCompoundDocumentExpression.toBsonDocument(): BsonDocument =
buildBsonDocument {
writeTo(this)
}
diff --git a/driver-sync/src/commonMain/kotlin/FilteredCollection.kt b/driver-sync/src/commonMain/kotlin/FilteredCollection.kt
new file mode 100644
index 0000000000000000000000000000000000000000..a1226872f7d22cf54fe46c5ca028a6f41dda907b
--- /dev/null
+++ b/driver-sync/src/commonMain/kotlin/FilteredCollection.kt
@@ -0,0 +1,131 @@
+/*
+ * 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 opensavvy.ktmongo.bson.BsonContext
+import opensavvy.ktmongo.dsl.LowLevelApi
+import opensavvy.ktmongo.dsl.expr.BulkUpdateExpression
+import opensavvy.ktmongo.dsl.expr.FilterExpression
+import opensavvy.ktmongo.dsl.expr.UpdateExpression
+
+private class FilteredCollection(
+ private val upstream: MongoCollection,
+ private val globalFilter: FilterExpression.() -> Unit,
+) : MongoCollection {
+
+ override fun find(): MongoIterable =
+ upstream.find(globalFilter)
+
+ override fun find(predicate: FilterExpression.() -> Unit): MongoIterable =
+ upstream.find {
+ globalFilter()
+ predicate()
+ }
+
+ @LowLevelApi
+ override val context: BsonContext
+ get() = upstream.context
+
+ override fun count(): Long =
+ upstream.count(globalFilter)
+
+ override fun count(predicate: FilterExpression.() -> Unit): Long =
+ upstream.count {
+ globalFilter()
+ predicate()
+ }
+
+ override fun countEstimated(): Long =
+ count()
+
+ override fun updateMany(filter: FilterExpression.() -> Unit, update: UpdateExpression.() -> Unit) =
+ upstream.updateMany(
+ filter = {
+ globalFilter()
+ filter()
+ },
+ update = update,
+ )
+
+ override fun updateOne(filter: FilterExpression.() -> Unit, update: UpdateExpression.() -> Unit) =
+ upstream.updateOne(
+ filter = {
+ globalFilter()
+ filter()
+ },
+ update = update,
+ )
+
+ override fun upsertOne(filter: FilterExpression.() -> Unit, update: UpdateExpression.() -> Unit) =
+ upstream.upsertOne(
+ filter = {
+ globalFilter()
+ filter()
+ },
+ update = update,
+ )
+
+ override fun findOneAndUpdate(filter: FilterExpression.() -> Unit, update: UpdateExpression.() -> Unit): Document? =
+ upstream.findOneAndUpdate(
+ filter = {
+ globalFilter()
+ filter()
+ },
+ update = update,
+ )
+
+ override fun bulkWrite(filter: FilterExpression.() -> Unit, update: BulkUpdateExpression.() -> Unit) =
+ upstream.bulkWrite(
+ filter = {
+ globalFilter()
+ filter()
+ },
+ update = update,
+ )
+
+}
+
+/**
+ * Returns a filtered collection that only contains the elements that match [filter].
+ *
+ * This function creates a logical view of the collection: by itself, this function does nothing, and MongoDB is never
+ * aware of the existence of this logical view. However, operations invoked on the returned collection will only affect
+ * elements from the original that match the [filter].
+ *
+ * Unlike actual MongoDB views, which are read-only, collections returned by this function can also be used for write operations.
+ *
+ * ### Example
+ *
+ * A typical usage of this function is to reuse filters for multiple operations.
+ * For example, if you have a concept of logical deletion, this function can be used to hide deleted values.
+ *
+ * ```kotlin
+ * class Order(
+ * val id: String,
+ * val date: Instant,
+ * val deleted: Boolean,
+ * )
+ *
+ * val allOrders = database.getCollection("orders").asKtMongo()
+ * val activeOrders = allOrders.filter { Order::deleted ne true }
+ *
+ * allOrders.find() // Returns all orders, deleted or not
+ * activeOrders.find() // Only returns orders that are not logically deleted
+ * ```
+ */
+fun MongoCollection.filter(filter: FilterExpression.() -> Unit): MongoCollection =
+ FilteredCollection(this, filter)
diff --git a/driver-sync/src/commonMain/kotlin/operations/UpdateOperations.kt b/driver-sync/src/commonMain/kotlin/operations/UpdateOperations.kt
index eac38a40c6ba2acc11326525170825590f52d70f..1aa7b2a394c7ae5afdfd24ea7a8ef0bfdc196aa6 100644
--- a/driver-sync/src/commonMain/kotlin/operations/UpdateOperations.kt
+++ b/driver-sync/src/commonMain/kotlin/operations/UpdateOperations.kt
@@ -16,8 +16,11 @@
package opensavvy.ktmongo.sync.operations
+import opensavvy.ktmongo.dsl.expr.BulkUpdateExpression
import opensavvy.ktmongo.dsl.expr.FilterExpression
import opensavvy.ktmongo.dsl.expr.UpdateExpression
+import opensavvy.ktmongo.sync.MongoCollection
+import opensavvy.ktmongo.sync.filter
/**
* Interface grouping MongoDB operations allowing to update existing information.
@@ -45,6 +48,19 @@ interface UpdateOperations : BaseOperations {
* )
* ```
*
+ * ### Using filtered collections
+ *
+ * The following code is equivalent:
+ * ```kotlin
+ * collection.filter {
+ * User::name eq "Patrick"
+ * }.updateMany {
+ * User::age set 15
+ * }
+ * ```
+ *
+ * To learn more, see [filter][MongoCollection.filter].
+ *
* ### External resources
*
* - [Official documentation](https://www.mongodb.com/docs/manual/reference/command/update/)
@@ -81,6 +97,19 @@ interface UpdateOperations : BaseOperations {
* )
* ```
*
+ * ### Using filtered collections
+ *
+ * The following code is equivalent:
+ * ```kotlin
+ * collection.filter {
+ * User::name eq "Patrick"
+ * }.updateOne {
+ * User::age set 15
+ * }
+ * ```
+ *
+ * To learn more, see [filter][MongoCollection.filter].
+ *
* ### External resources
*
* - [Official documentation](https://www.mongodb.com/docs/manual/reference/command/update/)
@@ -123,6 +152,19 @@ interface UpdateOperations : BaseOperations {
* If a document exists that has the `name` of "Patrick", its age is set to 15.
* If none exists, a document with `name` "Patrick" and `age` 15 is created.
*
+ * ### Using filtered collections
+ *
+ * The following code is equivalent:
+ * ```kotlin
+ * collection.filter {
+ * User::name eq "Patrick"
+ * }.upsertOne {
+ * User::age set 15
+ * }
+ * ```
+ *
+ * To learn more, see [filter][MongoCollection.filter].
+ *
* ### External resources
*
* - [The update operation](https://www.mongodb.com/docs/manual/reference/command/update/)
@@ -156,6 +198,19 @@ interface UpdateOperations : BaseOperations {
* )
* ```
*
+ * ### Using filtered collections
+ *
+ * The following code is equivalent:
+ * ```kotlin
+ * collection.filter {
+ * User::name eq "Patrick"
+ * }.findOneAndUpdate {
+ * User::age set 15
+ * }
+ * ```
+ *
+ * To learn more, see [filter][MongoCollection.filter].
+ *
* ### External resources
*
* - [Official documentation](https://www.mongodb.com/docs/manual/reference/command/findAndModify/)
@@ -170,4 +225,36 @@ interface UpdateOperations : BaseOperations {
update: UpdateExpression.() -> Unit,
): Document?
+ /**
+ * Performs multiple write operations in a single request.
+ *
+ * ### Example
+ *
+ * ```kotlin
+ * class User(
+ * val name: String,
+ * val age: Int,
+ * )
+ *
+ * collection.bulkWrite {
+ * upsertOne({ User::name eq "Alex" }) {
+ * User::age set 18
+ * }
+ *
+ * updateMany {
+ * User::age setOnInsert 20
+ * User::age inc 1
+ * }
+ * }
+ * ```
+ *
+ * ### External resources
+ *
+ * - [Official documentation](https://www.mongodb.com/docs/manual/reference/method/db.collection.bulkWrite)
+ */
+ fun bulkWrite(
+ filter: FilterExpression.() -> Unit = {},
+ update: BulkUpdateExpression.() -> Unit,
+ )
+
}
diff --git a/driver-sync/src/commonTest/kotlin/BasicReadWriteTest.kt b/driver-sync/src/commonTest/kotlin/BasicReadWriteTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..bb5db58f02a3bc2d61cc019f726a2b2e8c9025ae
--- /dev/null
+++ b/driver-sync/src/commonTest/kotlin/BasicReadWriteTest.kt
@@ -0,0 +1,45 @@
+/*
+ * 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 opensavvy.prepared.runner.kotest.PreparedSpec
+import opensavvy.prepared.suite.SuiteDsl
+
+fun SuiteDsl.basicReadWriteTest() = suite("Basic read/write test") {
+ class User(
+ val name: String,
+ val age: Int,
+ )
+
+ val users by testCollection("basic-users")
+
+ test("Foo") {
+ users().upsertOne(
+ filter = {
+ User::name eq "Foo"
+ },
+ update = {
+ User::name set "Bad"
+ User::age setOnInsert 0
+ }
+ )
+ }
+}
+
+class BasicReadWriteTest : PreparedSpec({
+ basicReadWriteTest()
+})
diff --git a/driver-sync/src/commonTest/kotlin/TestDatabase.kt b/driver-sync/src/commonTest/kotlin/TestDatabase.kt
new file mode 100644
index 0000000000000000000000000000000000000000..d868fcc00a84346ac9960d8ac36d99e1e023fb4f
--- /dev/null
+++ b/driver-sync/src/commonTest/kotlin/TestDatabase.kt
@@ -0,0 +1,21 @@
+/*
+ * 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 opensavvy.prepared.suite.PreparedProvider
+
+expect inline fun testCollection(name: String): PreparedProvider>
diff --git a/driver-sync/src/jsTest/kotlin/TestDatabase.js.kt b/driver-sync/src/jsTest/kotlin/TestDatabase.js.kt
new file mode 100644
index 0000000000000000000000000000000000000000..97e2b8ab759bd54b5b41e92e001414c0f93b519e
--- /dev/null
+++ b/driver-sync/src/jsTest/kotlin/TestDatabase.js.kt
@@ -0,0 +1,23 @@
+/*
+ * 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 opensavvy.prepared.suite.PreparedProvider
+
+actual inline fun testCollection(name: String): PreparedProvider> {
+ TODO("Not yet implemented")
+}
diff --git a/driver-sync/src/jvmMain/kotlin/JvmMongoCollection.kt b/driver-sync/src/jvmMain/kotlin/JvmMongoCollection.kt
index f6dd4ceb18c4518a993c6362e022512cc4275964..e5d04ec7c4961bef98ec2c5f26e65b02da4e9c3b 100644
--- a/driver-sync/src/jvmMain/kotlin/JvmMongoCollection.kt
+++ b/driver-sync/src/jvmMain/kotlin/JvmMongoCollection.kt
@@ -18,11 +18,15 @@ package opensavvy.ktmongo.sync
import com.mongodb.client.model.UpdateOptions
import opensavvy.ktmongo.bson.BsonContext
+import opensavvy.ktmongo.bson.buildBsonArray
import opensavvy.ktmongo.bson.buildBsonDocument
import opensavvy.ktmongo.dsl.LowLevelApi
+import opensavvy.ktmongo.dsl.expr.BulkUpdateExpression
import opensavvy.ktmongo.dsl.expr.FilterExpression
import opensavvy.ktmongo.dsl.expr.UpdateExpression
-import opensavvy.ktmongo.dsl.expr.common.AbstractCompoundExpression
+import opensavvy.ktmongo.dsl.expr.common.AbstractCompoundArrayExpression
+import opensavvy.ktmongo.dsl.expr.common.AbstractCompoundDocumentExpression
+import org.bson.BsonArray
import org.bson.BsonDocument
/**
@@ -130,16 +134,34 @@ class JvmMongoCollection internal constructor(
return inner.findOneAndUpdate(filter, update)
}
+ override fun bulkWrite(filter: FilterExpression.() -> Unit, update: BulkUpdateExpression.() -> Unit) {
+ val bulk = BulkUpdateExpression(context, filter)
+ .apply(update)
+ .toBsonArray()
+
+ inner.bulkWrite(
+ listOf() // TODO
+ )
+
+ TODO("Not yet implemented")
+ }
+
// endregion
}
@OptIn(LowLevelApi::class)
-private fun AbstractCompoundExpression.toBsonDocument(): BsonDocument =
+private fun AbstractCompoundDocumentExpression.toBsonDocument(): BsonDocument =
buildBsonDocument {
writeTo(this)
}
+@OptIn(LowLevelApi::class)
+private fun AbstractCompoundArrayExpression.toBsonArray(): BsonArray =
+ buildBsonArray {
+ writeTo(this)
+ }
+
/**
* Converts a [MongoDB collection][com.mongodb.kotlin.client.MongoCollection] into a [KtMongo collection][JvmMongoCollection].
*/
diff --git a/driver-sync/src/jvmTest/kotlin/TestDatabase.jvm.kt b/driver-sync/src/jvmTest/kotlin/TestDatabase.jvm.kt
new file mode 100644
index 0000000000000000000000000000000000000000..e696cabe197c691e2067ed541072e15b43aa1667
--- /dev/null
+++ b/driver-sync/src/jvmTest/kotlin/TestDatabase.jvm.kt
@@ -0,0 +1,41 @@
+/*
+ * 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 com.mongodb.MongoTimeoutException
+import com.mongodb.kotlin.client.MongoClient
+import kotlinx.coroutines.CoroutineName
+import opensavvy.prepared.suite.PreparedProvider
+import opensavvy.prepared.suite.prepared
+import opensavvy.prepared.suite.shared
+
+@PublishedApi
+internal val database by shared(CoroutineName("mongodb-establish-connection")) {
+ val client = try {
+ MongoClient.create("mongodb://localhost:27017")
+ .also { it.getDatabase("ktmongo-sync-tests").getCollection("test").countDocuments() }
+ } catch (e: MongoTimeoutException) {
+ System.err.println("Cannot connect to localhost:27017. Did you start the docker-compose services? [This is normal in CI]\n${e.stackTraceToString()}")
+ MongoClient.create("mongodb://mongo:27017")
+ }
+ client.getDatabase("ktmongo-sync-tests")
+}
+
+actual inline fun testCollection(name: String): PreparedProvider> = prepared(CoroutineName("mongodb-create-collection-$name")) {
+ val collection = database().getCollection(name)
+ collection.asKtMongo()
+}
diff --git a/dsl/src/commonMain/kotlin/expr/BulkUpdateExpression.kt b/dsl/src/commonMain/kotlin/expr/BulkUpdateExpression.kt
new file mode 100644
index 0000000000000000000000000000000000000000..78df3fb3754a6592b64e5df6ba4200a370a5d855
--- /dev/null
+++ b/dsl/src/commonMain/kotlin/expr/BulkUpdateExpression.kt
@@ -0,0 +1,251 @@
+/*
+ * 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.BsonContext
+import opensavvy.ktmongo.bson.BsonValueWriter
+import opensavvy.ktmongo.dsl.DangerousMongoApi
+import opensavvy.ktmongo.dsl.KtMongoDsl
+import opensavvy.ktmongo.dsl.LowLevelApi
+import opensavvy.ktmongo.dsl.expr.common.AbstractArrayExpression
+import opensavvy.ktmongo.dsl.expr.common.AbstractCompoundArrayExpression
+import opensavvy.ktmongo.dsl.expr.common.DocumentExpression
+import opensavvy.ktmongo.dsl.path.FieldDsl
+
+/**
+ * DSL for MongoDB's `bulkWrite` operation.
+ *
+ * ### Example
+ *
+ * ```kotlin
+ * class User(
+ * val name: String,
+ * val age: Int,
+ * )
+ *
+ * collection.bulkWrite {
+ * updateOne(
+ * filter = { User::name eq "Bob" },
+ * update = { User::age set 19 }
+ * )
+ *
+ * upsertOne(
+ * filter = { User::name eq "Alex" },
+ * update = { User::age set 20 }
+ * )
+ * }
+ * ```
+ *
+ * ### Operations
+ *
+ * - [updateOne] and [upsertOne]
+ * - [updateMany]
+ *
+ * ### External resources
+ *
+ * - [Official documentation](https://www.mongodb.com/docs/manual/reference/method/db.collection.bulkWrite)
+ */
+@KtMongoDsl
+class BulkUpdateExpression(
+ context: BsonContext,
+ private val globalFilter: FilterExpression.() -> Unit,
+) : AbstractCompoundArrayExpression(context), FieldDsl {
+
+ // region Low-level operations
+
+ @LowLevelApi
+ private sealed class BulkUpdateExpressionNode(context: BsonContext) : AbstractArrayExpression(context)
+
+ // endregion
+ // region update
+
+ /**
+ * Updates a single document in the collection that matches the [filter].
+ *
+ * If multiple documents match, only the first matching document will be updated.
+ *
+ * ### Example
+ *
+ * ```kotlin
+ * class User(
+ * val id: Long,
+ * val name: String,
+ * val score: Int,
+ * )
+ *
+ * users.bulkWrite {
+ * updateOne({ User::id eq 123456L }) {
+ * User::name set "Bobby"
+ * }
+ *
+ * updateOne({ User::id eq 123123L }) {
+ * User::score.inc()
+ * }
+ * }
+ * ```
+ *
+ * ### External resources
+ *
+ * - [Official documentation](https://www.mongodb.com/docs/manual/reference/method/db.collection.bulkWrite/#syntax)
+ *
+ * @see updateMany Update multiple documents.
+ * @see upsertOne Create the document if it doesn't already exist.
+ */
+ @KtMongoDsl
+ @OptIn(LowLevelApi::class, DangerousMongoApi::class)
+ fun updateOne(
+ filter: FilterExpression.() -> Unit = {},
+ update: UpdateExpression.() -> Unit,
+ ) {
+ accept(UpdateExpressionNode(
+ operation = "updateOne",
+ filter = FilterExpression(context).apply {
+ this@BulkUpdateExpression.globalFilter(this)
+ filter()
+ },
+ update = UpdateExpression(context).apply(update),
+ upsert = false,
+ context = context
+ ))
+ }
+
+ /**
+ * Updates a single document in the collection that matches the [filter], creating one if none are found.
+ *
+ * If multiple documents match, only the first matching document will be updated.
+ *
+ * If no document match, a new document is created, using the information provided both in the filter
+ * and in the update.
+ *
+ * ### Example
+ *
+ * ```kotlin
+ * class User(
+ * val id: Long,
+ * val name: String,
+ * val score: Int,
+ * )
+ *
+ * users.bulkWrite {
+ * upsertOne({ User::name eq "Bobby" }) {
+ * User::score inc 1
+ * }
+ * }
+ * ```
+ *
+ * ### External resources
+ *
+ * - [Official documentation](https://www.mongodb.com/docs/manual/reference/method/db.collection.bulkWrite/#syntax)
+ * - [The behavior of upsert functions](https://www.mongodb.com/docs/manual/reference/method/db.collection.update/#insert-a-new-document-if-no-match-exists--upsert-)
+ *
+ * @see updateOne Do nothing if the document doesn't exist.
+ */
+ @KtMongoDsl
+ @OptIn(LowLevelApi::class, DangerousMongoApi::class)
+ fun upsertOne(
+ filter: FilterExpression.() -> Unit = {},
+ update: UpdateExpression.() -> Unit,
+ ) {
+ accept(UpdateExpressionNode(
+ operation = "updateOne",
+ filter = FilterExpression(context).apply {
+ this@BulkUpdateExpression.globalFilter(this)
+ filter()
+ },
+ update = UpdateExpression(context).apply(update),
+ upsert = true,
+ context = context
+ ))
+ }
+
+ /**
+ * Updates all documents in the collection that match the [filter].
+ *
+ * ### Example
+ *
+ * ```kotlin
+ * class User(
+ * val id: Long,
+ * val name: String,
+ * val score: Int,
+ * )
+ *
+ * users.bulkWrite {
+ * updateMany {
+ * User::score inc 1
+ * }
+ *
+ * updateMany({ User::id eq 123456L }) {
+ * User::score inc 3
+ * }
+ * }
+ * ```
+ *
+ * At the end of this operation, the user with identifier `123456`, if it exists, will have a score increased by 4 (1 + 3).
+ * All other users will have a score increased by 1.
+ *
+ * ### External resources
+ *
+ * - [Official documentation](https://www.mongodb.com/docs/manual/reference/method/db.collection.bulkWrite/#syntax)
+ *
+ * @see updateOne Update a single document.
+ */
+ @KtMongoDsl
+ @OptIn(LowLevelApi::class, DangerousMongoApi::class)
+ fun updateMany(
+ filter: FilterExpression.() -> Unit = {},
+ update: UpdateExpression.() -> Unit,
+ ) {
+ accept(UpdateExpressionNode(
+ operation = "updateMany",
+ filter = FilterExpression(context).apply {
+ this@BulkUpdateExpression.globalFilter(this)
+ filter()
+ },
+ update = UpdateExpression(context).apply(update),
+ upsert = false,
+ context = context
+ ))
+ }
+
+ @LowLevelApi
+ private class UpdateExpressionNode(
+ private val operation: String,
+ private val filter: DocumentExpression,
+ private val update: DocumentExpression,
+ private val upsert: Boolean,
+ context: BsonContext,
+ ) : BulkUpdateExpressionNode(context) {
+ @LowLevelApi
+ override fun write(writer: BsonValueWriter) = with(writer) {
+ writeDocument {
+ writeDocument(operation) {
+ writeDocument("filter") {
+ filter.writeTo(this)
+ }
+ writeDocument("update") {
+ update.writeTo(this)
+ }
+ writeBoolean("upsert", upsert)
+ }
+ }
+ }
+ }
+
+ // endregion
+
+}
diff --git a/dsl/src/commonMain/kotlin/expr/FilterExpression.kt b/dsl/src/commonMain/kotlin/expr/FilterExpression.kt
index 912fa4a7f16e21fe4e35a2cb00b9a6c56f3f6826..7a12a3e717447c52130ac24c8f8c39287eb11072 100644
--- a/dsl/src/commonMain/kotlin/expr/FilterExpression.kt
+++ b/dsl/src/commonMain/kotlin/expr/FilterExpression.kt
@@ -23,9 +23,10 @@ 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.AbstractCompoundExpression
+import opensavvy.ktmongo.dsl.expr.common.AbstractCompoundDocumentExpression
+import opensavvy.ktmongo.dsl.expr.common.AbstractDocumentExpression
import opensavvy.ktmongo.dsl.expr.common.AbstractExpression
-import opensavvy.ktmongo.dsl.expr.common.Expression
+import opensavvy.ktmongo.dsl.expr.common.DocumentExpression
import opensavvy.ktmongo.dsl.path.Field
import opensavvy.ktmongo.dsl.path.FieldDsl
import opensavvy.ktmongo.dsl.path.FieldImpl
@@ -127,14 +128,14 @@ import kotlin.reflect.KProperty1
@KtMongoDsl
class FilterExpression(
context: BsonContext,
-) : AbstractCompoundExpression(context),
+) : AbstractCompoundDocumentExpression(context),
FieldDsl {
// region Low-level operations
@OptIn(DangerousMongoApi::class)
@LowLevelApi
- override fun simplify(children: List): AbstractExpression? =
+ override fun simplify(children: List): AbstractExpression? =
when (children.size) {
0 -> null
1 -> this
@@ -142,7 +143,7 @@ class FilterExpression(
}
@LowLevelApi
- private sealed class FilterExpressionNode(context: BsonContext) : AbstractExpression(context)
+ private sealed class FilterExpressionNode(context: BsonContext) : AbstractDocumentExpression(context)
// endregion
// region $and, $or
@@ -182,11 +183,11 @@ class FilterExpression(
@DangerousMongoApi
@LowLevelApi
private class AndFilterExpressionNode(
- val declaredChildren: List,
+ val declaredChildren: List,
context: BsonContext,
) : FilterExpressionNode(context) {
- override fun simplify(): AbstractExpression? {
+ override fun simplify(): AbstractExpression? {
if (declaredChildren.isEmpty())
return null
@@ -194,7 +195,7 @@ class FilterExpression(
return FilterExpression(context).apply { accept(declaredChildren.single()) }
// If there are nested $and operators, we combine them into the current one
- val nestedChildren = ArrayList()
+ val nestedChildren = ArrayList()
for (child in declaredChildren) {
if (child is AndFilterExpressionNode<*>) {
@@ -256,11 +257,11 @@ class FilterExpression(
@DangerousMongoApi
@LowLevelApi
private class OrFilterExpressionNode(
- val declaredChildren: List,
+ val declaredChildren: List,
context: BsonContext,
) : FilterExpressionNode(context) {
- override fun simplify(): AbstractExpression? {
+ override fun simplify(): AbstractExpression? {
if (declaredChildren.isEmpty())
return null
@@ -354,11 +355,11 @@ class FilterExpression(
@LowLevelApi
private class PredicateInFilterExpression(
val target: Path,
- val expression: Expression,
+ val expression: DocumentExpression,
context: BsonContext,
) : FilterExpressionNode(context) {
- override fun simplify(): AbstractExpression? =
+ override fun simplify(): AbstractExpression? =
expression.simplify()
?.let { PredicateInFilterExpression(target, it, context) }
@@ -2136,11 +2137,11 @@ class FilterExpression(
@LowLevelApi
private class ElementMatchExpressionNode(
val target: Path,
- val expression: Expression,
+ val expression: DocumentExpression,
context: BsonContext,
) : FilterExpressionNode(context) {
- override fun simplify(): AbstractExpression =
+ override fun simplify(): AbstractExpression =
ElementMatchExpressionNode(target, expression.simplify()
?: OrFilterExpressionNode(emptyList(), context), context)
diff --git a/dsl/src/commonMain/kotlin/expr/PredicateExpression.kt b/dsl/src/commonMain/kotlin/expr/PredicateExpression.kt
index 3dbb4de481f3761d8724f9ce7962affaee02d1aa..53caca1331e9ede29eaf3f4fddc7a49564dc56a2 100644
--- a/dsl/src/commonMain/kotlin/expr/PredicateExpression.kt
+++ b/dsl/src/commonMain/kotlin/expr/PredicateExpression.kt
@@ -23,7 +23,8 @@ 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.AbstractCompoundExpression
+import opensavvy.ktmongo.dsl.expr.common.AbstractCompoundDocumentExpression
+import opensavvy.ktmongo.dsl.expr.common.AbstractDocumentExpression
import opensavvy.ktmongo.dsl.expr.common.AbstractExpression
/**
@@ -58,12 +59,12 @@ import opensavvy.ktmongo.dsl.expr.common.AbstractExpression
@KtMongoDsl
class PredicateExpression(
context: BsonContext,
-) : AbstractCompoundExpression(context) {
+) : AbstractCompoundDocumentExpression(context) {
// region Low-level operations
@LowLevelApi
- private sealed class PredicateExpressionNode(context: BsonContext) : AbstractExpression(context)
+ private sealed class PredicateExpressionNode(context: BsonContext) : AbstractDocumentExpression(context)
// endregion
// region $eq
@@ -302,8 +303,8 @@ class PredicateExpression(
* - [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].
+ * @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
@@ -365,7 +366,7 @@ class PredicateExpression(
context: BsonContext,
) : PredicateExpressionNode(context) {
- override fun simplify(): AbstractExpression? {
+ override fun simplify(): AbstractExpression? {
if (expression.children.isEmpty())
return null
@@ -706,7 +707,7 @@ class PredicateExpression(
* - [Official documentation](https://www.mongodb.com/docs/manual/reference/operator/query/lt/)
*
* @see FilterExpression.ltNotNull
- * @see lqNotNull Learn more about the 'notNull' variants
+ * @see ltNotNull Learn more about the 'notNull' variants
*/
@KtMongoDsl
fun ltNotNull(value: T?) {
diff --git a/dsl/src/commonMain/kotlin/expr/UpdateExpression.kt b/dsl/src/commonMain/kotlin/expr/UpdateExpression.kt
index d333da76a1ae2e422c3c957427c31bcfaaac98f1..cffd51b5ea63d3c47da866ddfbbc166358e3a050 100644
--- a/dsl/src/commonMain/kotlin/expr/UpdateExpression.kt
+++ b/dsl/src/commonMain/kotlin/expr/UpdateExpression.kt
@@ -21,10 +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.expr.common.AbstractCompoundExpression
-import opensavvy.ktmongo.dsl.expr.common.AbstractExpression
-import opensavvy.ktmongo.dsl.expr.common.Expression
-import opensavvy.ktmongo.dsl.expr.common.acceptAll
+import opensavvy.ktmongo.dsl.expr.common.*
import opensavvy.ktmongo.dsl.path.Field
import opensavvy.ktmongo.dsl.path.FieldDsl
import opensavvy.ktmongo.dsl.path.Path
@@ -74,22 +71,23 @@ import kotlin.reflect.KProperty1
@KtMongoDsl
class UpdateExpression(
context: BsonContext,
-) : AbstractCompoundExpression(context),
+) : AbstractCompoundDocumentExpression(context),
FieldDsl {
// region Low-level operations
- private class OperatorCombinator(
+ @OptIn(LowLevelApi::class)
+ private class OperatorCombinator>(
val type: KClass,
val combinator: (List, BsonContext) -> T
) {
@Suppress("UNCHECKED_CAST") // This is a private class, it should not be used incorrectly
- operator fun invoke(sources: List, context: BsonContext) =
+ operator fun invoke(sources: List>, context: BsonContext) =
combinator(sources as List, context)
}
@LowLevelApi
- override fun simplify(children: List): AbstractExpression? {
+ override fun simplify(children: List): AbstractExpression? {
if (children.isEmpty())
return null
@@ -116,7 +114,7 @@ class UpdateExpression(
}
@LowLevelApi
- private sealed class UpdateExpressionNode(context: BsonContext) : AbstractExpression(context)
+ private sealed class UpdateExpressionNode(context: BsonContext) : AbstractDocumentExpression(context)
// endregion
// region $set
@@ -281,7 +279,7 @@ class UpdateExpression(
val mappings: List>,
context: BsonContext,
) : UpdateExpressionNode(context) {
- override fun simplify(): AbstractExpression? =
+ override fun simplify(): AbstractExpression? =
this.takeUnless { mappings.isEmpty() }
override fun write(writer: BsonFieldWriter) = with(writer) {
@@ -371,7 +369,7 @@ class UpdateExpression(
val mappings: List>,
context: BsonContext,
) : UpdateExpressionNode(context) {
- override fun simplify(): AbstractExpression? =
+ override fun simplify(): AbstractExpression? =
this.takeUnless { mappings.isEmpty() }
override fun write(writer: BsonFieldWriter) = with(writer) {
@@ -453,7 +451,7 @@ class UpdateExpression(
val fields: List,
context: BsonContext,
) : UpdateExpressionNode(context) {
- override fun simplify(): AbstractExpression? =
+ override fun simplify(): AbstractExpression? =
this.takeUnless { fields.isEmpty() }
override fun write(writer: BsonFieldWriter) = with(writer) {
@@ -585,7 +583,7 @@ class UpdateExpression(
val fields: List>,
context: BsonContext,
) : UpdateExpressionNode(context) {
- override fun simplify(): AbstractExpression? =
+ override fun simplify(): AbstractExpression? =
this.takeUnless { fields.isEmpty() }
override fun write(writer: BsonFieldWriter) = with(writer) {
diff --git a/dsl/src/commonMain/kotlin/expr/common/CompoundExpression.kt b/dsl/src/commonMain/kotlin/expr/common/CompoundExpression.kt
index ad72f105bdca4d55a29dcea0e5b14517e10efee2..4d1624f08840272b4d53bb2c9fafc41ba39c2f47 100644
--- a/dsl/src/commonMain/kotlin/expr/common/CompoundExpression.kt
+++ b/dsl/src/commonMain/kotlin/expr/common/CompoundExpression.kt
@@ -16,15 +16,21 @@
package opensavvy.ktmongo.dsl.expr.common
-import opensavvy.ktmongo.bson.BsonContext
-import opensavvy.ktmongo.bson.BsonFieldWriter
+import opensavvy.ktmongo.bson.*
import opensavvy.ktmongo.dsl.DangerousMongoApi
import opensavvy.ktmongo.dsl.KtMongoDsl
import opensavvy.ktmongo.dsl.LowLevelApi
import opensavvy.ktmongo.dsl.utils.asImmutable
/**
- * A compound expression is an [Expression] that may have children.
+ * A compound expression is an [Expression] that combines multiple expressions together into a single BSON value.
+ *
+ * For example, the compound expression [PredicateExpression][opensavvy.ktmongo.dsl.expr.PredicateExpression] allows
+ * combining multiple operators (for example `{ $ne: 12 }` and `{ $gt: 10 }`) into a single predicate expression:
+ * `{ $ne: 12, $gt: 10 }`.
+ *
+ * The distinct expressions that are combined by this compound are called "children".
+ * See [CompoundDocumentExpression] and [CompoundArrayExpression] to learn more about how children are merged.
*
* A compound expression may have `0..n` children.
* Children are added by calling the [accept] function.
@@ -34,9 +40,12 @@ import opensavvy.ktmongo.dsl.utils.asImmutable
*
* ### Implementation notes
*
- * Prefer implementing [AbstractCompoundExpression] instead of implementing this interface directly.
+ * See [AbstractCompoundExpression] instead of implementing this interface directly.
+ *
+ * @param Writer See [CompoundDocumentExpression] and [CompoundArrayExpression].
*/
-interface CompoundExpression : Expression {
+@OptIn(LowLevelApi::class)
+interface CompoundExpression : Expression {
/**
* Adds a new [expression] as a child of this one.
@@ -53,27 +62,105 @@ interface CompoundExpression : Expression {
@LowLevelApi
@DangerousMongoApi
@KtMongoDsl
- fun accept(expression: Expression)
+ fun accept(expression: Expression)
companion object
}
-abstract class AbstractCompoundExpression(
+/**
+ * A compound expression that is expected to be added in a [BSON document][Bson].
+ *
+ * When an expression is added into this compound, it is merged with the existing children, overriding previous
+ * expressions with the same identifier.
+ *
+ * For example, if we have a compound expression:
+ * ```bson
+ * {
+ * $ne: 12,
+ * $gt: 10
+ * }
+ * ```
+ * and call [accept][CompoundExpression.accept] with the following expression:
+ * ```bson
+ * {
+ * $lte: 17,
+ * $ne: 13
+ * }
+ * ```
+ * we obtain the following result (the order of fields is not guaranteed):
+ * ```bson
+ * {
+ * $ne: 13,
+ * $gt: 10,
+ * $lte: 17
+ * }
+ * ```
+ *
+ * @see CompoundArrayExpression Equivalent for arrays.
+ */
+@OptIn(LowLevelApi::class)
+typealias CompoundDocumentExpression = CompoundExpression
+
+/**
+ * A compound expression that is expected to be added in a [BSON array][BsonArray].
+ *
+ * When an expression is added into this compound, it is added as a new element of the underlying array.
+ *
+ * For example, if we have a compound expression:
+ * ```bson
+ * [
+ * 5,
+ * null
+ * ]
+ * ```
+ * and we call [accept][CompoundExpression.accept] with the following expression:
+ * ```bson
+ * {
+ * "foo": "bar"
+ * }
+ * ```
+ * we obtain the following result (the order of fields is guaranteed):
+ * ```bson
+ * [
+ * 5,
+ * null,
+ * {
+ * "foo": "bar"
+ * }
+ * ]
+ * ```
+ *
+ * @see CompoundDocumentExpression Equivalent for documents.
+ */
+@OptIn(LowLevelApi::class)
+typealias CompoundArrayExpression = CompoundExpression
+
+/**
+ * Interface for declaring custom compound expressions.
+ *
+ * Each KtMongo DSL is powered by an instance of this class.
+ *
+ * Start by learning [how to create operators][AbstractExpression].
+ * Then, implement either [AbstractCompoundDocumentExpression] or [AbstractCompoundArrayExpression].
+ */
+@OptIn(LowLevelApi::class)
+sealed class AbstractCompoundExpression(
context: BsonContext,
-) : AbstractExpression(context), CompoundExpression {
+ defaultWriterGenerator: (block: Writer.() -> Unit) -> Any,
+) : AbstractExpression(context, defaultWriterGenerator), CompoundExpression {
// region Sub-expression binding
- private val _children = ArrayList()
+ private val _children = ArrayList>()
@LowLevelApi
- protected val children: List
+ protected val children: List>
get() = _children.asImmutable()
@LowLevelApi
@DangerousMongoApi
@KtMongoDsl
- override fun accept(expression: Expression) {
+ override fun accept(expression: Expression) {
require(!frozen) { "This expression has already been frozen, it cannot accept the child expression $expression" }
require(expression != this) { "Trying to add an expression to itself!" }
@@ -97,11 +184,11 @@ abstract class AbstractCompoundExpression(
* @see Expression.simplify
*/
@LowLevelApi
- protected open fun simplify(children: List): AbstractExpression? =
+ protected open fun simplify(children: List>): AbstractExpression? =
this
@LowLevelApi
- final override fun simplify(): AbstractExpression? =
+ final override fun simplify(): AbstractExpression? =
simplify(children)
// endregion
@@ -115,21 +202,51 @@ abstract class AbstractCompoundExpression(
* @see AbstractExpression.write
*/
@LowLevelApi
- protected open fun write(writer: BsonFieldWriter, children: List) {
- for (child in children) {
- check(this !== child) { "Trying to write myself as my own child!" }
- child.writeTo(writer)
- }
- }
+ protected abstract fun write(writer: Writer, children: List>)
@LowLevelApi
- final override fun write(writer: BsonFieldWriter) {
+ final override fun write(writer: Writer) {
write(writer, children)
}
// endregion
+}
- companion object
+/**
+ * Parent interface for DSLs that are written into BSON documents.
+ *
+ * Implements the merging behavior described in [CompoundDocumentExpression].
+ */
+@OptIn(LowLevelApi::class)
+abstract class AbstractCompoundDocumentExpression(
+ context: BsonContext,
+) : AbstractCompoundExpression(context, ::buildBsonDocument), DocumentExpression {
+
+ override fun write(writer: BsonFieldWriter, children: List) {
+ for (child in children) {
+ check(this !== child) { "Trying to write myself as my own child!" }
+ child.writeTo(writer)
+ }
+ }
+}
+
+/**
+ * Parent interface for DSLs that are written into BSON arrays.
+ *
+ * Implements the merging behavior described in [CompoundArrayExpression].
+ */
+@OptIn(LowLevelApi::class)
+abstract class AbstractCompoundArrayExpression(
+ context: BsonContext,
+) : AbstractCompoundExpression(context, ::buildBsonArray), ArrayExpression {
+
+ final override fun write(writer: BsonValueWriter, children: List) {
+ writer.writeArray {
+ for (child in children) {
+ child.writeTo(this)
+ }
+ }
+ }
}
/**
@@ -140,7 +257,7 @@ abstract class AbstractCompoundExpression(
@LowLevelApi
@DangerousMongoApi
@KtMongoDsl
-fun CompoundExpression.acceptAll(expressions: Iterable) {
+fun CompoundExpression.acceptAll(expressions: Iterable>) {
for (child in expressions) {
accept(child)
}
diff --git a/dsl/src/commonMain/kotlin/expr/common/Expression.kt b/dsl/src/commonMain/kotlin/expr/common/Expression.kt
index 7676d72839a8c00b5b37ab5ff61969991e1edfa6..64ef36c6008a805140c487ca40bda7f6cd779fb1 100644
--- a/dsl/src/commonMain/kotlin/expr/common/Expression.kt
+++ b/dsl/src/commonMain/kotlin/expr/common/Expression.kt
@@ -16,9 +16,7 @@
package opensavvy.ktmongo.dsl.expr.common
-import opensavvy.ktmongo.bson.BsonContext
-import opensavvy.ktmongo.bson.BsonFieldWriter
-import opensavvy.ktmongo.bson.buildBsonDocument
+import opensavvy.ktmongo.bson.*
import opensavvy.ktmongo.dsl.LowLevelApi
import opensavvy.ktmongo.dsl.expr.PredicateExpression
@@ -39,8 +37,13 @@ import opensavvy.ktmongo.dsl.expr.PredicateExpression
* ### Debugging notes
*
* Use [toString][Any.toString] to view the JSON representation of this expression.
+ *
+ * @param Writer The context in which this expression is written.
+ * - If this expression is an element of an object, [BsonFieldWriter] is used. See [DocumentExpression].
+ * - If this expression is an element of an array, [BsonValueWriter] is used. See [ArrayExpression].
*/
-interface Expression {
+@OptIn(LowLevelApi::class)
+interface Expression {
/**
* The context used to generate this expression.
@@ -63,22 +66,32 @@ interface Expression {
* Returns `null` when the current expression was simplified into a no-op (= it does nothing).
*/
@LowLevelApi
- fun simplify(): Expression?
+ fun simplify(): Expression?
/**
* Writes the result of [simplifying][simplify] this expression into [writer].
*/
@LowLevelApi
- fun writeTo(writer: BsonFieldWriter)
+ fun writeTo(writer: Writer)
/**
* JSON representation of this expression.
*/
override fun toString(): String
-
- companion object
}
+/**
+ * An expression that is expected to be added in a [BSON document][Bson].
+ */
+@OptIn(LowLevelApi::class)
+typealias DocumentExpression = Expression
+
+/**
+ * An expression that is expected to be added in a [BSON array][BsonArray].
+ */
+@OptIn(LowLevelApi::class)
+typealias ArrayExpression = Expression
+
/**
* Utility implementation for [Expression], which handles the [context], [toString] representation and [freezing][freeze].
*
@@ -93,7 +106,7 @@ interface Expression {
* Before writing your own operator, familiarize yourself with the documentation of [Expression], [AbstractExpression],
* [CompoundExpression] and [AbstractCompoundExpression], as well as [BsonFieldWriter].
*
- * Fundamentally, an operator is anything that is able to [write] itself into a BSON document.
+ * Fundamentally, an operator is anything that is able to [write] itself into a BSON document or array.
* Operators should not be mutable, except through their [accept][CompoundExpression.accept] method (if they have one).
*
* An operator generally looks like the following:
@@ -102,13 +115,17 @@ interface Expression {
* private class TypePredicateExpressionNode(
* val type: BsonType,
* context: BsonContext,
- * ) : AbstractExpression(context) {
+ * ) : AbstractDocumentExpression(context) {
*
* override fun write(writer: BsonFieldWriter) {
* writer.writeInt32("\$type", type.code)
* }
* }
* ```
+ * Note that we implemented [AbstractDocumentExpression] instead of this class. The implementation is slightly different
+ * depending on whether this expression is expected to be written to a document or an array. Implement either
+ * [AbstractDocumentExpression] or [AbstractArrayExpression].
+ *
* The [BsonContext] is required at construction because it is needed to implement [toString], which the user could call at any time,
* including while the operator is being constructed (e.g. when using a debugger). It is extremely important that the
* `toString` representation they see is consistent with the final BSON sent over the wire.
@@ -133,9 +150,11 @@ interface Expression {
* operator you create so they can benefit from future fixes. Again, **an improperly-written operator may allow data
* corruption or leaking**.
*/
-abstract class AbstractExpression(
+@OptIn(LowLevelApi::class)
+sealed class AbstractExpression(
@property:LowLevelApi override val context: BsonContext,
-) : Expression {
+ private val defaultWriterGenerator: (block: Writer.() -> Unit) -> Any,
+) : Expression {
/**
* `true` if this expression is immutable.
@@ -155,13 +174,13 @@ abstract class AbstractExpression(
* so it is guaranteed that this expression is fully simplified already.
*/
@LowLevelApi
- protected abstract fun write(writer: BsonFieldWriter)
+ protected abstract fun write(writer: Writer)
@LowLevelApi
- override fun simplify(): AbstractExpression? = this
+ override fun simplify(): AbstractExpression? = this
@LowLevelApi
- final override fun writeTo(writer: BsonFieldWriter) {
+ final override fun writeTo(writer: Writer) {
this.simplify()?.write(writer)
}
@@ -173,7 +192,7 @@ abstract class AbstractExpression(
*/
@OptIn(LowLevelApi::class)
fun toString(simplified: Boolean): String {
- val document = buildBsonDocument {
+ val document = defaultWriterGenerator {
if (simplified)
writeTo(this)
else
@@ -185,6 +204,24 @@ abstract class AbstractExpression(
final override fun toString(): String =
toString(simplified = true)
-
- companion object
}
+
+/**
+ * Specification of [AbstractExpression] for the case of implementing a [DocumentExpression].
+ *
+ * Learn more about implementing this class in [AbstractExpression].
+ */
+@OptIn(LowLevelApi::class)
+abstract class AbstractDocumentExpression(
+ context: BsonContext
+) : AbstractExpression(context, ::buildBsonDocument), DocumentExpression
+
+/**
+ * Specification of [AbstractExpression] for the case of implementing an [ArrayExpression].
+ *
+ * Learn more about implementing this class in [AbstractExpression].
+ */
+@OptIn(LowLevelApi::class)
+abstract class AbstractArrayExpression(
+ context: BsonContext
+) : AbstractExpression(context, ::buildBsonArray), ArrayExpression
diff --git a/dsl/src/commonMain/kotlin/options/CountOptions.kt b/dsl/src/commonMain/kotlin/options/CountOptions.kt
index 13702d806c1d22c5a2bb5fd7d7b2721b870fadc2..6073057c233fb5675abb96bcffaf5e03f0235ae7 100644
--- a/dsl/src/commonMain/kotlin/options/CountOptions.kt
+++ b/dsl/src/commonMain/kotlin/options/CountOptions.kt
@@ -17,11 +17,11 @@
package opensavvy.ktmongo.dsl.options
import opensavvy.ktmongo.bson.BsonContext
-import opensavvy.ktmongo.dsl.expr.common.AbstractCompoundExpression
+import opensavvy.ktmongo.dsl.expr.common.AbstractCompoundDocumentExpression
import opensavvy.ktmongo.dsl.options.common.LimitOption
import opensavvy.ktmongo.dsl.options.common.Options
/**
* The options for a `collection.count` operation.
*/
-class CountOptions(context: BsonContext) : AbstractCompoundExpression(context), Options, LimitOption
+class CountOptions(context: BsonContext) : AbstractCompoundDocumentExpression(context), Options, LimitOption
diff --git a/dsl/src/commonMain/kotlin/options/common/LimitOption.kt b/dsl/src/commonMain/kotlin/options/common/LimitOption.kt
index 3d36d087f9bfd3125f347dac11a1714691f4a5ee..b91ad2d892a39c74ec43f21de6c944b82b331288 100644
--- a/dsl/src/commonMain/kotlin/options/common/LimitOption.kt
+++ b/dsl/src/commonMain/kotlin/options/common/LimitOption.kt
@@ -20,15 +20,15 @@ import opensavvy.ktmongo.bson.BsonContext
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
+import opensavvy.ktmongo.dsl.expr.common.AbstractDocumentExpression
+import opensavvy.ktmongo.dsl.expr.common.CompoundDocumentExpression
/**
* Limits the number of elements returned by a query.
*
* See [limit].
*/
-interface LimitOption : CompoundExpression {
+interface LimitOption : CompoundDocumentExpression {
/**
* The maximum number of matching documents to return.
@@ -48,7 +48,7 @@ interface LimitOption : CompoundExpression {
private class LimitOptionExpression(
private val limit: Long,
context: BsonContext,
- ) : AbstractExpression(context) {
+ ) : AbstractDocumentExpression(context) {
@LowLevelApi
override fun write(writer: BsonFieldWriter) {
diff --git a/dsl/src/commonMain/kotlin/options/common/Options.kt b/dsl/src/commonMain/kotlin/options/common/Options.kt
index 80816cf6b98455e7315aec0baae125e794a9b068..6a778787b844b2e60c606c804977f9c9a66fd901 100644
--- a/dsl/src/commonMain/kotlin/options/common/Options.kt
+++ b/dsl/src/commonMain/kotlin/options/common/Options.kt
@@ -16,7 +16,7 @@
package opensavvy.ktmongo.dsl.options.common
-import opensavvy.ktmongo.dsl.expr.common.CompoundExpression
+import opensavvy.ktmongo.dsl.expr.common.CompoundDocumentExpression
/**
* Parent interface for all option types.
@@ -24,4 +24,4 @@ import opensavvy.ktmongo.dsl.expr.common.CompoundExpression
* Option types are used to pass additional arguments to MongoDB operations.
* See the different implementations for more information.
*/
-interface Options : CompoundExpression
+interface Options : CompoundDocumentExpression