diff --git a/bson-multiplatform/src/commonMain/kotlin/BsonArray.kt b/bson-multiplatform/src/commonMain/kotlin/BsonArray.kt index 0abd5b80..fdff657a 100644 --- a/bson-multiplatform/src/commonMain/kotlin/BsonArray.kt +++ b/bson-multiplatform/src/commonMain/kotlin/BsonArray.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, OpenSavvy and contributors. + * Copyright (c) 2025-2026, 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. @@ -60,7 +60,7 @@ import kotlin.reflect.KType * Although it is not possible to mutate its state, this class uses internal mutation to lazily decode the BSON stream. */ class BsonArray internal constructor( - private val factory: BsonFactory, + override val factory: BsonFactory, private val bytesWithHeader: Bytes, ) : BsonArray { diff --git a/bson-multiplatform/src/commonMain/kotlin/BsonDocument.kt b/bson-multiplatform/src/commonMain/kotlin/BsonDocument.kt index b6b87545..21e05468 100644 --- a/bson-multiplatform/src/commonMain/kotlin/BsonDocument.kt +++ b/bson-multiplatform/src/commonMain/kotlin/BsonDocument.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, OpenSavvy and contributors. + * Copyright (c) 2025-2026, 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. @@ -69,7 +69,7 @@ import kotlin.reflect.KType * Although it is not possible to mutate its state, this class uses internal mutation to lazily decode the BSON stream. */ class BsonDocument internal constructor( - private val factory: BsonFactory, + override val factory: BsonFactory, private val bytesWithHeader: Bytes, ) : BsonDocument { diff --git a/bson-multiplatform/src/commonMain/kotlin/BsonValue.kt b/bson-multiplatform/src/commonMain/kotlin/BsonValue.kt index ac370737..8e706e10 100644 --- a/bson-multiplatform/src/commonMain/kotlin/BsonValue.kt +++ b/bson-multiplatform/src/commonMain/kotlin/BsonValue.kt @@ -73,7 +73,7 @@ import kotlin.time.Instant * Although it is not possible to mutate its state, this class uses internal mutation to lazily decode the BSON stream. */ class BsonValue internal constructor( - private val factory: BsonFactory, + override val factory: BsonFactory, override val type: BsonType, private val bytes: Bytes, ) : BsonValue { diff --git a/bson-official/src/commonMain/kotlin/Bson.kt b/bson-official/src/commonMain/kotlin/Bson.kt index 87b841aa..407e66d1 100644 --- a/bson-official/src/commonMain/kotlin/Bson.kt +++ b/bson-official/src/commonMain/kotlin/Bson.kt @@ -51,6 +51,8 @@ import kotlin.reflect.typeOf */ expect class BsonDocument : BsonDocument { + override val factory: opensavvy.ktmongo.bson.official.BsonFactory + override fun get(field: String): opensavvy.ktmongo.bson.official.BsonValue? override fun asValue(): opensavvy.ktmongo.bson.official.BsonValue @@ -102,6 +104,8 @@ expect class BsonDocument : BsonDocument { */ expect class BsonArray : BsonArray { + override val factory: opensavvy.ktmongo.bson.official.BsonFactory + override fun get(index: Int): opensavvy.ktmongo.bson.official.BsonValue? override fun asValue(): opensavvy.ktmongo.bson.official.BsonValue @@ -149,6 +153,8 @@ expect class BsonArray : BsonArray { */ expect class BsonValue : BsonValue { + override val factory: opensavvy.ktmongo.bson.official.BsonFactory + override fun decodeDocument(): opensavvy.ktmongo.bson.official.BsonDocument override fun decodeArray(): opensavvy.ktmongo.bson.official.BsonArray diff --git a/bson-official/src/jvmMain/kotlin/BsonArray.jvm.kt b/bson-official/src/jvmMain/kotlin/BsonArray.jvm.kt index 2c132e00..2f76489c 100644 --- a/bson-official/src/jvmMain/kotlin/BsonArray.jvm.kt +++ b/bson-official/src/jvmMain/kotlin/BsonArray.jvm.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, OpenSavvy and contributors. + * Copyright (c) 2024-2026, 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. @@ -31,7 +31,7 @@ import org.bson.BsonArray as OfficialBsonArray */ actual class BsonArray internal constructor( val raw: OfficialBsonArray, - private val factory: BsonFactory, + actual override val factory: BsonFactory, ) : BsonArray { @LowLevelApi diff --git a/bson-official/src/jvmMain/kotlin/BsonDocument.jvm.kt b/bson-official/src/jvmMain/kotlin/BsonDocument.jvm.kt index cc6e3028..553892fa 100644 --- a/bson-official/src/jvmMain/kotlin/BsonDocument.jvm.kt +++ b/bson-official/src/jvmMain/kotlin/BsonDocument.jvm.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, OpenSavvy and contributors. + * Copyright (c) 2024-2026, 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. @@ -33,7 +33,7 @@ import org.bson.BsonDocument as OfficialBsonDocument */ actual class BsonDocument internal constructor( val raw: OfficialBsonDocument, - private val factory: BsonFactory, + actual override val factory: BsonFactory, ) : opensavvy.ktmongo.bson.BsonDocument { @LowLevelApi diff --git a/bson-official/src/jvmMain/kotlin/BsonValue.jvm.kt b/bson-official/src/jvmMain/kotlin/BsonValue.jvm.kt index 41dd82b5..35607cb6 100644 --- a/bson-official/src/jvmMain/kotlin/BsonValue.jvm.kt +++ b/bson-official/src/jvmMain/kotlin/BsonValue.jvm.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, OpenSavvy and contributors. + * Copyright (c) 2024-2026, 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. @@ -39,7 +39,7 @@ import kotlin.reflect.KType */ actual class BsonValue internal constructor( val raw: org.bson.BsonValue, - private val factory: BsonFactory, + actual override val factory: BsonFactory, ) : BsonValue { override val type: BsonType diff --git a/bson/src/commonMain/kotlin/BsonArray.kt b/bson/src/commonMain/kotlin/BsonArray.kt index 742b050a..cd7ec1e2 100644 --- a/bson/src/commonMain/kotlin/BsonArray.kt +++ b/bson/src/commonMain/kotlin/BsonArray.kt @@ -53,6 +53,13 @@ import kotlin.reflect.typeOf */ interface BsonArray { + /** + * The instance of [BsonFactory] that created this instance. + * + * [BsonFactory] contains the serialization configuration that apply to this array. + */ + val factory: BsonFactory + /** * Decodes this array into an instance of the Kotlin type [T]. * diff --git a/bson/src/commonMain/kotlin/BsonDocument.kt b/bson/src/commonMain/kotlin/BsonDocument.kt index b92e5120..3b7782ef 100644 --- a/bson/src/commonMain/kotlin/BsonDocument.kt +++ b/bson/src/commonMain/kotlin/BsonDocument.kt @@ -56,6 +56,13 @@ import kotlin.reflect.typeOf */ interface BsonDocument { + /** + * The instance of [BsonFactory] that created this instance. + * + * [BsonFactory] contains the serialization configuration that apply to this document. + */ + val factory: BsonFactory + /** * Generates a [ByteArray] of the raw BSON representation of this value. */ diff --git a/bson/src/commonMain/kotlin/BsonValue.kt b/bson/src/commonMain/kotlin/BsonValue.kt index 33008c60..4943fd8f 100644 --- a/bson/src/commonMain/kotlin/BsonValue.kt +++ b/bson/src/commonMain/kotlin/BsonValue.kt @@ -62,6 +62,13 @@ import kotlin.time.Instant */ interface BsonValue { + /** + * The instance of [BsonFactory] that created this instance. + * + * [BsonFactory] contains the serialization configuration that apply to this value. + */ + val factory: BsonFactory + /** * The native [BSON type][BsonType] of this value. */ -- 2.51.2 From ecb8e643e0a356e60ac1f2bc19ddac6c501b917d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Sat, 2 May 2026 19:09:36 +0200 Subject: [PATCH 02/10] refactor(bson): Simplify BsonPath serialization implementation --- bson/src/commonMain/kotlin/BsonPath.kt | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/bson/src/commonMain/kotlin/BsonPath.kt b/bson/src/commonMain/kotlin/BsonPath.kt index 801be2eb..71dfb6e2 100644 --- a/bson/src/commonMain/kotlin/BsonPath.kt +++ b/bson/src/commonMain/kotlin/BsonPath.kt @@ -1641,17 +1641,7 @@ private inline fun Char.isNameChar() = @ExperimentalBsonPathApi inline fun BsonDocument.select(path: BsonPath): Sequence { return path.findIn(this.asValue()) - .map { - @Suppress("UNCHECKED_CAST") - val result = it.decode() - - if (null is T) { - result - } else { - result - ?: throw BsonDecodingException("Found an unexpected 'null' when reading the path $path in document $this") - } - } + .map { it.decode() } } /** -- 2.51.2 From 60a016ae2665eddbbd1d8558b65119ce34ab7fe2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Sat, 2 May 2026 21:15:42 +0200 Subject: [PATCH 03/10] feat(bson-official): Add codec for Kotlin primitive types --- .../src/jvmMain/kotlin/BsonFactory.jvm.kt | 8 ++ .../src/jvmMain/kotlin/types/Primitives.kt | 117 ++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 bson-official/src/jvmMain/kotlin/types/Primitives.kt diff --git a/bson-official/src/jvmMain/kotlin/BsonFactory.jvm.kt b/bson-official/src/jvmMain/kotlin/BsonFactory.jvm.kt index 02ce0f4b..506a8256 100644 --- a/bson-official/src/jvmMain/kotlin/BsonFactory.jvm.kt +++ b/bson-official/src/jvmMain/kotlin/BsonFactory.jvm.kt @@ -76,6 +76,14 @@ actual class BsonFactory( KotlinFloatVectorCodec(), KotlinBooleanVectorCodec(), KotlinByteVectorCodec(), + KotlinPrimitiveByteCodec(), + KotlinPrimitiveShortCodec(), + KotlinPrimitiveIntCodec(), + KotlinPrimitiveLongCodec(), + KotlinPrimitiveFloatCodec(), + KotlinPrimitiveDoubleCodec(), + KotlinPrimitiveBooleanCodec(), + KotlinPrimitiveCharCodec(), ), codecRegistry, ) diff --git a/bson-official/src/jvmMain/kotlin/types/Primitives.kt b/bson-official/src/jvmMain/kotlin/types/Primitives.kt new file mode 100644 index 00000000..94af2f9d --- /dev/null +++ b/bson-official/src/jvmMain/kotlin/types/Primitives.kt @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2026, 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.bson.official.types + +import org.bson.BsonReader +import org.bson.BsonWriter +import org.bson.codecs.* + +internal class KotlinPrimitiveByteCodec : Codec { + override fun getEncoderClass(): Class = + Byte::class.java + + override fun encode(writer: BsonWriter?, value: Byte?, encoderContext: EncoderContext?) { + ByteCodec().encode(writer, value, encoderContext) + } + + override fun decode(reader: BsonReader?, decoderContext: DecoderContext?): Byte = + ByteCodec().decode(reader, decoderContext) +} + +internal class KotlinPrimitiveShortCodec : Codec { + override fun getEncoderClass(): Class = + Short::class.java + + override fun encode(writer: BsonWriter?, value: Short?, encoderContext: EncoderContext?) { + ShortCodec().encode(writer, value, encoderContext) + } + + override fun decode(reader: BsonReader?, decoderContext: DecoderContext?): Short = + ShortCodec().decode(reader, decoderContext) +} + +internal class KotlinPrimitiveIntCodec : Codec { + override fun getEncoderClass(): Class = + Int::class.java + + override fun encode(writer: BsonWriter?, value: Int?, encoderContext: EncoderContext?) { + IntegerCodec().encode(writer, value, encoderContext) + } + + override fun decode(reader: BsonReader?, decoderContext: DecoderContext?): Int = + IntegerCodec().decode(reader, decoderContext) +} + +internal class KotlinPrimitiveLongCodec : Codec { + override fun getEncoderClass(): Class = + Long::class.java + + override fun encode(writer: BsonWriter?, value: Long?, encoderContext: EncoderContext?) { + LongCodec().encode(writer, value, encoderContext) + } + + override fun decode(reader: BsonReader?, decoderContext: DecoderContext?): Long = + LongCodec().decode(reader, decoderContext) +} + +internal class KotlinPrimitiveFloatCodec : Codec { + override fun getEncoderClass(): Class = + Float::class.java + + override fun encode(writer: BsonWriter?, value: Float?, encoderContext: EncoderContext?) { + FloatCodec().encode(writer, value, encoderContext) + } + + override fun decode(reader: BsonReader?, decoderContext: DecoderContext?): Float = + FloatCodec().decode(reader, decoderContext) +} + +internal class KotlinPrimitiveDoubleCodec : Codec { + override fun getEncoderClass(): Class = + Double::class.java + + override fun encode(writer: BsonWriter?, value: Double?, encoderContext: EncoderContext?) { + DoubleCodec().encode(writer, value, encoderContext) + } + + override fun decode(reader: BsonReader?, decoderContext: DecoderContext?): Double = + DoubleCodec().decode(reader, decoderContext) +} + +internal class KotlinPrimitiveBooleanCodec : Codec { + override fun getEncoderClass(): Class = + Boolean::class.java + + override fun encode(writer: BsonWriter?, value: Boolean?, encoderContext: EncoderContext?) { + BooleanCodec().encode(writer, value, encoderContext) + } + + override fun decode(reader: BsonReader?, decoderContext: DecoderContext?): Boolean = + BooleanCodec().decode(reader, decoderContext) +} + +internal class KotlinPrimitiveCharCodec : Codec { + override fun getEncoderClass(): Class = + Char::class.java + + override fun encode(writer: BsonWriter?, value: Char?, encoderContext: EncoderContext?) { + CharacterCodec().encode(writer, value, encoderContext) + } + + override fun decode(reader: BsonReader?, decoderContext: DecoderContext?): Char = + CharacterCodec().decode(reader, decoderContext) +} -- 2.51.2 From 6689f1edd5cdb5b1fb9c63700819edf0746cd1e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Sat, 2 May 2026 21:18:31 +0200 Subject: [PATCH 04/10] feat(bson): Access the KType instance when writing a BSON value --- .../write/MultiplatformArrayFieldWriter.kt | 7 +- .../write/MultiplatformDocumentFieldWriter.kt | 3 +- .../write/MultiplatformSingleFieldWriter.kt | 7 +- .../src/jvmMain/kotlin/BsonArrayWriter.jvm.kt | 26 +-- .../jvmMain/kotlin/BsonDocumentWriter.jvm.kt | 12 +- bson/src/commonMain/kotlin/BsonWriter.kt | 168 +++++++++++++++++- 6 files changed, 186 insertions(+), 37 deletions(-) diff --git a/bson-multiplatform/src/commonMain/kotlin/impl/write/MultiplatformArrayFieldWriter.kt b/bson-multiplatform/src/commonMain/kotlin/impl/write/MultiplatformArrayFieldWriter.kt index 2557f1ba..5e42e706 100644 --- a/bson-multiplatform/src/commonMain/kotlin/impl/write/MultiplatformArrayFieldWriter.kt +++ b/bson-multiplatform/src/commonMain/kotlin/impl/write/MultiplatformArrayFieldWriter.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, OpenSavvy and contributors. + * Copyright (c) 2025-2026, 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. @@ -22,6 +22,7 @@ import opensavvy.ktmongo.bson.DEPRECATED_IN_BSON_SPEC import opensavvy.ktmongo.bson.types.Timestamp import opensavvy.ktmongo.dsl.DangerousMongoApi import opensavvy.ktmongo.dsl.LowLevelApi +import kotlin.reflect.KType @LowLevelApi internal class MultiplatformArrayFieldWriter( @@ -157,7 +158,7 @@ internal class MultiplatformArrayFieldWriter( writer.openArray(nextIndex()) @LowLevelApi - override fun writeObjectSafe(obj: T) { - writer.writeObjectSafe(nextIndex(), obj) + override fun writeSafe(obj: T, type: KType) { + writer.writeSafe(nextIndex(), obj, type) } } diff --git a/bson-multiplatform/src/commonMain/kotlin/impl/write/MultiplatformDocumentFieldWriter.kt b/bson-multiplatform/src/commonMain/kotlin/impl/write/MultiplatformDocumentFieldWriter.kt index 9c2eb5f8..40976faa 100644 --- a/bson-multiplatform/src/commonMain/kotlin/impl/write/MultiplatformDocumentFieldWriter.kt +++ b/bson-multiplatform/src/commonMain/kotlin/impl/write/MultiplatformDocumentFieldWriter.kt @@ -28,6 +28,7 @@ import opensavvy.ktmongo.bson.types.ObjectId import opensavvy.ktmongo.bson.types.Timestamp import opensavvy.ktmongo.dsl.DangerousMongoApi import opensavvy.ktmongo.dsl.LowLevelApi +import kotlin.reflect.KType @LowLevelApi internal class MultiplatformDocumentFieldWriter( @@ -282,7 +283,7 @@ internal class MultiplatformDocumentFieldWriter( } @LowLevelApi - override fun writeObjectSafe(name: String, obj: T) { + override fun writeSafe(name: String, obj: T, type: KType) { TODO() } diff --git a/bson-multiplatform/src/commonMain/kotlin/impl/write/MultiplatformSingleFieldWriter.kt b/bson-multiplatform/src/commonMain/kotlin/impl/write/MultiplatformSingleFieldWriter.kt index 4b1c33ed..277d1108 100644 --- a/bson-multiplatform/src/commonMain/kotlin/impl/write/MultiplatformSingleFieldWriter.kt +++ b/bson-multiplatform/src/commonMain/kotlin/impl/write/MultiplatformSingleFieldWriter.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, OpenSavvy and contributors. + * Copyright (c) 2025-2026, 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. @@ -23,6 +23,7 @@ import opensavvy.ktmongo.bson.DEPRECATED_IN_BSON_SPEC import opensavvy.ktmongo.bson.types.Timestamp import opensavvy.ktmongo.dsl.DangerousMongoApi import opensavvy.ktmongo.dsl.LowLevelApi +import kotlin.reflect.KType @LowLevelApi internal class MultiplatformSingleFieldWriter( @@ -140,8 +141,8 @@ internal class MultiplatformSingleFieldWriter( } @LowLevelApi - override fun writeObjectSafe(obj: T) { - writer.writeObjectSafe(name, obj) + override fun writeSafe(obj: T, type: KType) { + writer.writeSafe(name, obj, type) } override fun complete() { diff --git a/bson-official/src/jvmMain/kotlin/BsonArrayWriter.jvm.kt b/bson-official/src/jvmMain/kotlin/BsonArrayWriter.jvm.kt index fb94ceaf..ba894b41 100644 --- a/bson-official/src/jvmMain/kotlin/BsonArrayWriter.jvm.kt +++ b/bson-official/src/jvmMain/kotlin/BsonArrayWriter.jvm.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, OpenSavvy and contributors. + * Copyright (c) 2024-2026, 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. @@ -22,28 +22,12 @@ import opensavvy.ktmongo.bson.DEPRECATED_IN_BSON_SPEC import opensavvy.ktmongo.bson.official.types.toOfficial import opensavvy.ktmongo.bson.types.Timestamp import opensavvy.ktmongo.dsl.LowLevelApi +import org.bson.* import org.bson.BsonArray -import org.bson.BsonBinary -import org.bson.BsonBoolean -import org.bson.BsonDateTime -import org.bson.BsonDbPointer -import org.bson.BsonDecimal128 import org.bson.BsonDocument -import org.bson.BsonDocumentWriter -import org.bson.BsonDouble -import org.bson.BsonInt32 -import org.bson.BsonInt64 -import org.bson.BsonJavaScript -import org.bson.BsonMaxKey -import org.bson.BsonMinKey -import org.bson.BsonNull -import org.bson.BsonObjectId -import org.bson.BsonRegularExpression -import org.bson.BsonString -import org.bson.BsonSymbol -import org.bson.BsonUndefined import org.bson.types.Decimal128 import org.bson.types.ObjectId +import kotlin.reflect.KType @LowLevelApi internal class JavaBsonArrayWriter( @@ -150,11 +134,11 @@ internal class JavaBsonArrayWriter( } @LowLevelApi - override fun writeObjectSafe(obj: T) { + override fun writeSafe(obj: T, type: KType) { val document = BsonDocument() BsonDocumentWriter(document).use { writer -> - JavaBsonDocumentWriter(factory, writer).writeObjectSafe(obj) + JavaBsonDocumentWriter(factory, writer).writeSafe(obj, type) } array.add(document) diff --git a/bson-official/src/jvmMain/kotlin/BsonDocumentWriter.jvm.kt b/bson-official/src/jvmMain/kotlin/BsonDocumentWriter.jvm.kt index 4842a539..aa90de2f 100644 --- a/bson-official/src/jvmMain/kotlin/BsonDocumentWriter.jvm.kt +++ b/bson-official/src/jvmMain/kotlin/BsonDocumentWriter.jvm.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, OpenSavvy and contributors. + * Copyright (c) 2024-2026, 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. @@ -26,10 +26,10 @@ import org.bson.BsonBinary import org.bson.BsonDbPointer import org.bson.BsonRegularExpression import org.bson.BsonWriter -import org.bson.codecs.Encoder import org.bson.codecs.EncoderContext import org.bson.types.Decimal128 import org.bson.types.ObjectId +import kotlin.reflect.KType @OptIn(LowLevelApi::class) internal class JavaBsonDocumentWriter( @@ -112,9 +112,9 @@ internal class JavaBsonDocumentWriter( writer.writeEndArray() } - override fun writeObjectSafe(name: String, obj: T) { + override fun writeSafe(name: String, obj: T, type: KType) { writer.writeName(name) - writeObjectSafe(obj) + writeSafe(obj, type) } @Deprecated(DEPRECATED_IN_BSON_SPEC) @@ -213,12 +213,12 @@ internal class JavaBsonDocumentWriter( writer.writeEndArray() } - override fun writeObjectSafe(obj: T) { + override fun writeSafe(obj: T, type: KType) { if (obj == null) { writer.writeNull() } else { @Suppress("UNCHECKED_CAST", "UNNECESSARY_NOT_NULL_ASSERTION") - val codec = factory.codecRegistry.get(obj!!::class.java) as Encoder + val codec = factory.findCodecForType(type) codec.encode( writer, obj, diff --git a/bson/src/commonMain/kotlin/BsonWriter.kt b/bson/src/commonMain/kotlin/BsonWriter.kt index 1731541e..025aa30a 100644 --- a/bson/src/commonMain/kotlin/BsonWriter.kt +++ b/bson/src/commonMain/kotlin/BsonWriter.kt @@ -22,6 +22,8 @@ import opensavvy.ktmongo.bson.types.Vector import opensavvy.ktmongo.dsl.DangerousMongoApi import opensavvy.ktmongo.dsl.LowLevelApi import kotlin.experimental.and +import kotlin.reflect.KType +import kotlin.reflect.typeOf import kotlin.time.Instant /** @@ -110,8 +112,90 @@ interface BsonValueWriter : AnyBsonWriter { * Writes an arbitrary [obj] into a BSON document. * * All nested values are escaped as necessary such that the result is a completely inert BSON document. + * + * ### Serialization configuration + * + * This method uses the serialization methods configured in the [BsonFactory] that created this instance. + * + * For example, if you use the official Java or Kotlin MongoDB drivers, + * this method will use your configured `CodecRegistry`. + * + * ### Example + * + * ```kotlin + * data class User( + * val _id: ObjectId, + * val profile: Profile, + * ) + * + * data class Profile( + * val name: String, + * val age: Int?, + * ) + * + * val factory: BsonFactory = … + * + * val bson = factory.buildDocument { + * write("user") { + * writeSafe("user", User(ObjectId("69c93e17b96e83b72d11b734"), Profile("Bob", 30))) + * } + * } + * + * val user = bson.decode() + * + * println(user._id) // ObjectId(69c93e17b96e83b72d11b734) + * println(user.profile.name) // Bob + * println(user.profile.age) // 30 + * ``` */ - @LowLevelApi fun writeObjectSafe(obj: T) + @LowLevelApi + fun writeSafe(obj: T, type: KType) + + /** + * Writes an arbitrary [obj] into a BSON document. + * + * All nested values are escaped as necessary such that the result is a completely inert BSON document. + * + * ### Serialization configuration + * + * This method uses the serialization methods configured in the [BsonFactory] that created this instance. + * + * For example, if you use the official Java or Kotlin MongoDB drivers, + * this method will use your configured `CodecRegistry`. + * + * ### Example + * + * ```kotlin + * data class User( + * val _id: ObjectId, + * val profile: Profile, + * ) + * + * data class Profile( + * val name: String, + * val age: Int?, + * ) + * + * val factory: BsonFactory = … + * + * val bson = factory.buildDocument { + * write("user") { + * writeSafe("user", User(ObjectId("69c93e17b96e83b72d11b734"), Profile("Bob", 30))) + * } + * } + * + * val user = bson.decode() + * + * println(user._id) // ObjectId(69c93e17b96e83b72d11b734) + * println(user.profile.name) // Bob + * println(user.profile.age) // 30 + * ``` + */ + @Suppress("WRONG_MODIFIER_CONTAINING_DECLARATION") + @LowLevelApi + final inline fun writeSafe(obj: T) { + this.writeSafe(obj, typeOf()) + } /** * Writes the arbitrary [obj] into this writer. @@ -119,7 +203,7 @@ interface BsonValueWriter : AnyBsonWriter { * Note that the object will be written as-is, with no safety checks whatsoever. * Only use this method if you are absolutely sure attackers cannot control the contents of [obj]. * - * If in doubt, prefer using [writeObjectSafe]. + * If in doubt, prefer using [writeSafe]. */ @LowLevelApi @DangerousMongoApi @@ -247,8 +331,86 @@ interface BsonFieldWriter : AnyBsonWriter { * Writes an arbitrary [obj] into a BSON document. * * All nested values are escaped as necessary such that the result is a completely inert BSON document. + * + * ### Serialization configuration + * + * This method uses the serialization methods configured in the [BsonFactory] that created this instance. + * + * For example, if you use the official Java or Kotlin MongoDB drivers, + * this method will use your configured `CodecRegistry`. + * + * ### Example + * + * ```kotlin + * data class User( + * val _id: ObjectId, + * val profile: Profile, + * ) + * + * data class Profile( + * val name: String, + * val age: Int?, + * ) + * + * val factory: BsonFactory = … + * + * val bson = factory.buildDocument { + * writeSafe("user", User(ObjectId("69c93e17b96e83b72d11b734"), Profile("Bob", 30))) + * } + * + * val user = bson.decode() + * + * println(user._id) // ObjectId(69c93e17b96e83b72d11b734) + * println(user.profile.name) // Bob + * println(user.profile.age) // 30 + * ``` */ - @LowLevelApi fun writeObjectSafe(name: String, obj: T) + @LowLevelApi + fun writeSafe(name: String, obj: T, type: KType) + + /** + * Writes an arbitrary [obj] into a BSON document. + * + * All nested values are escaped as necessary such that the result is a completely inert BSON document. + * + * ### Serialization configuration + * + * This method uses the serialization methods configured in the [BsonFactory] that created this instance. + * + * For example, if you use the official Java or Kotlin MongoDB drivers, + * this method will use your configured `CodecRegistry`. + * + * ### Example + * + * ```kotlin + * data class User( + * val _id: ObjectId, + * val profile: Profile, + * ) + * + * data class Profile( + * val name: String, + * val age: Int?, + * ) + * + * val factory: BsonFactory = … + * + * val bson = factory.buildDocument { + * writeSafe("user", User(ObjectId("69c93e17b96e83b72d11b734"), Profile("Bob", 30))) + * } + * + * val user = bson.decode() + * + * println(user._id) // ObjectId(69c93e17b96e83b72d11b734) + * println(user.profile.name) // Bob + * println(user.profile.age) // 30 + * ``` + */ + @Suppress("WRONG_MODIFIER_CONTAINING_DECLARATION") + @LowLevelApi + final inline fun writeSafe(name: String, obj: T) { + this.writeSafe(name, obj, typeOf()) + } // No 'pipe' overload because it would encourage people to use it. // If you really must use 'pipe', use 'write("name") { pipe(…) }' -- 2.51.2 From 9a21966253145c1b52fcfdb099f6ae4c7e96adb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Sat, 2 May 2026 21:20:35 +0200 Subject: [PATCH 05/10] refactor(dsl): Operators take advantage of the KType instance --- .../operators/ArrayValueOperators.kt | 6 +- .../aggregation/operators/ValueOperators.kt | 5 +- .../commonMain/kotlin/command/BulkWrite.kt | 23 +++-- .../src/commonMain/kotlin/command/Insert.kt | 19 +++- .../src/commonMain/kotlin/command/Replace.kt | 19 +++- .../commonMain/kotlin/query/FilterQuery.kt | 5 +- .../kotlin/query/FilterQueryImpl.kt | 5 +- .../kotlin/query/FilterQueryPredicateImpl.kt | 40 ++++---- .../kotlin/query/UpdateQueryImpl.kt | 52 +++++----- .../operators/ArrayValueOperators.kt | 96 +++++++++---------- .../aggregation/operators/ValueOperators.kt | 5 +- .../commonMain/kotlin/command/BulkWrite.kt | 23 +++-- dsl/src/commonMain/kotlin/command/Insert.kt | 19 +++- dsl/src/commonMain/kotlin/command/Replace.kt | 19 +++- .../commonMain/kotlin/query/FilterQuery.kt | 5 +- .../kotlin/query/FilterQueryImpl.kt | 5 +- .../kotlin/query/FilterQueryPredicateImpl.kt | 40 ++++---- .../kotlin/query/UpdateQueryImpl.kt | 52 +++++----- .../kotlin/command/BulkWriteTest.kt | 5 +- .../commonTest/kotlin/command/InsertTest.kt | 7 +- 20 files changed, 274 insertions(+), 176 deletions(-) diff --git a/dsl-template/src/commonMain/kotlin/aggregation/operators/ArrayValueOperators.kt b/dsl-template/src/commonMain/kotlin/aggregation/operators/ArrayValueOperators.kt index 2d7fa484..7f7e7bd8 100644 --- a/dsl-template/src/commonMain/kotlin/aggregation/operators/ArrayValueOperators.kt +++ b/dsl-template/src/commonMain/kotlin/aggregation/operators/ArrayValueOperators.kt @@ -208,7 +208,7 @@ interface ArrayValueOperators : ValueOperators { @Suppress("INVISIBLE_REFERENCE") @KtMongoDsl fun Value>.filter( - limit: Value? = null, + limit: Value? = null, variableName: String = "this", predicate: AggregationOperators.(Value) -> Value, ): Value> = @@ -295,7 +295,7 @@ interface ArrayValueOperators : ValueOperators { @OptIn(LowLevelApi::class) @KtMongoDsl fun Value>.take( - limit: Value, + limit: Value, ): Value> = TakeValueOperator( input = this, @@ -354,7 +354,7 @@ interface ArrayValueOperators : ValueOperators { @OptIn(LowLevelApi::class) @KtMongoDsl fun Value>.takeLast( - limit: Value, + limit: Value, ): Value> = TakeLastValueOperator( input = this, diff --git a/dsl-template/src/commonMain/kotlin/aggregation/operators/ValueOperators.kt b/dsl-template/src/commonMain/kotlin/aggregation/operators/ValueOperators.kt index 31b77a44..a64222a6 100644 --- a/dsl-template/src/commonMain/kotlin/aggregation/operators/ValueOperators.kt +++ b/dsl-template/src/commonMain/kotlin/aggregation/operators/ValueOperators.kt @@ -102,7 +102,7 @@ interface ValueOperators : FieldDsl { */ @OptIn(LowLevelApi::class) fun of(value: Result, type: KType): Value = - LiteralValue(value, context) + LiteralValue(value, context, type) /** * Refers to a Kotlin [value] within an [aggregation value][AggregationOperators]. @@ -222,12 +222,13 @@ private class FieldValue( private class LiteralValue( val value: Any?, context: BsonContext, + val type: KType, ) : AbstractValue(context) { @LowLevelApi override fun write(writer: BsonValueWriter) { writer.writeDocument { - writeObjectSafe("\$literal", value) + writeSafe("\$literal", value, type) } } } diff --git a/dsl-template/src/commonMain/kotlin/command/BulkWrite.kt b/dsl-template/src/commonMain/kotlin/command/BulkWrite.kt index 90569f1d..274b0ebf 100644 --- a/dsl-template/src/commonMain/kotlin/command/BulkWrite.kt +++ b/dsl-template/src/commonMain/kotlin/command/BulkWrite.kt @@ -31,6 +31,7 @@ import opensavvy.ktmongo.dsl.tree.AbstractBsonNode import opensavvy.ktmongo.dsl.tree.CompoundNode import opensavvy.ktmongo.dsl.tree.Node import opensavvy.ktmongo.dsl.tree.acceptAll +import kotlin.reflect.KType sealed interface AvailableInBulkWrite : Node, Command @@ -88,12 +89,17 @@ class BulkWrite private constructor( context: BsonContext, private val globalFilter: FilterQuery.() -> Unit, val options: BulkWriteOptions, + val documentType: KType, ) : Command, AbstractBsonNode(context), CompoundNode> { private val _operations = ArrayList>() val operations: Sequence> get() = _operations.asSequence() - constructor(context: BsonContext, globalFilter: FilterQuery.() -> Unit) : this(context, globalFilter, BulkWriteOptions(context)) + constructor( + context: BsonContext, + documentType: KType, + globalFilter: FilterQuery.() -> Unit, + ) : this(context, globalFilter, BulkWriteOptions(context), documentType) @LowLevelApi @DangerousMongoApi @@ -126,10 +132,11 @@ class BulkWrite private constructor( val child = BulkWrite( context = context, + documentType, globalFilter = { parent.globalFilter(this) filter() - } + }, ) child.operations() @@ -167,7 +174,7 @@ class BulkWrite private constructor( document: Document, options: InsertOneOptions.() -> Unit = {}, ) { - val model = InsertOne(context, document) + val model = InsertOne(context, document, documentType) model.options.options() @@ -412,7 +419,7 @@ class BulkWrite private constructor( filter: FilterQuery.() -> Unit = {}, document: Document, ) { - val model = ReplaceOne(context, document) + val model = ReplaceOne(context, document, documentType) model.options.options() model.filter.globalFilter() @@ -461,7 +468,7 @@ class BulkWrite private constructor( filter: FilterQuery.() -> Unit = {}, document: Document, ) { - val model = RepsertOne(context, document) + val model = RepsertOne(context, document, documentType) model.options.options() model.filter.globalFilter() @@ -479,7 +486,7 @@ class BulkWrite private constructor( when (operation) { is InsertOne<*> -> { writeInt32("insert", 0) - writeObjectSafe("document", operation.document) + writeSafe("document", operation.document) operation.options.writeTo(this) } @@ -500,7 +507,7 @@ class BulkWrite private constructor( writeDocument("filter") { operation.filter.writeTo(this) } - writeObjectSafe("updateMods", operation.document) + writeSafe("updateMods", operation.document) writeBoolean("multi", false) operation.options.writeTo(this) } @@ -510,7 +517,7 @@ class BulkWrite private constructor( writeDocument("filter") { operation.filter.writeTo(this) } - writeObjectSafe("updateMods", operation.document) + writeSafe("updateMods", operation.document) writeBoolean("multi", false) writeBoolean("upsert", true) operation.options.writeTo(this) diff --git a/dsl-template/src/commonMain/kotlin/command/Insert.kt b/dsl-template/src/commonMain/kotlin/command/Insert.kt index 8791fd90..9c9f5fa4 100644 --- a/dsl-template/src/commonMain/kotlin/command/Insert.kt +++ b/dsl-template/src/commonMain/kotlin/command/Insert.kt @@ -24,6 +24,7 @@ import opensavvy.ktmongo.dsl.options.Options import opensavvy.ktmongo.dsl.options.OptionsHolder import opensavvy.ktmongo.dsl.options.WithWriteConcern import opensavvy.ktmongo.dsl.tree.AbstractBsonNode +import kotlin.reflect.KType /** * Inserting a single element in a collection. @@ -46,14 +47,19 @@ class InsertOne private constructor( context: BsonContext, val options: InsertOneOptions, val document: Document, + val documentType: KType, ) : Command, AbstractBsonNode(context), AvailableInBulkWrite { - constructor(context: BsonContext, document: Document) : this(context, InsertOneOptions(context), document) + constructor( + context: BsonContext, + document: Document, + documentType: KType, + ) : this(context, InsertOneOptions(context), document, documentType) @LowLevelApi override fun write(writer: BsonFieldWriter) = with(writer) { writeArray("documents") { - writeObjectSafe(document) + writeSafe(document, documentType) } options.writeTo(this) @@ -81,15 +87,20 @@ class InsertMany private constructor( context: BsonContext, val options: InsertManyOptions, val documents: List, + val documentType: KType, ) : Command, AbstractBsonNode(context) { - constructor(context: BsonContext, documents: List) : this(context, InsertManyOptions(context), documents) + constructor( + context: BsonContext, + documents: List, + documentType: KType, + ) : this(context, InsertManyOptions(context), documents, documentType) @LowLevelApi override fun write(writer: BsonFieldWriter) = with(writer) { writeArray("documents") { for (document in documents) { - writeObjectSafe(document) + writeSafe(document, documentType) } } diff --git a/dsl-template/src/commonMain/kotlin/command/Replace.kt b/dsl-template/src/commonMain/kotlin/command/Replace.kt index ab7e521c..c780bfc9 100644 --- a/dsl-template/src/commonMain/kotlin/command/Replace.kt +++ b/dsl-template/src/commonMain/kotlin/command/Replace.kt @@ -25,6 +25,7 @@ import opensavvy.ktmongo.dsl.options.OptionsHolder import opensavvy.ktmongo.dsl.options.WithWriteConcern import opensavvy.ktmongo.dsl.query.FilterQuery import opensavvy.ktmongo.dsl.tree.AbstractBsonNode +import kotlin.reflect.KType /** * Replaces a single element in a collection. @@ -47,10 +48,15 @@ class ReplaceOne private constructor( val options: ReplaceOptions, val filter: FilterQuery, val document: Document, + val documentType: KType, ) : AbstractBsonNode(context), Command, AvailableInBulkWrite { @OptIn(LowLevelApi::class) - constructor(context: BsonContext, document: Document) : this(context, ReplaceOptions(context), FilterQuery(context), document) + constructor( + context: BsonContext, + document: Document, + documentType: KType, + ) : this(context, ReplaceOptions(context), FilterQuery(context), document, documentType) @LowLevelApi override fun write(writer: BsonFieldWriter) = with(writer) { @@ -59,7 +65,7 @@ class ReplaceOne private constructor( writeDocument("q") { filter.writeTo(this) } - writeObjectSafe("u", document) + writeSafe("u", document, documentType) writeBoolean("upsert", false) writeBoolean("multi", false) } @@ -90,10 +96,15 @@ class RepsertOne private constructor( val options: ReplaceOptions, val filter: FilterQuery, val document: Document, + val documentType: KType, ) : AbstractBsonNode(context), Command, AvailableInBulkWrite { @OptIn(LowLevelApi::class) - constructor(context: BsonContext, document: Document) : this(context, ReplaceOptions(context), FilterQuery(context), document) + constructor( + context: BsonContext, + document: Document, + documentType: KType, + ) : this(context, ReplaceOptions(context), FilterQuery(context), document, documentType) @LowLevelApi override fun write(writer: BsonFieldWriter) = with(writer) { @@ -102,7 +113,7 @@ class RepsertOne private constructor( writeDocument("q") { filter.writeTo(this) } - writeObjectSafe("u", document) + writeSafe("u", document, documentType) writeBoolean("upsert", true) writeBoolean("multi", false) } diff --git a/dsl-template/src/commonMain/kotlin/query/FilterQuery.kt b/dsl-template/src/commonMain/kotlin/query/FilterQuery.kt index 93e18117..894e736b 100644 --- a/dsl-template/src/commonMain/kotlin/query/FilterQuery.kt +++ b/dsl-template/src/commonMain/kotlin/query/FilterQuery.kt @@ -16,6 +16,7 @@ package opensavvy.ktmongo.dsl.query +import opensavvy.ktmongo.bson.BsonDocument import opensavvy.ktmongo.bson.BsonType import opensavvy.ktmongo.bson.DEPRECATED_IN_BSON_SPEC import opensavvy.ktmongo.dsl.DangerousMongoApi @@ -665,7 +666,7 @@ interface FilterQuery : CompoundBsonNode, FieldDsl { fun Field>.isMapEmpty() { or { doesNotExist() - FieldImpl(path) eq context.buildDocument { } + FieldImpl(path).eq(context.buildDocument { }, typeOf()) } } @@ -724,7 +725,7 @@ interface FilterQuery : CompoundBsonNode, FieldDsl { @OptIn(LowLevelApi::class) @KtMongoDsl fun Field>.isMapNotEmpty() { - FieldImpl(path) gt context.buildDocument { } + FieldImpl(path).gt(context.buildDocument { }, typeOf()) } // endregion diff --git a/dsl-template/src/commonMain/kotlin/query/FilterQueryImpl.kt b/dsl-template/src/commonMain/kotlin/query/FilterQueryImpl.kt index 123a6f04..9372ef50 100644 --- a/dsl-template/src/commonMain/kotlin/query/FilterQueryImpl.kt +++ b/dsl-template/src/commonMain/kotlin/query/FilterQueryImpl.kt @@ -240,7 +240,7 @@ private class FilterQueryImpl( @OptIn(LowLevelApi::class, DangerousMongoApi::class) @KtMongoDsl override fun Field>.containsAll(values: Collection, type: KType) { - accept(ArrayAllBsonNodeNode(path, values, context)) + accept(ArrayAllBsonNodeNode(path, values, context, type)) } @LowLevelApi @@ -248,13 +248,14 @@ private class FilterQueryImpl( val path: Path, val values: Collection, context: BsonContext, + val type: KType, ) : FilterBsonNodeNode(context) { override fun write(writer: BsonFieldWriter) = with(writer) { writeDocument(path.toString()) { writeArray("\$all") { for (value in values) { - writeObjectSafe(value) + writeSafe(value, type) } } } diff --git a/dsl-template/src/commonMain/kotlin/query/FilterQueryPredicateImpl.kt b/dsl-template/src/commonMain/kotlin/query/FilterQueryPredicateImpl.kt index a941cf01..f5a69e6a 100644 --- a/dsl-template/src/commonMain/kotlin/query/FilterQueryPredicateImpl.kt +++ b/dsl-template/src/commonMain/kotlin/query/FilterQueryPredicateImpl.kt @@ -56,17 +56,18 @@ private class FilterQueryPredicateImpl( @OptIn(LowLevelApi::class, DangerousMongoApi::class) @KtMongoDsl override fun eq(value: T) { - accept(EqualityBsonNodeNode(value, context)) + accept(EqualityBsonNodeNode(value, context, type)) } @LowLevelApi private class EqualityBsonNodeNode( val value: T, context: BsonContext, + val type: KType, ) : PredicateBsonNodeNode(context) { override fun write(writer: BsonFieldWriter) { - writer.writeObjectSafe("\$eq", value) + writer.writeSafe("\$eq", value, type) } } @@ -76,17 +77,18 @@ private class FilterQueryPredicateImpl( @OptIn(LowLevelApi::class, DangerousMongoApi::class) @KtMongoDsl override fun ne(value: T) { - accept(InequalityBsonNodeNode(value, context)) + accept(InequalityBsonNodeNode(value, context, type)) } @LowLevelApi private class InequalityBsonNodeNode( val value: T, context: BsonContext, + val type: KType, ) : PredicateBsonNodeNode(context) { override fun write(writer: BsonFieldWriter) { - writer.writeObjectSafe("\$ne", value) + writer.writeSafe("\$ne", value, type) } } @@ -171,72 +173,76 @@ private class FilterQueryPredicateImpl( @OptIn(LowLevelApi::class, DangerousMongoApi::class) @KtMongoDsl override fun gt(value: T) { - accept(GtPredicateBsonNodeNode(value, context)) + accept(GtPredicateBsonNodeNode(value, context, type)) } @LowLevelApi private class GtPredicateBsonNodeNode( private val value: T, context: BsonContext, + val type: KType, ) : PredicateBsonNodeNode(context) { @LowLevelApi override fun write(writer: BsonFieldWriter) { - writer.writeObjectSafe("\$gt", value) + writer.writeSafe("\$gt", value, type) } } @OptIn(LowLevelApi::class, DangerousMongoApi::class) @KtMongoDsl override fun gte(value: T) { - accept(GtePredicateBsonNodeNode(value, context)) + accept(GtePredicateBsonNodeNode(value, context, type)) } @LowLevelApi private class GtePredicateBsonNodeNode( private val value: T, context: BsonContext, + val type: KType, ) : PredicateBsonNodeNode(context) { @LowLevelApi override fun write(writer: BsonFieldWriter) { - writer.writeObjectSafe("\$gte", value) + writer.writeSafe("\$gte", value, type) } } @OptIn(LowLevelApi::class, DangerousMongoApi::class) @KtMongoDsl override fun lt(value: T) { - accept(LtPredicateBsonNodeNode(value, context)) + accept(LtPredicateBsonNodeNode(value, context, type)) } @LowLevelApi private class LtPredicateBsonNodeNode( private val value: T, context: BsonContext, + val type: KType, ) : PredicateBsonNodeNode(context) { @LowLevelApi override fun write(writer: BsonFieldWriter) { - writer.writeObjectSafe("\$lt", value) + writer.writeSafe("\$lt", value, type) } } @OptIn(LowLevelApi::class, DangerousMongoApi::class) @KtMongoDsl override fun lte(value: T) { - accept(LtePredicateBsonNodeNode(value, context)) + accept(LtePredicateBsonNodeNode(value, context, type)) } @LowLevelApi private class LtePredicateBsonNodeNode( private val value: T, context: BsonContext, + val type: KType, ) : PredicateBsonNodeNode(context) { @LowLevelApi override fun write(writer: BsonFieldWriter) { - writer.writeObjectSafe("\$lte", value) + writer.writeSafe("\$lte", value, type) } } @@ -246,20 +252,21 @@ private class FilterQueryPredicateImpl( @OptIn(LowLevelApi::class, DangerousMongoApi::class) @KtMongoDsl override fun isOneOf(values: Collection) { - accept(OneOfPredicateBsonNodeNode(values, context)) + accept(OneOfPredicateBsonNodeNode(values, context, type)) } @LowLevelApi private class OneOfPredicateBsonNodeNode( val values: Collection, context: BsonContext, + val type: KType, ) : PredicateBsonNodeNode(context) { @LowLevelApi override fun write(writer: BsonFieldWriter) { writer.writeArray("\$in") { for (value in values) - writeObjectSafe(value) + writeSafe(value, type) } } } @@ -270,20 +277,21 @@ private class FilterQueryPredicateImpl( @OptIn(LowLevelApi::class, DangerousMongoApi::class) @KtMongoDsl override fun isNotOneOf(values: Collection) { - accept(NotOneOfPredicateExpressionNode(values, context)) + accept(NotOneOfPredicateExpressionNode(values, context, type)) } @LowLevelApi private class NotOneOfPredicateExpressionNode( val values: Collection, context: BsonContext, + val type: KType, ) : PredicateBsonNodeNode(context) { @LowLevelApi override fun write(writer: BsonFieldWriter) { writer.writeArray("\$nin") { for (value in values) - writeObjectSafe(value) + writeSafe(value, type) } } } diff --git a/dsl-template/src/commonMain/kotlin/query/UpdateQueryImpl.kt b/dsl-template/src/commonMain/kotlin/query/UpdateQueryImpl.kt index c6d4e0c9..7a4a783f 100644 --- a/dsl-template/src/commonMain/kotlin/query/UpdateQueryImpl.kt +++ b/dsl-template/src/commonMain/kotlin/query/UpdateQueryImpl.kt @@ -84,6 +84,14 @@ private class UpdateQueryImpl( return this } + private class Value( + val value: Any?, + val type: KType, + ) { + override fun toString(): String = + "Value($value, $type)" + } + @LowLevelApi private sealed class UpdateBsonNodeNode(context: BsonContext) : AbstractBsonNode(context) @@ -94,12 +102,12 @@ private class UpdateQueryImpl( @Suppress("INVISIBLE_REFERENCE") @KtMongoDsl override fun <@kotlin.internal.OnlyInputTypes V> Field.set(value: V, type: KType) { - accept(SetBsonNodeNode(listOf(this.path to value), context)) + accept(SetBsonNodeNode(listOf(this.path to Value(value, type)), context)) } @LowLevelApi private class SetBsonNodeNode( - val mappings: List>, + val mappings: List>, context: BsonContext, ) : UpdateBsonNodeNode(context) { @@ -109,7 +117,7 @@ private class UpdateQueryImpl( override fun write(writer: BsonFieldWriter) = with(writer) { writeDocument("\$set") { for ((field, value) in mappings) { - writeObjectSafe(field.toString(), value) + writeSafe(field.toString(), value.value, value.type) } } } @@ -122,12 +130,12 @@ private class UpdateQueryImpl( @Suppress("INVISIBLE_REFERENCE") @KtMongoDsl override fun <@kotlin.internal.OnlyInputTypes V> Field.setOnInsert(value: V, type: KType) { - accept(SetOnInsertBsonNodeNode(listOf(this.path to value), context)) + accept(SetOnInsertBsonNodeNode(listOf(this.path to Value(value, type)), context)) } @LowLevelApi private class SetOnInsertBsonNodeNode( - val mappings: List>, + val mappings: List>, context: BsonContext, ) : UpdateBsonNodeNode(context) { override fun simplify(): AbstractBsonNode? = @@ -136,7 +144,7 @@ private class UpdateQueryImpl( override fun write(writer: BsonFieldWriter) = with(writer) { writeDocument("\$setOnInsert") { for ((field, value) in mappings) { - writeObjectSafe(field.toString(), value) + writeSafe(field.toString(), value.value, value.type) } } } @@ -149,12 +157,12 @@ private class UpdateQueryImpl( @Suppress("INVISIBLE_REFERENCE") @KtMongoDsl override fun <@kotlin.internal.OnlyInputTypes V : Number> Field.inc(amount: V, type: KType) { - accept(IncrementBsonNodeNode(listOf(this.path to amount), context)) + accept(IncrementBsonNodeNode(listOf(this.path to Value(amount, type)), context)) } @LowLevelApi private class IncrementBsonNodeNode( - val mappings: List>, + val mappings: List>, context: BsonContext, ) : UpdateBsonNodeNode(context) { override fun simplify(): AbstractBsonNode? = @@ -163,7 +171,7 @@ private class UpdateQueryImpl( override fun write(writer: BsonFieldWriter) = with(writer) { writeDocument("\$inc") { for ((field, value) in mappings) { - writeObjectSafe(field.toString(), value) + writeSafe(field.toString(), value.value, value.type) } } } @@ -176,12 +184,12 @@ private class UpdateQueryImpl( @Suppress("INVISIBLE_REFERENCE") @KtMongoDsl override fun <@kotlin.internal.OnlyInputTypes V : Number> Field.mul(amount: V, type: KType) { - accept(MultiplyBsonNodeNode(listOf(this.path to amount), context)) + accept(MultiplyBsonNodeNode(listOf(this.path to Value(amount, type)), context)) } @LowLevelApi private class MultiplyBsonNodeNode( - val mappings: List>, + val mappings: List>, context: BsonContext, ) : UpdateBsonNodeNode(context) { override fun simplify(): AbstractBsonNode? = @@ -190,7 +198,7 @@ private class UpdateQueryImpl( override fun write(writer: BsonFieldWriter) = with(writer) { writeDocument("\$mul") { for ((field, value) in mappings) { - writeObjectSafe(field.toString(), value) + writeSafe(field.toString(), value.value, value.type) } } } @@ -230,12 +238,12 @@ private class UpdateQueryImpl( @Suppress("INVISIBLE_REFERENCE") @KtMongoDsl override fun <@kotlin.internal.OnlyInputTypes V : Comparable> Field.min(value: V, type: KType) { - accept(MinBsonNodeNode(listOf(this.path to value), context)) + accept(MinBsonNodeNode(listOf(this.path to Value(value, type)), context)) } @LowLevelApi private class MinBsonNodeNode( - val mappings: List>, + val mappings: List>, context: BsonContext, ) : UpdateBsonNodeNode(context) { override fun simplify(): AbstractBsonNode? = @@ -244,7 +252,7 @@ private class UpdateQueryImpl( override fun write(writer: BsonFieldWriter) = with(writer) { writeDocument("\$min") { for ((field, value) in mappings) { - writeObjectSafe(field.toString(), value) + writeSafe(field.toString(), value.value, value.type) } } } @@ -257,12 +265,12 @@ private class UpdateQueryImpl( @Suppress("INVISIBLE_REFERENCE") @KtMongoDsl override fun <@kotlin.internal.OnlyInputTypes V : Comparable> Field.max(value: V, type: KType) { - accept(MaxBsonNodeNode(listOf(this.path to value), context)) + accept(MaxBsonNodeNode(listOf(this.path to Value(value, type)), context)) } @LowLevelApi private class MaxBsonNodeNode( - val mappings: List>, + val mappings: List>, context: BsonContext, ) : UpdateBsonNodeNode(context) { override fun simplify(): AbstractBsonNode? = @@ -271,7 +279,7 @@ private class UpdateQueryImpl( override fun write(writer: BsonFieldWriter) = with(writer) { writeDocument("\$max") { for ((field, value) in mappings) { - writeObjectSafe(field.toString(), value) + writeSafe(field.toString(), value.value, value.type) } } } @@ -350,12 +358,12 @@ private class UpdateQueryImpl( @OptIn(DangerousMongoApi::class, LowLevelApi::class) override fun Field>.addToSet(value: V, type: KType) { - accept(AddToSetBsonNode(listOf(this.path to value), context)) + accept(AddToSetBsonNode(listOf(this.path to Value(value, type)), context)) } @LowLevelApi private class AddToSetBsonNode( - val mappings: List>, + val mappings: List>, context: BsonContext, ) : UpdateBsonNodeNode(context) { @@ -371,12 +379,12 @@ private class UpdateQueryImpl( writeDocument($$"$addToSet") { for ((field, values) in groupedMappings) { if (values.size == 1) - writeObjectSafe(field.toString(), values.single()) + writeSafe(field.toString(), values.single().value, values.single().type) else writeDocument(field.toString()) { writeArray($$"$each") { for (value in values) - writeObjectSafe(value) + writeSafe(value.value, value.type) } } } diff --git a/dsl/src/commonMain/kotlin/aggregation/operators/ArrayValueOperators.kt b/dsl/src/commonMain/kotlin/aggregation/operators/ArrayValueOperators.kt index aa193299..d8110fd4 100644 --- a/dsl/src/commonMain/kotlin/aggregation/operators/ArrayValueOperators.kt +++ b/dsl/src/commonMain/kotlin/aggregation/operators/ArrayValueOperators.kt @@ -362,7 +362,7 @@ interface ArrayValueOperators : ValueOperators { @Suppress("INVISIBLE_REFERENCE") @KtMongoDsl fun Value>.filter( - limit: Value? = null, + limit: Value? = null, variableName: String = "this", predicate: AggregationOperators.(Value) -> Value, ): Value> = @@ -410,7 +410,7 @@ interface ArrayValueOperators : ValueOperators { @Suppress("INAPPLICABLE_JVM_NAME", "INVISIBLE_REFERENCE") @KtMongoDsl fun Value>.filter( - limit: opensavvy.ktmongo.dsl.path.Field, + limit: opensavvy.ktmongo.dsl.path.Field, variableName: String = "this", predicate: AggregationOperators.(Value) -> Value, ): Value> = @@ -452,7 +452,7 @@ interface ArrayValueOperators : ValueOperators { @Suppress("INAPPLICABLE_JVM_NAME", "INVISIBLE_REFERENCE") @KtMongoDsl fun Value>.filter( - limit: kotlin.reflect.KProperty1, + limit: kotlin.reflect.KProperty1, variableName: String = "this", predicate: AggregationOperators.(Value) -> Value, ): Value> = @@ -494,7 +494,7 @@ interface ArrayValueOperators : ValueOperators { @Suppress("INVISIBLE_REFERENCE") @KtMongoDsl fun Value>.filter( - limit: Number, + limit: Int, variableName: String = "this", predicate: AggregationOperators.(Value) -> Value, ): Value> = @@ -536,7 +536,7 @@ interface ArrayValueOperators : ValueOperators { @Suppress("INAPPLICABLE_JVM_NAME", "INVISIBLE_REFERENCE") @KtMongoDsl fun opensavvy.ktmongo.dsl.path.Field>.filter( - limit: Value? = null, + limit: Value? = null, variableName: String = "this", predicate: AggregationOperators.(Value) -> Value, ): Value> = @@ -578,7 +578,7 @@ interface ArrayValueOperators : ValueOperators { @Suppress("INAPPLICABLE_JVM_NAME", "INVISIBLE_REFERENCE") @KtMongoDsl fun opensavvy.ktmongo.dsl.path.Field>.filter( - limit: opensavvy.ktmongo.dsl.path.Field, + limit: opensavvy.ktmongo.dsl.path.Field, variableName: String = "this", predicate: AggregationOperators.(Value) -> Value, ): Value> = @@ -620,7 +620,7 @@ interface ArrayValueOperators : ValueOperators { @Suppress("INAPPLICABLE_JVM_NAME", "INVISIBLE_REFERENCE") @KtMongoDsl fun opensavvy.ktmongo.dsl.path.Field>.filter( - limit: kotlin.reflect.KProperty1, + limit: kotlin.reflect.KProperty1, variableName: String = "this", predicate: AggregationOperators.(Value) -> Value, ): Value> = @@ -662,7 +662,7 @@ interface ArrayValueOperators : ValueOperators { @Suppress("INAPPLICABLE_JVM_NAME", "INVISIBLE_REFERENCE") @KtMongoDsl fun opensavvy.ktmongo.dsl.path.Field>.filter( - limit: Number, + limit: Int, variableName: String = "this", predicate: AggregationOperators.(Value) -> Value, ): Value> = @@ -704,7 +704,7 @@ interface ArrayValueOperators : ValueOperators { @Suppress("INAPPLICABLE_JVM_NAME", "INVISIBLE_REFERENCE") @KtMongoDsl fun kotlin.reflect.KProperty1>.filter( - limit: Value? = null, + limit: Value? = null, variableName: String = "this", predicate: AggregationOperators.(Value) -> Value, ): Value> = @@ -746,7 +746,7 @@ interface ArrayValueOperators : ValueOperators { @Suppress("INAPPLICABLE_JVM_NAME", "INVISIBLE_REFERENCE") @KtMongoDsl fun kotlin.reflect.KProperty1>.filter( - limit: opensavvy.ktmongo.dsl.path.Field, + limit: opensavvy.ktmongo.dsl.path.Field, variableName: String = "this", predicate: AggregationOperators.(Value) -> Value, ): Value> = @@ -788,7 +788,7 @@ interface ArrayValueOperators : ValueOperators { @Suppress("INAPPLICABLE_JVM_NAME", "INVISIBLE_REFERENCE") @KtMongoDsl fun kotlin.reflect.KProperty1>.filter( - limit: kotlin.reflect.KProperty1, + limit: kotlin.reflect.KProperty1, variableName: String = "this", predicate: AggregationOperators.(Value) -> Value, ): Value> = @@ -830,7 +830,7 @@ interface ArrayValueOperators : ValueOperators { @Suppress("INAPPLICABLE_JVM_NAME", "INVISIBLE_REFERENCE") @KtMongoDsl fun kotlin.reflect.KProperty1>.filter( - limit: Number, + limit: Int, variableName: String = "this", predicate: AggregationOperators.(Value) -> Value, ): Value> = @@ -873,7 +873,7 @@ interface ArrayValueOperators : ValueOperators { @Suppress("INAPPLICABLE_JVM_NAME", "INVISIBLE_REFERENCE") @KtMongoDsl fun Collection.filter( - limit: Value? = null, + limit: Value? = null, variableName: String = "this", predicate: AggregationOperators.(Value) -> Value, ): Value> = @@ -916,7 +916,7 @@ interface ArrayValueOperators : ValueOperators { @Suppress("INAPPLICABLE_JVM_NAME", "INVISIBLE_REFERENCE") @KtMongoDsl fun Collection.filter( - limit: opensavvy.ktmongo.dsl.path.Field, + limit: opensavvy.ktmongo.dsl.path.Field, variableName: String = "this", predicate: AggregationOperators.(Value) -> Value, ): Value> = @@ -959,7 +959,7 @@ interface ArrayValueOperators : ValueOperators { @Suppress("INAPPLICABLE_JVM_NAME", "INVISIBLE_REFERENCE") @KtMongoDsl fun Collection.filter( - limit: kotlin.reflect.KProperty1, + limit: kotlin.reflect.KProperty1, variableName: String = "this", predicate: AggregationOperators.(Value) -> Value, ): Value> = @@ -1002,7 +1002,7 @@ interface ArrayValueOperators : ValueOperators { @Suppress("INAPPLICABLE_JVM_NAME", "INVISIBLE_REFERENCE") @KtMongoDsl fun Collection.filter( - limit: Number, + limit: Int, variableName: String = "this", predicate: AggregationOperators.(Value) -> Value, ): Value> = @@ -1083,7 +1083,7 @@ interface ArrayValueOperators : ValueOperators { @OptIn(LowLevelApi::class) @KtMongoDsl fun Value>.take( - limit: Value, + limit: Value, ): Value> = TakeValueOperator( input = this, @@ -1119,7 +1119,7 @@ interface ArrayValueOperators : ValueOperators { @OptIn(LowLevelApi::class) @KtMongoDsl fun Value>.take( - limit: opensavvy.ktmongo.dsl.path.Field, + limit: opensavvy.ktmongo.dsl.path.Field, ): Value> = this.take(of(limit)) @@ -1151,7 +1151,7 @@ interface ArrayValueOperators : ValueOperators { @OptIn(LowLevelApi::class) @KtMongoDsl fun Value>.take( - limit: kotlin.reflect.KProperty1, + limit: kotlin.reflect.KProperty1, ): Value> = this.take(of(limit)) @@ -1183,7 +1183,7 @@ interface ArrayValueOperators : ValueOperators { @OptIn(LowLevelApi::class) @KtMongoDsl final inline fun Value>.take( - limit: Number, + limit: Int, ): Value> = this.take(of(limit)) @@ -1215,7 +1215,7 @@ interface ArrayValueOperators : ValueOperators { @OptIn(LowLevelApi::class) @KtMongoDsl fun opensavvy.ktmongo.dsl.path.Field>.take( - limit: Value, + limit: Value, ): Value> = of(this).take(limit) @@ -1247,7 +1247,7 @@ interface ArrayValueOperators : ValueOperators { @OptIn(LowLevelApi::class) @KtMongoDsl fun opensavvy.ktmongo.dsl.path.Field>.take( - limit: opensavvy.ktmongo.dsl.path.Field, + limit: opensavvy.ktmongo.dsl.path.Field, ): Value> = of(this).take(of(limit)) @@ -1279,7 +1279,7 @@ interface ArrayValueOperators : ValueOperators { @OptIn(LowLevelApi::class) @KtMongoDsl fun opensavvy.ktmongo.dsl.path.Field>.take( - limit: kotlin.reflect.KProperty1, + limit: kotlin.reflect.KProperty1, ): Value> = of(this).take(of(limit)) @@ -1311,7 +1311,7 @@ interface ArrayValueOperators : ValueOperators { @OptIn(LowLevelApi::class) @KtMongoDsl final inline fun opensavvy.ktmongo.dsl.path.Field>.take( - limit: Number, + limit: Int, ): Value> = of(this).take(of(limit)) @@ -1343,7 +1343,7 @@ interface ArrayValueOperators : ValueOperators { @OptIn(LowLevelApi::class) @KtMongoDsl fun kotlin.reflect.KProperty1>.take( - limit: Value, + limit: Value, ): Value> = of(this).take(limit) @@ -1375,7 +1375,7 @@ interface ArrayValueOperators : ValueOperators { @OptIn(LowLevelApi::class) @KtMongoDsl fun kotlin.reflect.KProperty1>.take( - limit: opensavvy.ktmongo.dsl.path.Field, + limit: opensavvy.ktmongo.dsl.path.Field, ): Value> = of(this).take(of(limit)) @@ -1407,7 +1407,7 @@ interface ArrayValueOperators : ValueOperators { @OptIn(LowLevelApi::class) @KtMongoDsl fun kotlin.reflect.KProperty1>.take( - limit: kotlin.reflect.KProperty1, + limit: kotlin.reflect.KProperty1, ): Value> = of(this).take(of(limit)) @@ -1439,7 +1439,7 @@ interface ArrayValueOperators : ValueOperators { @OptIn(LowLevelApi::class) @KtMongoDsl final inline fun kotlin.reflect.KProperty1>.take( - limit: Number, + limit: Int, ): Value> = of(this).take(of(limit)) @@ -1472,7 +1472,7 @@ interface ArrayValueOperators : ValueOperators { @OptIn(LowLevelApi::class) @KtMongoDsl fun Collection.take( - limit: Value, + limit: Value, ): Value> = of(this).take(limit) @@ -1505,7 +1505,7 @@ interface ArrayValueOperators : ValueOperators { @OptIn(LowLevelApi::class) @KtMongoDsl fun Collection.take( - limit: opensavvy.ktmongo.dsl.path.Field, + limit: opensavvy.ktmongo.dsl.path.Field, ): Value> = of(this).take(of(limit)) @@ -1538,7 +1538,7 @@ interface ArrayValueOperators : ValueOperators { @OptIn(LowLevelApi::class) @KtMongoDsl fun Collection.take( - limit: kotlin.reflect.KProperty1, + limit: kotlin.reflect.KProperty1, ): Value> = of(this).take(of(limit)) @@ -1571,7 +1571,7 @@ interface ArrayValueOperators : ValueOperators { @OptIn(LowLevelApi::class) @KtMongoDsl final inline fun Collection.take( - limit: Number, + limit: Int, ): Value> = of(this).take(of(limit)) @@ -1626,7 +1626,7 @@ interface ArrayValueOperators : ValueOperators { @OptIn(LowLevelApi::class) @KtMongoDsl fun Value>.takeLast( - limit: Value, + limit: Value, ): Value> = TakeLastValueOperator( input = this, @@ -1662,7 +1662,7 @@ interface ArrayValueOperators : ValueOperators { @OptIn(LowLevelApi::class) @KtMongoDsl fun Value>.takeLast( - limit: opensavvy.ktmongo.dsl.path.Field, + limit: opensavvy.ktmongo.dsl.path.Field, ): Value> = this.takeLast(of(limit)) @@ -1694,7 +1694,7 @@ interface ArrayValueOperators : ValueOperators { @OptIn(LowLevelApi::class) @KtMongoDsl fun Value>.takeLast( - limit: kotlin.reflect.KProperty1, + limit: kotlin.reflect.KProperty1, ): Value> = this.takeLast(of(limit)) @@ -1726,7 +1726,7 @@ interface ArrayValueOperators : ValueOperators { @OptIn(LowLevelApi::class) @KtMongoDsl final inline fun Value>.takeLast( - limit: Number, + limit: Int, ): Value> = this.takeLast(of(limit)) @@ -1758,7 +1758,7 @@ interface ArrayValueOperators : ValueOperators { @OptIn(LowLevelApi::class) @KtMongoDsl fun opensavvy.ktmongo.dsl.path.Field>.takeLast( - limit: Value, + limit: Value, ): Value> = of(this).takeLast(limit) @@ -1790,7 +1790,7 @@ interface ArrayValueOperators : ValueOperators { @OptIn(LowLevelApi::class) @KtMongoDsl fun opensavvy.ktmongo.dsl.path.Field>.takeLast( - limit: opensavvy.ktmongo.dsl.path.Field, + limit: opensavvy.ktmongo.dsl.path.Field, ): Value> = of(this).takeLast(of(limit)) @@ -1822,7 +1822,7 @@ interface ArrayValueOperators : ValueOperators { @OptIn(LowLevelApi::class) @KtMongoDsl fun opensavvy.ktmongo.dsl.path.Field>.takeLast( - limit: kotlin.reflect.KProperty1, + limit: kotlin.reflect.KProperty1, ): Value> = of(this).takeLast(of(limit)) @@ -1854,7 +1854,7 @@ interface ArrayValueOperators : ValueOperators { @OptIn(LowLevelApi::class) @KtMongoDsl final inline fun opensavvy.ktmongo.dsl.path.Field>.takeLast( - limit: Number, + limit: Int, ): Value> = of(this).takeLast(of(limit)) @@ -1886,7 +1886,7 @@ interface ArrayValueOperators : ValueOperators { @OptIn(LowLevelApi::class) @KtMongoDsl fun kotlin.reflect.KProperty1>.takeLast( - limit: Value, + limit: Value, ): Value> = of(this).takeLast(limit) @@ -1918,7 +1918,7 @@ interface ArrayValueOperators : ValueOperators { @OptIn(LowLevelApi::class) @KtMongoDsl fun kotlin.reflect.KProperty1>.takeLast( - limit: opensavvy.ktmongo.dsl.path.Field, + limit: opensavvy.ktmongo.dsl.path.Field, ): Value> = of(this).takeLast(of(limit)) @@ -1950,7 +1950,7 @@ interface ArrayValueOperators : ValueOperators { @OptIn(LowLevelApi::class) @KtMongoDsl fun kotlin.reflect.KProperty1>.takeLast( - limit: kotlin.reflect.KProperty1, + limit: kotlin.reflect.KProperty1, ): Value> = of(this).takeLast(of(limit)) @@ -1982,7 +1982,7 @@ interface ArrayValueOperators : ValueOperators { @OptIn(LowLevelApi::class) @KtMongoDsl final inline fun kotlin.reflect.KProperty1>.takeLast( - limit: Number, + limit: Int, ): Value> = of(this).takeLast(of(limit)) @@ -2015,7 +2015,7 @@ interface ArrayValueOperators : ValueOperators { @OptIn(LowLevelApi::class) @KtMongoDsl fun Collection.takeLast( - limit: Value, + limit: Value, ): Value> = of(this).takeLast(limit) @@ -2048,7 +2048,7 @@ interface ArrayValueOperators : ValueOperators { @OptIn(LowLevelApi::class) @KtMongoDsl fun Collection.takeLast( - limit: opensavvy.ktmongo.dsl.path.Field, + limit: opensavvy.ktmongo.dsl.path.Field, ): Value> = of(this).takeLast(of(limit)) @@ -2081,7 +2081,7 @@ interface ArrayValueOperators : ValueOperators { @OptIn(LowLevelApi::class) @KtMongoDsl fun Collection.takeLast( - limit: kotlin.reflect.KProperty1, + limit: kotlin.reflect.KProperty1, ): Value> = of(this).takeLast(of(limit)) @@ -2114,7 +2114,7 @@ interface ArrayValueOperators : ValueOperators { @OptIn(LowLevelApi::class) @KtMongoDsl final inline fun Collection.takeLast( - limit: Number, + limit: Int, ): Value> = of(this).takeLast(of(limit)) diff --git a/dsl/src/commonMain/kotlin/aggregation/operators/ValueOperators.kt b/dsl/src/commonMain/kotlin/aggregation/operators/ValueOperators.kt index 67ae746c..eea87016 100644 --- a/dsl/src/commonMain/kotlin/aggregation/operators/ValueOperators.kt +++ b/dsl/src/commonMain/kotlin/aggregation/operators/ValueOperators.kt @@ -105,7 +105,7 @@ interface ValueOperators : FieldDsl { */ @OptIn(LowLevelApi::class) fun of(value: Result, type: KType): Value = - LiteralValue(value, context) + LiteralValue(value, context, type) /** * Refers to a Kotlin [value] within an [aggregation value][AggregationOperators]. @@ -349,12 +349,13 @@ private class FieldValue( private class LiteralValue( val value: Any?, context: BsonContext, + val type: KType, ) : AbstractValue(context) { @LowLevelApi override fun write(writer: BsonValueWriter) { writer.writeDocument { - writeObjectSafe("\$literal", value) + writeSafe("\$literal", value, type) } } } diff --git a/dsl/src/commonMain/kotlin/command/BulkWrite.kt b/dsl/src/commonMain/kotlin/command/BulkWrite.kt index f141a793..a455d412 100644 --- a/dsl/src/commonMain/kotlin/command/BulkWrite.kt +++ b/dsl/src/commonMain/kotlin/command/BulkWrite.kt @@ -34,6 +34,7 @@ import opensavvy.ktmongo.dsl.tree.AbstractBsonNode import opensavvy.ktmongo.dsl.tree.CompoundNode import opensavvy.ktmongo.dsl.tree.Node import opensavvy.ktmongo.dsl.tree.acceptAll +import kotlin.reflect.KType sealed interface AvailableInBulkWrite : Node, Command @@ -91,12 +92,17 @@ class BulkWrite private constructor( context: BsonContext, private val globalFilter: FilterQuery.() -> Unit, val options: BulkWriteOptions, + val documentType: KType, ) : Command, AbstractBsonNode(context), CompoundNode> { private val _operations = ArrayList>() val operations: Sequence> get() = _operations.asSequence() - constructor(context: BsonContext, globalFilter: FilterQuery.() -> Unit) : this(context, globalFilter, BulkWriteOptions(context)) + constructor( + context: BsonContext, + documentType: KType, + globalFilter: FilterQuery.() -> Unit, + ) : this(context, globalFilter, BulkWriteOptions(context), documentType) @LowLevelApi @DangerousMongoApi @@ -129,10 +135,11 @@ class BulkWrite private constructor( val child = BulkWrite( context = context, + documentType, globalFilter = { parent.globalFilter(this) filter() - } + }, ) child.operations() @@ -170,7 +177,7 @@ class BulkWrite private constructor( document: Document, options: InsertOneOptions.() -> Unit = {}, ) { - val model = InsertOne(context, document) + val model = InsertOne(context, document, documentType) model.options.options() @@ -415,7 +422,7 @@ class BulkWrite private constructor( filter: FilterQuery.() -> Unit = {}, document: Document, ) { - val model = ReplaceOne(context, document) + val model = ReplaceOne(context, document, documentType) model.options.options() model.filter.globalFilter() @@ -464,7 +471,7 @@ class BulkWrite private constructor( filter: FilterQuery.() -> Unit = {}, document: Document, ) { - val model = RepsertOne(context, document) + val model = RepsertOne(context, document, documentType) model.options.options() model.filter.globalFilter() @@ -482,7 +489,7 @@ class BulkWrite private constructor( when (operation) { is InsertOne<*> -> { writeInt32("insert", 0) - writeObjectSafe("document", operation.document) + writeSafe("document", operation.document) operation.options.writeTo(this) } @@ -503,7 +510,7 @@ class BulkWrite private constructor( writeDocument("filter") { operation.filter.writeTo(this) } - writeObjectSafe("updateMods", operation.document) + writeSafe("updateMods", operation.document) writeBoolean("multi", false) operation.options.writeTo(this) } @@ -513,7 +520,7 @@ class BulkWrite private constructor( writeDocument("filter") { operation.filter.writeTo(this) } - writeObjectSafe("updateMods", operation.document) + writeSafe("updateMods", operation.document) writeBoolean("multi", false) writeBoolean("upsert", true) operation.options.writeTo(this) diff --git a/dsl/src/commonMain/kotlin/command/Insert.kt b/dsl/src/commonMain/kotlin/command/Insert.kt index 746b8920..d080c2a0 100644 --- a/dsl/src/commonMain/kotlin/command/Insert.kt +++ b/dsl/src/commonMain/kotlin/command/Insert.kt @@ -27,6 +27,7 @@ import opensavvy.ktmongo.dsl.options.Options import opensavvy.ktmongo.dsl.options.OptionsHolder import opensavvy.ktmongo.dsl.options.WithWriteConcern import opensavvy.ktmongo.dsl.tree.AbstractBsonNode +import kotlin.reflect.KType /** * Inserting a single element in a collection. @@ -49,14 +50,19 @@ class InsertOne private constructor( context: BsonContext, val options: InsertOneOptions, val document: Document, + val documentType: KType, ) : Command, AbstractBsonNode(context), AvailableInBulkWrite { - constructor(context: BsonContext, document: Document) : this(context, InsertOneOptions(context), document) + constructor( + context: BsonContext, + document: Document, + documentType: KType, + ) : this(context, InsertOneOptions(context), document, documentType) @LowLevelApi override fun write(writer: BsonFieldWriter) = with(writer) { writeArray("documents") { - writeObjectSafe(document) + writeSafe(document, documentType) } options.writeTo(this) @@ -84,15 +90,20 @@ class InsertMany private constructor( context: BsonContext, val options: InsertManyOptions, val documents: List, + val documentType: KType, ) : Command, AbstractBsonNode(context) { - constructor(context: BsonContext, documents: List) : this(context, InsertManyOptions(context), documents) + constructor( + context: BsonContext, + documents: List, + documentType: KType, + ) : this(context, InsertManyOptions(context), documents, documentType) @LowLevelApi override fun write(writer: BsonFieldWriter) = with(writer) { writeArray("documents") { for (document in documents) { - writeObjectSafe(document) + writeSafe(document, documentType) } } diff --git a/dsl/src/commonMain/kotlin/command/Replace.kt b/dsl/src/commonMain/kotlin/command/Replace.kt index 6d57ba13..c7db171f 100644 --- a/dsl/src/commonMain/kotlin/command/Replace.kt +++ b/dsl/src/commonMain/kotlin/command/Replace.kt @@ -28,6 +28,7 @@ import opensavvy.ktmongo.dsl.options.OptionsHolder import opensavvy.ktmongo.dsl.options.WithWriteConcern import opensavvy.ktmongo.dsl.query.FilterQuery import opensavvy.ktmongo.dsl.tree.AbstractBsonNode +import kotlin.reflect.KType /** * Replaces a single element in a collection. @@ -50,10 +51,15 @@ class ReplaceOne private constructor( val options: ReplaceOptions, val filter: FilterQuery, val document: Document, + val documentType: KType, ) : AbstractBsonNode(context), Command, AvailableInBulkWrite { @OptIn(LowLevelApi::class) - constructor(context: BsonContext, document: Document) : this(context, ReplaceOptions(context), FilterQuery(context), document) + constructor( + context: BsonContext, + document: Document, + documentType: KType, + ) : this(context, ReplaceOptions(context), FilterQuery(context), document, documentType) @LowLevelApi override fun write(writer: BsonFieldWriter) = with(writer) { @@ -62,7 +68,7 @@ class ReplaceOne private constructor( writeDocument("q") { filter.writeTo(this) } - writeObjectSafe("u", document) + writeSafe("u", document, documentType) writeBoolean("upsert", false) writeBoolean("multi", false) } @@ -93,10 +99,15 @@ class RepsertOne private constructor( val options: ReplaceOptions, val filter: FilterQuery, val document: Document, + val documentType: KType, ) : AbstractBsonNode(context), Command, AvailableInBulkWrite { @OptIn(LowLevelApi::class) - constructor(context: BsonContext, document: Document) : this(context, ReplaceOptions(context), FilterQuery(context), document) + constructor( + context: BsonContext, + document: Document, + documentType: KType, + ) : this(context, ReplaceOptions(context), FilterQuery(context), document, documentType) @LowLevelApi override fun write(writer: BsonFieldWriter) = with(writer) { @@ -105,7 +116,7 @@ class RepsertOne private constructor( writeDocument("q") { filter.writeTo(this) } - writeObjectSafe("u", document) + writeSafe("u", document, documentType) writeBoolean("upsert", true) writeBoolean("multi", false) } diff --git a/dsl/src/commonMain/kotlin/query/FilterQuery.kt b/dsl/src/commonMain/kotlin/query/FilterQuery.kt index a4ce03b9..00194a73 100644 --- a/dsl/src/commonMain/kotlin/query/FilterQuery.kt +++ b/dsl/src/commonMain/kotlin/query/FilterQuery.kt @@ -19,6 +19,7 @@ package opensavvy.ktmongo.dsl.query +import opensavvy.ktmongo.bson.BsonDocument import opensavvy.ktmongo.bson.BsonType import opensavvy.ktmongo.bson.DEPRECATED_IN_BSON_SPEC import opensavvy.ktmongo.dsl.DangerousMongoApi @@ -907,7 +908,7 @@ interface FilterQuery : CompoundBsonNode, FieldDsl { fun Field>.isMapEmpty() { or { doesNotExist() - FieldImpl(path) eq context.buildDocument { } + FieldImpl(path).eq(context.buildDocument { }, typeOf()) } } @@ -1022,7 +1023,7 @@ interface FilterQuery : CompoundBsonNode, FieldDsl { @OptIn(LowLevelApi::class) @KtMongoDsl fun Field>.isMapNotEmpty() { - FieldImpl(path) gt context.buildDocument { } + FieldImpl(path).gt(context.buildDocument { }, typeOf()) } /** diff --git a/dsl/src/commonMain/kotlin/query/FilterQueryImpl.kt b/dsl/src/commonMain/kotlin/query/FilterQueryImpl.kt index a0d3e630..aea75e33 100644 --- a/dsl/src/commonMain/kotlin/query/FilterQueryImpl.kt +++ b/dsl/src/commonMain/kotlin/query/FilterQueryImpl.kt @@ -243,7 +243,7 @@ private class FilterQueryImpl( @OptIn(LowLevelApi::class, DangerousMongoApi::class) @KtMongoDsl override fun Field>.containsAll(values: Collection, type: KType) { - accept(ArrayAllBsonNodeNode(path, values, context)) + accept(ArrayAllBsonNodeNode(path, values, context, type)) } @LowLevelApi @@ -251,13 +251,14 @@ private class FilterQueryImpl( val path: Path, val values: Collection, context: BsonContext, + val type: KType, ) : FilterBsonNodeNode(context) { override fun write(writer: BsonFieldWriter) = with(writer) { writeDocument(path.toString()) { writeArray("\$all") { for (value in values) { - writeObjectSafe(value) + writeSafe(value, type) } } } diff --git a/dsl/src/commonMain/kotlin/query/FilterQueryPredicateImpl.kt b/dsl/src/commonMain/kotlin/query/FilterQueryPredicateImpl.kt index 885a54b5..0bbbd7d8 100644 --- a/dsl/src/commonMain/kotlin/query/FilterQueryPredicateImpl.kt +++ b/dsl/src/commonMain/kotlin/query/FilterQueryPredicateImpl.kt @@ -59,17 +59,18 @@ private class FilterQueryPredicateImpl( @OptIn(LowLevelApi::class, DangerousMongoApi::class) @KtMongoDsl override fun eq(value: T) { - accept(EqualityBsonNodeNode(value, context)) + accept(EqualityBsonNodeNode(value, context, type)) } @LowLevelApi private class EqualityBsonNodeNode( val value: T, context: BsonContext, + val type: KType, ) : PredicateBsonNodeNode(context) { override fun write(writer: BsonFieldWriter) { - writer.writeObjectSafe("\$eq", value) + writer.writeSafe("\$eq", value, type) } } @@ -79,17 +80,18 @@ private class FilterQueryPredicateImpl( @OptIn(LowLevelApi::class, DangerousMongoApi::class) @KtMongoDsl override fun ne(value: T) { - accept(InequalityBsonNodeNode(value, context)) + accept(InequalityBsonNodeNode(value, context, type)) } @LowLevelApi private class InequalityBsonNodeNode( val value: T, context: BsonContext, + val type: KType, ) : PredicateBsonNodeNode(context) { override fun write(writer: BsonFieldWriter) { - writer.writeObjectSafe("\$ne", value) + writer.writeSafe("\$ne", value, type) } } @@ -174,72 +176,76 @@ private class FilterQueryPredicateImpl( @OptIn(LowLevelApi::class, DangerousMongoApi::class) @KtMongoDsl override fun gt(value: T) { - accept(GtPredicateBsonNodeNode(value, context)) + accept(GtPredicateBsonNodeNode(value, context, type)) } @LowLevelApi private class GtPredicateBsonNodeNode( private val value: T, context: BsonContext, + val type: KType, ) : PredicateBsonNodeNode(context) { @LowLevelApi override fun write(writer: BsonFieldWriter) { - writer.writeObjectSafe("\$gt", value) + writer.writeSafe("\$gt", value, type) } } @OptIn(LowLevelApi::class, DangerousMongoApi::class) @KtMongoDsl override fun gte(value: T) { - accept(GtePredicateBsonNodeNode(value, context)) + accept(GtePredicateBsonNodeNode(value, context, type)) } @LowLevelApi private class GtePredicateBsonNodeNode( private val value: T, context: BsonContext, + val type: KType, ) : PredicateBsonNodeNode(context) { @LowLevelApi override fun write(writer: BsonFieldWriter) { - writer.writeObjectSafe("\$gte", value) + writer.writeSafe("\$gte", value, type) } } @OptIn(LowLevelApi::class, DangerousMongoApi::class) @KtMongoDsl override fun lt(value: T) { - accept(LtPredicateBsonNodeNode(value, context)) + accept(LtPredicateBsonNodeNode(value, context, type)) } @LowLevelApi private class LtPredicateBsonNodeNode( private val value: T, context: BsonContext, + val type: KType, ) : PredicateBsonNodeNode(context) { @LowLevelApi override fun write(writer: BsonFieldWriter) { - writer.writeObjectSafe("\$lt", value) + writer.writeSafe("\$lt", value, type) } } @OptIn(LowLevelApi::class, DangerousMongoApi::class) @KtMongoDsl override fun lte(value: T) { - accept(LtePredicateBsonNodeNode(value, context)) + accept(LtePredicateBsonNodeNode(value, context, type)) } @LowLevelApi private class LtePredicateBsonNodeNode( private val value: T, context: BsonContext, + val type: KType, ) : PredicateBsonNodeNode(context) { @LowLevelApi override fun write(writer: BsonFieldWriter) { - writer.writeObjectSafe("\$lte", value) + writer.writeSafe("\$lte", value, type) } } @@ -249,20 +255,21 @@ private class FilterQueryPredicateImpl( @OptIn(LowLevelApi::class, DangerousMongoApi::class) @KtMongoDsl override fun isOneOf(values: Collection) { - accept(OneOfPredicateBsonNodeNode(values, context)) + accept(OneOfPredicateBsonNodeNode(values, context, type)) } @LowLevelApi private class OneOfPredicateBsonNodeNode( val values: Collection, context: BsonContext, + val type: KType, ) : PredicateBsonNodeNode(context) { @LowLevelApi override fun write(writer: BsonFieldWriter) { writer.writeArray("\$in") { for (value in values) - writeObjectSafe(value) + writeSafe(value, type) } } } @@ -273,20 +280,21 @@ private class FilterQueryPredicateImpl( @OptIn(LowLevelApi::class, DangerousMongoApi::class) @KtMongoDsl override fun isNotOneOf(values: Collection) { - accept(NotOneOfPredicateExpressionNode(values, context)) + accept(NotOneOfPredicateExpressionNode(values, context, type)) } @LowLevelApi private class NotOneOfPredicateExpressionNode( val values: Collection, context: BsonContext, + val type: KType, ) : PredicateBsonNodeNode(context) { @LowLevelApi override fun write(writer: BsonFieldWriter) { writer.writeArray("\$nin") { for (value in values) - writeObjectSafe(value) + writeSafe(value, type) } } } diff --git a/dsl/src/commonMain/kotlin/query/UpdateQueryImpl.kt b/dsl/src/commonMain/kotlin/query/UpdateQueryImpl.kt index a7d4967e..f3cbb9c1 100644 --- a/dsl/src/commonMain/kotlin/query/UpdateQueryImpl.kt +++ b/dsl/src/commonMain/kotlin/query/UpdateQueryImpl.kt @@ -87,6 +87,14 @@ private class UpdateQueryImpl( return this } + private class Value( + val value: Any?, + val type: KType, + ) { + override fun toString(): String = + "Value($value, $type)" + } + @LowLevelApi private sealed class UpdateBsonNodeNode(context: BsonContext) : AbstractBsonNode(context) @@ -97,12 +105,12 @@ private class UpdateQueryImpl( @Suppress("INVISIBLE_REFERENCE") @KtMongoDsl override fun <@kotlin.internal.OnlyInputTypes V> Field.set(value: V, type: KType) { - accept(SetBsonNodeNode(listOf(this.path to value), context)) + accept(SetBsonNodeNode(listOf(this.path to Value(value, type)), context)) } @LowLevelApi private class SetBsonNodeNode( - val mappings: List>, + val mappings: List>, context: BsonContext, ) : UpdateBsonNodeNode(context) { @@ -112,7 +120,7 @@ private class UpdateQueryImpl( override fun write(writer: BsonFieldWriter) = with(writer) { writeDocument("\$set") { for ((field, value) in mappings) { - writeObjectSafe(field.toString(), value) + writeSafe(field.toString(), value.value, value.type) } } } @@ -125,12 +133,12 @@ private class UpdateQueryImpl( @Suppress("INVISIBLE_REFERENCE") @KtMongoDsl override fun <@kotlin.internal.OnlyInputTypes V> Field.setOnInsert(value: V, type: KType) { - accept(SetOnInsertBsonNodeNode(listOf(this.path to value), context)) + accept(SetOnInsertBsonNodeNode(listOf(this.path to Value(value, type)), context)) } @LowLevelApi private class SetOnInsertBsonNodeNode( - val mappings: List>, + val mappings: List>, context: BsonContext, ) : UpdateBsonNodeNode(context) { override fun simplify(): AbstractBsonNode? = @@ -139,7 +147,7 @@ private class UpdateQueryImpl( override fun write(writer: BsonFieldWriter) = with(writer) { writeDocument("\$setOnInsert") { for ((field, value) in mappings) { - writeObjectSafe(field.toString(), value) + writeSafe(field.toString(), value.value, value.type) } } } @@ -152,12 +160,12 @@ private class UpdateQueryImpl( @Suppress("INVISIBLE_REFERENCE") @KtMongoDsl override fun <@kotlin.internal.OnlyInputTypes V : Number> Field.inc(amount: V, type: KType) { - accept(IncrementBsonNodeNode(listOf(this.path to amount), context)) + accept(IncrementBsonNodeNode(listOf(this.path to Value(amount, type)), context)) } @LowLevelApi private class IncrementBsonNodeNode( - val mappings: List>, + val mappings: List>, context: BsonContext, ) : UpdateBsonNodeNode(context) { override fun simplify(): AbstractBsonNode? = @@ -166,7 +174,7 @@ private class UpdateQueryImpl( override fun write(writer: BsonFieldWriter) = with(writer) { writeDocument("\$inc") { for ((field, value) in mappings) { - writeObjectSafe(field.toString(), value) + writeSafe(field.toString(), value.value, value.type) } } } @@ -179,12 +187,12 @@ private class UpdateQueryImpl( @Suppress("INVISIBLE_REFERENCE") @KtMongoDsl override fun <@kotlin.internal.OnlyInputTypes V : Number> Field.mul(amount: V, type: KType) { - accept(MultiplyBsonNodeNode(listOf(this.path to amount), context)) + accept(MultiplyBsonNodeNode(listOf(this.path to Value(amount, type)), context)) } @LowLevelApi private class MultiplyBsonNodeNode( - val mappings: List>, + val mappings: List>, context: BsonContext, ) : UpdateBsonNodeNode(context) { override fun simplify(): AbstractBsonNode? = @@ -193,7 +201,7 @@ private class UpdateQueryImpl( override fun write(writer: BsonFieldWriter) = with(writer) { writeDocument("\$mul") { for ((field, value) in mappings) { - writeObjectSafe(field.toString(), value) + writeSafe(field.toString(), value.value, value.type) } } } @@ -233,12 +241,12 @@ private class UpdateQueryImpl( @Suppress("INVISIBLE_REFERENCE") @KtMongoDsl override fun <@kotlin.internal.OnlyInputTypes V : Comparable> Field.min(value: V, type: KType) { - accept(MinBsonNodeNode(listOf(this.path to value), context)) + accept(MinBsonNodeNode(listOf(this.path to Value(value, type)), context)) } @LowLevelApi private class MinBsonNodeNode( - val mappings: List>, + val mappings: List>, context: BsonContext, ) : UpdateBsonNodeNode(context) { override fun simplify(): AbstractBsonNode? = @@ -247,7 +255,7 @@ private class UpdateQueryImpl( override fun write(writer: BsonFieldWriter) = with(writer) { writeDocument("\$min") { for ((field, value) in mappings) { - writeObjectSafe(field.toString(), value) + writeSafe(field.toString(), value.value, value.type) } } } @@ -260,12 +268,12 @@ private class UpdateQueryImpl( @Suppress("INVISIBLE_REFERENCE") @KtMongoDsl override fun <@kotlin.internal.OnlyInputTypes V : Comparable> Field.max(value: V, type: KType) { - accept(MaxBsonNodeNode(listOf(this.path to value), context)) + accept(MaxBsonNodeNode(listOf(this.path to Value(value, type)), context)) } @LowLevelApi private class MaxBsonNodeNode( - val mappings: List>, + val mappings: List>, context: BsonContext, ) : UpdateBsonNodeNode(context) { override fun simplify(): AbstractBsonNode? = @@ -274,7 +282,7 @@ private class UpdateQueryImpl( override fun write(writer: BsonFieldWriter) = with(writer) { writeDocument("\$max") { for ((field, value) in mappings) { - writeObjectSafe(field.toString(), value) + writeSafe(field.toString(), value.value, value.type) } } } @@ -353,12 +361,12 @@ private class UpdateQueryImpl( @OptIn(DangerousMongoApi::class, LowLevelApi::class) override fun Field>.addToSet(value: V, type: KType) { - accept(AddToSetBsonNode(listOf(this.path to value), context)) + accept(AddToSetBsonNode(listOf(this.path to Value(value, type)), context)) } @LowLevelApi private class AddToSetBsonNode( - val mappings: List>, + val mappings: List>, context: BsonContext, ) : UpdateBsonNodeNode(context) { @@ -374,12 +382,12 @@ private class UpdateQueryImpl( writeDocument($$"$addToSet") { for ((field, values) in groupedMappings) { if (values.size == 1) - writeObjectSafe(field.toString(), values.single()) + writeSafe(field.toString(), values.single().value, values.single().type) else writeDocument(field.toString()) { writeArray($$"$each") { for (value in values) - writeObjectSafe(value) + writeSafe(value.value, value.type) } } } diff --git a/dsl/src/commonTest/kotlin/command/BulkWriteTest.kt b/dsl/src/commonTest/kotlin/command/BulkWriteTest.kt index d3ea0281..a34226c3 100644 --- a/dsl/src/commonTest/kotlin/command/BulkWriteTest.kt +++ b/dsl/src/commonTest/kotlin/command/BulkWriteTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, OpenSavvy and contributors. + * Copyright (c) 2025-2026, 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. @@ -20,6 +20,7 @@ import opensavvy.ktmongo.dsl.options.WriteConcern import opensavvy.ktmongo.dsl.query.shouldBeBson import opensavvy.ktmongo.dsl.query.testContext import opensavvy.prepared.runner.testballoon.preparedSuite +import kotlin.reflect.typeOf import kotlin.time.Duration.Companion.minutes val BulkWriteTest by preparedSuite { @@ -31,7 +32,7 @@ val BulkWriteTest by preparedSuite { ) test("bulkWrite") { - BulkWrite(testContext(), {}).apply { + BulkWrite(testContext(), typeOf(), {}).apply { updateOne( filter = { Target::name eq "Daniel" diff --git a/dsl/src/commonTest/kotlin/command/InsertTest.kt b/dsl/src/commonTest/kotlin/command/InsertTest.kt index 14c674d0..ffbbf7e6 100644 --- a/dsl/src/commonTest/kotlin/command/InsertTest.kt +++ b/dsl/src/commonTest/kotlin/command/InsertTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, OpenSavvy and contributors. + * Copyright (c) 2025-2026, 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. @@ -23,6 +23,7 @@ import opensavvy.ktmongo.dsl.options.WriteConcern import opensavvy.ktmongo.dsl.query.shouldBeBson import opensavvy.ktmongo.dsl.query.testContext import opensavvy.prepared.runner.testballoon.preparedSuite +import kotlin.reflect.typeOf val InsertTest by preparedSuite { @@ -32,7 +33,7 @@ val InsertTest by preparedSuite { writeString("name", "Daniel") } as opensavvy.ktmongo.bson.official.BsonDocument - InsertOne(testContext(), daniel.raw).apply { + InsertOne(testContext(), daniel.raw, typeOf()).apply { options.apply { writeConcern(WriteConcern.FireAndForget) } @@ -67,7 +68,7 @@ val InsertTest by preparedSuite { writeString("name", "Alice") } as opensavvy.ktmongo.bson.official.BsonDocument - InsertMany(testContext(), listOf(daniel.raw, fred.raw, alice.raw)).apply { + InsertMany(testContext(), listOf(daniel.raw, fred.raw, alice.raw), typeOf()).apply { options.apply { writeConcern(WriteConcern.Primary) } -- 2.51.2 From b36f6d81c09dff8be1ae73a5767a131dffc1c2b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Sat, 2 May 2026 21:21:28 +0200 Subject: [PATCH 06/10] feat(bson-official): Add a codec for the common BsonDocument interface --- .../src/jvmMain/kotlin/BsonFactory.jvm.kt | 3 ++- .../jvmMain/kotlin/types/BsonDocument.jvm.kt | 19 ++++++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/bson-official/src/jvmMain/kotlin/BsonFactory.jvm.kt b/bson-official/src/jvmMain/kotlin/BsonFactory.jvm.kt index 506a8256..59867d9d 100644 --- a/bson-official/src/jvmMain/kotlin/BsonFactory.jvm.kt +++ b/bson-official/src/jvmMain/kotlin/BsonFactory.jvm.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, OpenSavvy and contributors. + * Copyright (c) 2024-2026, 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. @@ -67,6 +67,7 @@ actual class BsonFactory( val codecRegistry: CodecRegistry = CodecRegistries.fromRegistries( CodecRegistries.fromCodecs( KotlinBsonDocumentCodec(this), + KotlinCommonBsonDocumentCodec(this), KotlinBsonArrayCodec(this), KotlinObjectIdCodec(), KotlinTimestampCodec(), diff --git a/bson-official/src/jvmMain/kotlin/types/BsonDocument.jvm.kt b/bson-official/src/jvmMain/kotlin/types/BsonDocument.jvm.kt index 620e147d..9deb765f 100644 --- a/bson-official/src/jvmMain/kotlin/types/BsonDocument.jvm.kt +++ b/bson-official/src/jvmMain/kotlin/types/BsonDocument.jvm.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, OpenSavvy and contributors. + * Copyright (c) 2024-2026, 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. @@ -41,3 +41,20 @@ internal class KotlinBsonDocumentCodec( documentCodec.decode(reader, decoderContext)?.let { BsonDocument(it, factory) } } + +internal class KotlinCommonBsonDocumentCodec( + private val factory: BsonFactory, +) : Codec { + private val documentCodec = BsonDocumentCodec() + + override fun encode(writer: BsonWriter, value: opensavvy.ktmongo.bson.BsonDocument, encoderContext: EncoderContext) { + documentCodec.encode(writer, (value as BsonDocument).raw, encoderContext) + } + + override fun getEncoderClass(): Class = + opensavvy.ktmongo.bson.BsonDocument::class.java + + override fun decode(reader: BsonReader, decoderContext: DecoderContext): opensavvy.ktmongo.bson.BsonDocument? = + documentCodec.decode(reader, decoderContext)?.let { BsonDocument(it, factory) } + +} -- 2.51.2 From 07a98cc53e548fe1816e0df7a937c26a0e27da4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Sat, 2 May 2026 21:22:09 +0200 Subject: [PATCH 07/10] refactor(drivers): Propagate the KType changes --- .../src/jvmMain/kotlin/KMongoExt.kt | 4 +-- .../src/jvmMain/kotlin/JvmMongoCollection.kt | 25 +++++++++++++------ .../src/jvmMain/kotlin/KMongoExt.kt | 4 +-- .../src/jvmMain/kotlin/JvmMongoCollection.kt | 25 +++++++++++++------ 4 files changed, 40 insertions(+), 18 deletions(-) diff --git a/driver-coroutines-kmongo/src/jvmMain/kotlin/KMongoExt.kt b/driver-coroutines-kmongo/src/jvmMain/kotlin/KMongoExt.kt index 06334aa6..160a954a 100644 --- a/driver-coroutines-kmongo/src/jvmMain/kotlin/KMongoExt.kt +++ b/driver-coroutines-kmongo/src/jvmMain/kotlin/KMongoExt.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, OpenSavvy and contributors. + * Copyright (c) 2025-2026, 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. @@ -23,5 +23,5 @@ import opensavvy.ktmongo.coroutines.asKtMongo /** * Converts a collection from the official Java MongoDB driver into a KtMongo collection. */ -fun MongoCollection.asKtMongo(): JvmMongoCollection = +inline fun MongoCollection.asKtMongo(): JvmMongoCollection = com.mongodb.kotlin.client.coroutine.MongoCollection(this).asKtMongo() diff --git a/driver-coroutines/src/jvmMain/kotlin/JvmMongoCollection.kt b/driver-coroutines/src/jvmMain/kotlin/JvmMongoCollection.kt index 606a656e..1ab2b10a 100644 --- a/driver-coroutines/src/jvmMain/kotlin/JvmMongoCollection.kt +++ b/driver-coroutines/src/jvmMain/kotlin/JvmMongoCollection.kt @@ -49,6 +49,8 @@ import opensavvy.ktmongo.official.options.* import opensavvy.ktmongo.official.options.toJava import opensavvy.ktmongo.official.toJava import java.util.concurrent.TimeUnit +import kotlin.reflect.KType +import kotlin.reflect.typeOf /** * Implementation of [MongoCollection] based on [MongoDB's MongoCollection][com.mongodb.kotlin.client.coroutine.MongoCollection]. @@ -60,6 +62,7 @@ import java.util.concurrent.TimeUnit class JvmMongoCollection internal constructor( inner: com.mongodb.kotlin.client.coroutine.MongoCollection, nameStrategy: PropertyNameStrategy, + private val documentType: KType, ) : MongoCollection { @LowLevelApi @@ -172,7 +175,7 @@ class JvmMongoCollection internal constructor( filter: FilterQuery.() -> Unit, document: Document, ) { - val model = ReplaceOne(context, document) + val model = ReplaceOne(context, document, documentType) model.options.options() model.filter.filter() @@ -186,7 +189,7 @@ class JvmMongoCollection internal constructor( filter: FilterQuery.() -> Unit, document: Document, ) { - val model = RepsertOne(context, document) + val model = RepsertOne(context, document, documentType) model.options.options() model.filter.filter() @@ -231,7 +234,7 @@ class JvmMongoCollection internal constructor( filter: FilterQuery.() -> Unit, operations: BulkWrite.() -> Unit, ) { - val model = BulkWrite(context, filter) + val model = BulkWrite(context, documentType, filter) model.options.options() model.operations() @@ -298,7 +301,7 @@ class JvmMongoCollection internal constructor( @OptIn(LowLevelApi::class) override suspend fun insertOne(document: Document, options: InsertOneOptions.() -> Unit) { - val model = InsertOne(context, document) + val model = InsertOne(context, document, documentType) model.options.options() @@ -310,7 +313,7 @@ class JvmMongoCollection internal constructor( @OptIn(LowLevelApi::class) override suspend fun insertMany(documents: Iterable, options: InsertManyOptions.() -> Unit) { - val model = InsertMany(context, documents.toList()) + val model = InsertMany(context, documents.toList(), documentType) model.options.options() @@ -404,11 +407,19 @@ class JvmMongoCollection internal constructor( /** * Converts a [MongoDB collection][com.mongodb.kotlin.client.coroutine.MongoCollection] into a [KtMongo collection][JvmMongoCollection]. */ -@JvmOverloads fun com.mongodb.kotlin.client.coroutine.MongoCollection.asKtMongo( nameStrategy: PropertyNameStrategy = PropertyNameStrategy.Default, + documentType: KType, ): JvmMongoCollection = - JvmMongoCollection(this, nameStrategy) + JvmMongoCollection(this, nameStrategy, documentType) + +/** + * Converts a [MongoDB collection][com.mongodb.kotlin.client.coroutine.MongoCollection] into a [KtMongo collection][JvmMongoCollection]. + */ +inline fun com.mongodb.kotlin.client.coroutine.MongoCollection.asKtMongo( + nameStrategy: PropertyNameStrategy = PropertyNameStrategy.Default, +): JvmMongoCollection = + asKtMongo(nameStrategy, typeOf()) @LowLevelApi private fun com.mongodb.kotlin.client.coroutine.MongoCollection.withWriteConcern(option: WithWriteConcern): com.mongodb.kotlin.client.coroutine.MongoCollection { diff --git a/driver-sync-kmongo/src/jvmMain/kotlin/KMongoExt.kt b/driver-sync-kmongo/src/jvmMain/kotlin/KMongoExt.kt index 7b5faf34..d35e7f86 100644 --- a/driver-sync-kmongo/src/jvmMain/kotlin/KMongoExt.kt +++ b/driver-sync-kmongo/src/jvmMain/kotlin/KMongoExt.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, OpenSavvy and contributors. + * Copyright (c) 2025-2026, 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. @@ -24,5 +24,5 @@ import opensavvy.ktmongo.utils.kmongo.KMongoNameStrategy /** * Converts a collection from the official Java MongoDB driver into a KtMongo collection. */ -fun com.mongodb.client.MongoCollection.asKtMongo(): JvmMongoCollection = +inline fun com.mongodb.client.MongoCollection.asKtMongo(): JvmMongoCollection = MongoCollection(this).asKtMongo(nameStrategy = KMongoNameStrategy()) diff --git a/driver-sync/src/jvmMain/kotlin/JvmMongoCollection.kt b/driver-sync/src/jvmMain/kotlin/JvmMongoCollection.kt index ca75590f..4213fa46 100644 --- a/driver-sync/src/jvmMain/kotlin/JvmMongoCollection.kt +++ b/driver-sync/src/jvmMain/kotlin/JvmMongoCollection.kt @@ -47,6 +47,8 @@ import opensavvy.ktmongo.official.toJava import opensavvy.ktmongo.sync.operations.UpdateOperations.UpdateResult import opensavvy.ktmongo.sync.operations.UpdateOperations.UpsertResult import java.util.concurrent.TimeUnit +import kotlin.reflect.KType +import kotlin.reflect.typeOf /** * Implementation of [MongoCollection] based on [MongoDB's MongoCollection][com.mongodb.kotlin.client.MongoCollection]. @@ -58,6 +60,7 @@ import java.util.concurrent.TimeUnit class JvmMongoCollection internal constructor( inner: com.mongodb.kotlin.client.MongoCollection, nameStrategy: PropertyNameStrategy, + private val documentType: KType, ) : MongoCollection { @LowLevelApi @@ -186,7 +189,7 @@ class JvmMongoCollection internal constructor( filter: FilterQuery.() -> Unit, document: Document, ) { - val model = ReplaceOne(context, document) + val model = ReplaceOne(context, document, documentType) model.options.options() model.filter.filter() @@ -200,7 +203,7 @@ class JvmMongoCollection internal constructor( filter: FilterQuery.() -> Unit, document: Document, ) { - val model = RepsertOne(context, document) + val model = RepsertOne(context, document, documentType) model.options.options() model.filter.filter() @@ -229,7 +232,7 @@ class JvmMongoCollection internal constructor( filter: FilterQuery.() -> Unit, operations: BulkWrite.() -> Unit, ) { - val model = BulkWrite(context, filter) + val model = BulkWrite(context, documentType, filter) model.options.options() model.operations() @@ -296,7 +299,7 @@ class JvmMongoCollection internal constructor( @OptIn(LowLevelApi::class) override fun insertOne(document: Document, options: InsertOneOptions.() -> Unit) { - val model = InsertOne(context, document) + val model = InsertOne(context, document, documentType) model.options.options() @@ -308,7 +311,7 @@ class JvmMongoCollection internal constructor( @OptIn(LowLevelApi::class) override fun insertMany(documents: Iterable, options: InsertManyOptions.() -> Unit) { - val model = InsertMany(context, documents.toList()) + val model = InsertMany(context, documents.toList(), documentType) model.options.options() @@ -392,11 +395,19 @@ class JvmMongoCollection internal constructor( /** * Converts a [MongoDB collection][com.mongodb.kotlin.client.MongoCollection] into a [KtMongo collection][JvmMongoCollection]. */ -@JvmOverloads fun com.mongodb.kotlin.client.MongoCollection.asKtMongo( nameStrategy: PropertyNameStrategy = PropertyNameStrategy.Default, + documentType: KType, ): JvmMongoCollection = - JvmMongoCollection(this, nameStrategy) + JvmMongoCollection(this, nameStrategy, documentType) + +/** + * Converts a [MongoDB collection][com.mongodb.kotlin.client.MongoCollection] into a [KtMongo collection][JvmMongoCollection]. + */ +inline fun com.mongodb.kotlin.client.MongoCollection.asKtMongo( + nameStrategy: PropertyNameStrategy = PropertyNameStrategy.Default, +): JvmMongoCollection = + asKtMongo(nameStrategy, typeOf()) @LowLevelApi private fun com.mongodb.kotlin.client.MongoCollection.withWriteConcern(option: WithWriteConcern): com.mongodb.kotlin.client.MongoCollection { -- 2.51.2 From 59f95f92ab78c10991267a9a281468935bbe7d06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Sat, 2 May 2026 21:22:26 +0200 Subject: [PATCH 08/10] test: Propagate the KType changes --- test/src/commonTest/kotlin/AggregationTests.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/src/commonTest/kotlin/AggregationTests.kt b/test/src/commonTest/kotlin/AggregationTests.kt index 6c9d3513..50a0ca4e 100644 --- a/test/src/commonTest/kotlin/AggregationTests.kt +++ b/test/src/commonTest/kotlin/AggregationTests.kt @@ -32,8 +32,8 @@ import kotlin.time.Duration.Companion.seconds val AggregationTests by preparedSuite(preparedConfig = CoroutineTimeout(30.seconds)) { @Serializable data class Song( - val creationDate: Int, - val editionDate: Int, + val creationDate: Int = 0, // default values are required for dumping the collection + val editionDate: Int = 0, ) val songs by testCollection("aggregation-songs") -- 2.51.2 From 852b7dcdae3a810678a535cc28272dc32387675d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Sat, 2 May 2026 21:22:58 +0200 Subject: [PATCH 09/10] refactor(driver-sync-java): Propagate the KType changes --- driver-sync-java/src/main/kotlin/KtMongo.kt | 19 +++++++++++++++---- .../ktmongo/sync/SimpleJavaTest.java | 2 +- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/driver-sync-java/src/main/kotlin/KtMongo.kt b/driver-sync-java/src/main/kotlin/KtMongo.kt index b928d7fa..0a2ffa69 100644 --- a/driver-sync-java/src/main/kotlin/KtMongo.kt +++ b/driver-sync-java/src/main/kotlin/KtMongo.kt @@ -17,6 +17,7 @@ package opensavvy.ktmongo.sync import com.mongodb.client.MongoCollection +import opensavvy.ktmongo.dsl.path.PropertyNameStrategy import kotlin.reflect.KClass import kotlin.reflect.KClassifier import kotlin.reflect.KType @@ -52,8 +53,13 @@ object KtMongo { * ``` */ @JvmStatic - fun from(driver: MongoCollection): JvmMongoCollection = - from(com.mongodb.kotlin.client.MongoCollection(driver)) + @JvmOverloads + fun from( + driver: MongoCollection, + documentType: KType, + nameStrategy: PropertyNameStrategy = PropertyNameStrategy.Default, + ): JvmMongoCollection = + from(com.mongodb.kotlin.client.MongoCollection(driver), documentType, nameStrategy) /** * Converts a Kotlin MongoDB collection into a KtMongo collection. @@ -74,8 +80,13 @@ object KtMongo { * ``` */ @JvmStatic - fun from(driver: com.mongodb.kotlin.client.MongoCollection): JvmMongoCollection = - driver.asKtMongo() + @JvmOverloads + fun from( + driver: com.mongodb.kotlin.client.MongoCollection, + documentType: KType, + nameStrategy: PropertyNameStrategy = PropertyNameStrategy.Default, + ): JvmMongoCollection = + driver.asKtMongo(nameStrategy, documentType) private val typeOfNull: KType = kotlin.reflect.typeOf() diff --git a/driver-sync-java/src/test/java/opensavvy/ktmongo/sync/SimpleJavaTest.java b/driver-sync-java/src/test/java/opensavvy/ktmongo/sync/SimpleJavaTest.java index 5e606289..e934b60c 100644 --- a/driver-sync-java/src/test/java/opensavvy/ktmongo/sync/SimpleJavaTest.java +++ b/driver-sync-java/src/test/java/opensavvy/ktmongo/sync/SimpleJavaTest.java @@ -47,7 +47,7 @@ public class SimpleJavaTest { var javaCollection = client.getDatabase("java-test") .getCollection("test-basic", Utilisateur.class); - var collection = KtMongo.from(javaCollection); + var collection = KtMongo.from(javaCollection, KtMongo.typeOf(new Utilisateur(null, null, null))); System.out.println("truc " + collection); -- 2.51.2 From 94dfb981dc2c4aaa02f39c841ed1d7b4bb6027e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Sat, 2 May 2026 21:32:40 +0200 Subject: [PATCH 10/10] test(bson-official): Decrease the mandatory coverage threshold --- bson-official/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bson-official/build.gradle.kts b/bson-official/build.gradle.kts index fff15689..0ba9d684 100644 --- a/bson-official/build.gradle.kts +++ b/bson-official/build.gradle.kts @@ -59,5 +59,5 @@ library { url.set("https://www.apache.org/licenses/LICENSE-2.0.txt") } - coverage.set(60) + coverage.set(58) }