From 71cd2e8acf4d54a7c7329998c99dc56c1d9198bb Mon Sep 17 00:00:00 2001 From: rustagir Date: Fri, 28 Jun 2024 16:32:15 -0400 Subject: [PATCH 1/6] DOCSP-36217: polymorphic serialization --- examples/build.gradle.kts | 2 +- .../test/kotlin/KotlinXSerializationTest.kt | 60 +++++++++++++++++++ examples/src/test/kotlin/TlsTest.kt | 21 ------- ...ionTest.snippet.polymorphic-dataclasses.kt | 22 +++++++ ...izationTest.snippet.polymorphic-example.kt | 19 ++++++ source/fundamentals/connection/tls.txt | 30 ---------- .../data-formats/serialization.txt | 55 +++++++++++++++++ source/whats-new.txt | 5 +- 8 files changed, 159 insertions(+), 55 deletions(-) create mode 100644 source/examples/generated/KotlinXSerializationTest.snippet.polymorphic-dataclasses.kt create mode 100644 source/examples/generated/KotlinXSerializationTest.snippet.polymorphic-example.kt diff --git a/examples/build.gradle.kts b/examples/build.gradle.kts index 424bc68a..7d24c98c 100644 --- a/examples/build.gradle.kts +++ b/examples/build.gradle.kts @@ -28,7 +28,7 @@ dependencies { implementation("com.github.luben:zstd-jni:1.5.5-4") implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.5.1") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") - implementation("org.mongodb:bson-kotlinx:4.10.0-alpha1") + implementation("org.mongodb:bson-kotlinx:5.1.1") implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0") } diff --git a/examples/src/test/kotlin/KotlinXSerializationTest.kt b/examples/src/test/kotlin/KotlinXSerializationTest.kt index bd08de64..1df5b695 100644 --- a/examples/src/test/kotlin/KotlinXSerializationTest.kt +++ b/examples/src/test/kotlin/KotlinXSerializationTest.kt @@ -1,4 +1,5 @@ +import com.mongodb.client.model.Filters import com.mongodb.client.model.Filters.eq import com.mongodb.kotlin.client.coroutine.MongoClient import config.getConfig @@ -35,6 +36,8 @@ import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance import java.util.Date +import kotlin.test.assertIs +import kotlin.test.assertTrue @TestInstance(TestInstance.Lifecycle.PER_CLASS) internal class KotlinXSerializationTest { @@ -178,5 +181,62 @@ internal class KotlinXSerializationTest { collection.drop() } + // :snippet-start: polymorphic-dataclasses + @Serializable + sealed interface Person { + val name: String + } + + @Serializable + data class Student( + @Contextual + @SerialName("_id") + val id: ObjectId, + override val name: String, + val grade: Int, + ) : Person + + @Serializable + data class Teacher( + @Contextual + @SerialName("_id") + val id: ObjectId, + override val name: String, + val department: String, + ) : Person + // :snippet-end: + + @Test + fun polymorphicSerializationTest() = runBlocking { + + // :snippet-start: polymorphic-example + val collection = database.getCollection("school") + + val teacherDoc = Teacher(ObjectId(), "Vivian Lee", "History") + val studentDoc = Student(ObjectId(), "Kate Parker", 10) + + collection.insertOne(teacherDoc) + collection.insertOne(studentDoc) + + println("Retrieving by using data classes") + collection.withDocumentClass().find(Filters.exists("department")).first().also { println(it) } + collection.withDocumentClass().find(Filters.exists("grade")).first().also { println(it) } + + println("\nRetrieving by using Person interface") + val resultsFlow = collection.withDocumentClass().find() + resultsFlow.collect { println(it) } + + println("\nRetrieving as Document type") + val resultsDocFlow = collection.withDocumentClass().find() + resultsDocFlow.collect { println(it) } + // :snippet-end: + + if (resultsFlow != null) { + assertTrue(resultsFlow.firstOrNull() is Teacher) + } + + collection.drop() + } + } diff --git a/examples/src/test/kotlin/TlsTest.kt b/examples/src/test/kotlin/TlsTest.kt index 21ed9667..f4548316 100644 --- a/examples/src/test/kotlin/TlsTest.kt +++ b/examples/src/test/kotlin/TlsTest.kt @@ -1,7 +1,6 @@ import com.mongodb.ConnectionString import com.mongodb.MongoClientSettings -import com.mongodb.connection.netty.NettyStreamFactoryFactory import com.mongodb.kotlin.client.coroutine.MongoClient import config.getConfig import io.netty.handler.ssl.SslContextBuilder @@ -87,25 +86,5 @@ internal class TlsTest { assertEquals(sslContext, settings.sslSettings.context) } - @Test - fun nettyTlsConfigurationTest() { - // :snippet-start: netty-tls-configuration - val sslContext = SslContextBuilder.forClient() - .sslProvider(SslProvider.OPENSSL) - .build() - val settings = MongoClientSettings.builder() - .applyToSslSettings { builder -> builder.enabled(true) } - .streamFactoryFactory( - NettyStreamFactoryFactory.builder() - .sslContext(sslContext) - .build() - ) - .build() - val mongoClient = MongoClient.create(settings) - // :snippet-end: - mongoClient.close() - assertEquals(true, settings.sslSettings.isEnabled) - assertEquals(true, settings.streamFactoryFactory is NettyStreamFactoryFactory) - } } // :replace-end: diff --git a/source/examples/generated/KotlinXSerializationTest.snippet.polymorphic-dataclasses.kt b/source/examples/generated/KotlinXSerializationTest.snippet.polymorphic-dataclasses.kt new file mode 100644 index 00000000..fdbdc1d4 --- /dev/null +++ b/source/examples/generated/KotlinXSerializationTest.snippet.polymorphic-dataclasses.kt @@ -0,0 +1,22 @@ +@Serializable +sealed interface Person { + val name: String +} + +@Serializable +data class Student( + @Contextual + @SerialName("_id") + val id: ObjectId, + override val name: String, + val grade: Int, +) : Person + +@Serializable +data class Teacher( + @Contextual + @SerialName("_id") + val id: ObjectId, + override val name: String, + val department: String, +) : Person diff --git a/source/examples/generated/KotlinXSerializationTest.snippet.polymorphic-example.kt b/source/examples/generated/KotlinXSerializationTest.snippet.polymorphic-example.kt new file mode 100644 index 00000000..3657f7f0 --- /dev/null +++ b/source/examples/generated/KotlinXSerializationTest.snippet.polymorphic-example.kt @@ -0,0 +1,19 @@ +val collection = database.getCollection("school") + +val teacherDoc = Teacher(ObjectId(), "Vivian Lee", "History") +val studentDoc = Student(ObjectId(), "Kate Parker", 10) + +collection.insertOne(teacherDoc) +collection.insertOne(studentDoc) + +println("Retrieving by using data classes") +collection.withDocumentClass().find(Filters.exists("department")).first().also { println(it) } +collection.withDocumentClass().find(Filters.exists("grade")).first().also { println(it) } + +println("\nRetrieving by using Person interface") +val resultsFlow = collection.withDocumentClass().find() +resultsFlow.collect { println(it) } + +println("\nRetrieving as Document type") +val resultsDocFlow = collection.withDocumentClass().find() +resultsDocFlow.collect { println(it) } diff --git a/source/fundamentals/connection/tls.txt b/source/fundamentals/connection/tls.txt index 6de9aae0..ab294bc1 100644 --- a/source/fundamentals/connection/tls.txt +++ b/source/fundamentals/connection/tls.txt @@ -239,36 +239,6 @@ object to the builder in the ``applyToSslSettings()`` lambda: .. literalinclude:: /examples/generated/TlsTest.snippet.custom-tls-configuration.kt :language: kotlin -Customize TLS/SSL Configuration through the Netty SslContext ------------------------------------------------------------- - -If you use the driver with `Netty `__ for network IO, -you have an option to plug an alternative TLS/SSL protocol implementation -provided by Netty. - -.. code-block:: kotlin - :copyable: true - - import com.mongodb.MongoClientSettings - import com.mongodb.client.MongoClient - import com.mongodb.connection.netty.NettyStreamFactoryFactory - import io.netty.handler.ssl.SslContext - import io.netty.handler.ssl.SslContextBuilder - import io.netty.handler.ssl.SslProvider - -.. note:: - - The driver tests with Netty version ``{+nettyVersion+}`` - -To instruct the driver to use `io.netty.handler.ssl.SslContext `__, -use the `NettyStreamFactoryFactory.Builder.sslContext <{+api+}/apidocs/mongodb-driver-core/com/mongodb/connection/netty/NettyStreamFactoryFactory.Builder.html#sslContext(io.netty.handler.ssl.SslContext)>`__ -method. See the method documentation for details about the different `io.netty.handler.ssl.SslProvider `__ -variants the driver supports and the implications of using them. - -.. literalinclude:: /examples/generated/TlsTest.snippet.netty-tls-configuration.kt - :language: kotlin - - Online Certificate Status Protocol (OCSP) ----------------------------------------- diff --git a/source/fundamentals/data-formats/serialization.txt b/source/fundamentals/data-formats/serialization.txt index 5ce30648..9ad245d0 100644 --- a/source/fundamentals/data-formats/serialization.txt +++ b/source/fundamentals/data-formats/serialization.txt @@ -98,6 +98,8 @@ dependencies to your project by using the :guilabel:`Gradle` and {+full-version+} +.. _kotlin-data-class-annotation: + Annotate Data Classes --------------------- @@ -246,3 +248,56 @@ see the following API documentation: - `KotlinSerializerCodec.create() <{+api+}/apidocs/bson-kotlinx/bson-kotlinx/org.bson.codecs.kotlinx/-kotlin-serializer-codec/-companion/create.html>`__ - `BsonConfiguration <{+api+}/apidocs/bson-kotlinx/bson-kotlinx/org.bson.codecs.kotlinx/-bson-configuration/index.html>`__ +.. _kotlin-polymorphic: + +Polymorphic Serialization +------------------------- + +The {+driver-short+} natively supports serialization and deserialization +of polymorphic classes. When you mark a sealed interface and data +classes that inherit that interface with the ``@Serializable`` +annotation, the driver uses a ``KSerializer`` implementation to handle +conversion of your types to and from BSON. + +When you insert an instance of a polymorphic data class into MongoDB, +the driver serializes it with an additional field ``_t``, the +discriminator field. The value of this field is the data class name. + +Polymorphic Data Classes Example +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In this example, you create an interface and two data classes that +inherit that interface. In the data classes, the ``id`` field is marked +with the annotations described in the +:ref:`kotlin-data-class-annotation` section: + +.. literalinclude:: /examples/generated/KotlinXSerializationTest.snippet.polymorphic-dataclasses.kt + :language: kotlin + +Then, you can perform operations with data classes as normal. In the +following example, you parametrize the collection with the ``Person`` +interface, but you can perform operations with the polymorphic classes +``Teacher`` and ``Student``. When you retrieve documents, the driver +automatically detects the type based on the discriminator value and +deserializes them accordingly. + +.. io-code-block:: + :copyable: true + + .. input:: /examples/generated/KotlinXSerializationTest.snippet.polymorphic-example.kt + :language: kotlin + + .. output:: + :language: console + + Retrieving by using data classes + Teacher(id=667f1d0933cea845b9971166, name=Vivian Lee, department=History) + Student(id=667f1d0933cea845b9971167, name=Kate Parker, grade=10) + + Retrieving by using Person interface + Teacher(id=667f1d0933cea845b9971166, name=Vivian Lee, department=History) + Student(id=667f1d0933cea845b9971167, name=Kate Parker, grade=10) + + Retrieving as Document type + Document{{_id=667f1d0933cea845b9971166, _t=Teacher, name=Vivian Lee, department=History}} + Document{{_id=667f1d0933cea845b9971167, _t=Student, name=Kate Parker, grade=10}} diff --git a/source/whats-new.txt b/source/whats-new.txt index c24396be..4c786a41 100644 --- a/source/whats-new.txt +++ b/source/whats-new.txt @@ -82,9 +82,8 @@ Improvements in 5.1 New Features in 5.1 ~~~~~~~~~~~~~~~~~~~ -- Support for polymorphic serialization. - -.. TODO add polymorphic serialization content +- Support for polymorphic serialization. To learn more, see the + :ref:`kotlin-polymorphic` section of the Kotlin Serialization guide. - Introduces the ``serverMonitoringMode`` connection URI option. To learn more, see the :ref:`connection-options` guide. From 116587a7328ae8b90f267bbe7345901ee100500b Mon Sep 17 00:00:00 2001 From: rustagir Date: Fri, 28 Jun 2024 16:34:09 -0400 Subject: [PATCH 2/6] wip --- examples/src/test/kotlin/KotlinXSerializationTest.kt | 9 +++++++-- ...XSerializationTest.snippet.polymorphic-example.kt | 9 +++++++-- source/fundamentals/data-formats/serialization.txt | 12 ++++++------ 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/examples/src/test/kotlin/KotlinXSerializationTest.kt b/examples/src/test/kotlin/KotlinXSerializationTest.kt index 1df5b695..5b1cc1a6 100644 --- a/examples/src/test/kotlin/KotlinXSerializationTest.kt +++ b/examples/src/test/kotlin/KotlinXSerializationTest.kt @@ -219,8 +219,13 @@ internal class KotlinXSerializationTest { collection.insertOne(studentDoc) println("Retrieving by using data classes") - collection.withDocumentClass().find(Filters.exists("department")).first().also { println(it) } - collection.withDocumentClass().find(Filters.exists("grade")).first().also { println(it) } + collection.withDocumentClass() + .find(Filters.exists("department")) + .first().also { println(it) } + + collection.withDocumentClass() + .find(Filters.exists("grade")) + .first().also { println(it) } println("\nRetrieving by using Person interface") val resultsFlow = collection.withDocumentClass().find() diff --git a/source/examples/generated/KotlinXSerializationTest.snippet.polymorphic-example.kt b/source/examples/generated/KotlinXSerializationTest.snippet.polymorphic-example.kt index 3657f7f0..a0418ec5 100644 --- a/source/examples/generated/KotlinXSerializationTest.snippet.polymorphic-example.kt +++ b/source/examples/generated/KotlinXSerializationTest.snippet.polymorphic-example.kt @@ -7,8 +7,13 @@ collection.insertOne(teacherDoc) collection.insertOne(studentDoc) println("Retrieving by using data classes") -collection.withDocumentClass().find(Filters.exists("department")).first().also { println(it) } -collection.withDocumentClass().find(Filters.exists("grade")).first().also { println(it) } +collection.withDocumentClass() + .find(Filters.exists("department")) + .first().also { println(it) } + +collection.withDocumentClass() + .find(Filters.exists("grade")) + .first().also { println(it) } println("\nRetrieving by using Person interface") val resultsFlow = collection.withDocumentClass().find() diff --git a/source/fundamentals/data-formats/serialization.txt b/source/fundamentals/data-formats/serialization.txt index 9ad245d0..c7f9897b 100644 --- a/source/fundamentals/data-formats/serialization.txt +++ b/source/fundamentals/data-formats/serialization.txt @@ -291,13 +291,13 @@ deserializes them accordingly. :language: console Retrieving by using data classes - Teacher(id=667f1d0933cea845b9971166, name=Vivian Lee, department=History) - Student(id=667f1d0933cea845b9971167, name=Kate Parker, grade=10) + Teacher(id=..., name=Vivian Lee, department=History) + Student(id=..., name=Kate Parker, grade=10) Retrieving by using Person interface - Teacher(id=667f1d0933cea845b9971166, name=Vivian Lee, department=History) - Student(id=667f1d0933cea845b9971167, name=Kate Parker, grade=10) + Teacher(id=..., name=Vivian Lee, department=History) + Student(id=..., name=Kate Parker, grade=10) Retrieving as Document type - Document{{_id=667f1d0933cea845b9971166, _t=Teacher, name=Vivian Lee, department=History}} - Document{{_id=667f1d0933cea845b9971167, _t=Student, name=Kate Parker, grade=10}} + Document{{_id=..., _t=Teacher, name=Vivian Lee, department=History}} + Document{{_id=..., _t=Student, name=Kate Parker, grade=10}} From be78bce88863f283de4b7a9fb6e1bf2e04d4edd1 Mon Sep 17 00:00:00 2001 From: rustagir Date: Fri, 28 Jun 2024 16:38:14 -0400 Subject: [PATCH 3/6] wip --- examples/src/test/kotlin/KotlinXSerializationTest.kt | 1 - source/fundamentals/data-formats/serialization.txt | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/src/test/kotlin/KotlinXSerializationTest.kt b/examples/src/test/kotlin/KotlinXSerializationTest.kt index 5b1cc1a6..2eb8e421 100644 --- a/examples/src/test/kotlin/KotlinXSerializationTest.kt +++ b/examples/src/test/kotlin/KotlinXSerializationTest.kt @@ -36,7 +36,6 @@ import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance import java.util.Date -import kotlin.test.assertIs import kotlin.test.assertTrue @TestInstance(TestInstance.Lifecycle.PER_CLASS) diff --git a/source/fundamentals/data-formats/serialization.txt b/source/fundamentals/data-formats/serialization.txt index c7f9897b..bb27d603 100644 --- a/source/fundamentals/data-formats/serialization.txt +++ b/source/fundamentals/data-formats/serialization.txt @@ -260,7 +260,7 @@ annotation, the driver uses a ``KSerializer`` implementation to handle conversion of your types to and from BSON. When you insert an instance of a polymorphic data class into MongoDB, -the driver serializes it with an additional field ``_t``, the +the driver adds the field ``_t``, the discriminator field. The value of this field is the data class name. Polymorphic Data Classes Example @@ -274,7 +274,7 @@ with the annotations described in the .. literalinclude:: /examples/generated/KotlinXSerializationTest.snippet.polymorphic-dataclasses.kt :language: kotlin -Then, you can perform operations with data classes as normal. In the +Then, you can perform operations with data classes as usual. In the following example, you parametrize the collection with the ``Person`` interface, but you can perform operations with the polymorphic classes ``Teacher`` and ``Student``. When you retrieve documents, the driver From f4b861364f76a8050fb55c0ec3bc19845dc6490b Mon Sep 17 00:00:00 2001 From: rustagir Date: Fri, 28 Jun 2024 16:41:22 -0400 Subject: [PATCH 4/6] wip --- source/fundamentals/data-formats/serialization.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/fundamentals/data-formats/serialization.txt b/source/fundamentals/data-formats/serialization.txt index bb27d603..68526fdd 100644 --- a/source/fundamentals/data-formats/serialization.txt +++ b/source/fundamentals/data-formats/serialization.txt @@ -9,7 +9,7 @@ Kotlin Serialization :values: reference .. meta:: - :keywords: code example, data model, conversion + :keywords: code example, data model, conversion, polymorphism .. contents:: On this page :local: From ab3ca9ebf84bcbb6f5d3f6e1886158f9b573979e Mon Sep 17 00:00:00 2001 From: rustagir Date: Fri, 28 Jun 2024 21:32:21 -0400 Subject: [PATCH 5/6] fix error in codectest --- examples/src/test/kotlin/CodecTest.kt | 2 +- source/examples/generated/CodecTest.snippet.example-codec-2.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/src/test/kotlin/CodecTest.kt b/examples/src/test/kotlin/CodecTest.kt index b2009280..5a926b83 100644 --- a/examples/src/test/kotlin/CodecTest.kt +++ b/examples/src/test/kotlin/CodecTest.kt @@ -66,7 +66,7 @@ internal class CodecTest { init { powerStatusCodec = registry[PowerStatus::class.java] - integerCodec = registry.get(Int::class.java) + integerCodec = IntegerCodec() } override fun encode(writer: BsonWriter, value: Monolight, encoderContext: EncoderContext) { diff --git a/source/examples/generated/CodecTest.snippet.example-codec-2.kt b/source/examples/generated/CodecTest.snippet.example-codec-2.kt index f044d0b0..14352f07 100644 --- a/source/examples/generated/CodecTest.snippet.example-codec-2.kt +++ b/source/examples/generated/CodecTest.snippet.example-codec-2.kt @@ -4,7 +4,7 @@ class MonolightCodec(registry: CodecRegistry) : Codec { init { powerStatusCodec = registry[PowerStatus::class.java] - integerCodec = registry.get(Int::class.java) + integerCodec = IntegerCodec() } override fun encode(writer: BsonWriter, value: Monolight, encoderContext: EncoderContext) { From af599604fb7afe763ffba013c1aea7fbdf9d44d3 Mon Sep 17 00:00:00 2001 From: rustagir Date: Mon, 1 Jul 2024 09:53:02 -0400 Subject: [PATCH 6/6] MM PR fixes 1 --- source/fundamentals/data-formats/serialization.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/fundamentals/data-formats/serialization.txt b/source/fundamentals/data-formats/serialization.txt index 68526fdd..d9147c13 100644 --- a/source/fundamentals/data-formats/serialization.txt +++ b/source/fundamentals/data-formats/serialization.txt @@ -266,7 +266,7 @@ discriminator field. The value of this field is the data class name. Polymorphic Data Classes Example ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -In this example, you create an interface and two data classes that +The following example creates an interface and two data classes that inherit that interface. In the data classes, the ``id`` field is marked with the annotations described in the :ref:`kotlin-data-class-annotation` section: @@ -274,9 +274,9 @@ with the annotations described in the .. literalinclude:: /examples/generated/KotlinXSerializationTest.snippet.polymorphic-dataclasses.kt :language: kotlin -Then, you can perform operations with data classes as usual. In the -following example, you parametrize the collection with the ``Person`` -interface, but you can perform operations with the polymorphic classes +Then, you can perform operations with data classes as usual. The +following example parametrizes the collection with the ``Person`` +interface, then performs operations with the polymorphic classes ``Teacher`` and ``Student``. When you retrieve documents, the driver automatically detects the type based on the discriminator value and deserializes them accordingly.