Skip to content

DOCSP-36218: builders with dataclass properties #186

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 21 commits into from
Jan 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions examples/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
val kotlin_mongodb_version: String by project

plugins {
kotlin("jvm") version "1.8.0"
id("org.jetbrains.kotlin.jvm") version "1.9.25"
id("com.google.osdetector") version "1.7.3"
application
kotlin("plugin.serialization") version "1.8.21"
id("org.jetbrains.kotlin.plugin.serialization") version "1.9.25"
id("org.jetbrains.kotlin.plugin.scripting") version "1.9.25"
}

group = "org.mongodb.docs.kotlin"
Expand All @@ -26,10 +27,11 @@ dependencies {
implementation("io.netty:netty-tcnative-boringssl-static:2.0.59.Final:${osdetector.classifier}")
implementation("org.xerial.snappy:snappy-java:1.1.10.0")
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.jetbrains.kotlinx:kotlinx-serialization-core:1.6.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0")
implementation("org.mongodb:bson-kotlinx:$kotlin_mongodb_version")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.1")
implementation("org.mongodb:mongodb-driver-kotlin-extensions:$kotlin_mongodb_version")
}

tasks.test {
Expand Down
234 changes: 234 additions & 0 deletions examples/src/test/kotlin/BuildersDataClassTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
import com.mongodb.client.model.Aggregates.group
import com.mongodb.client.model.Aggregates.limit
import com.mongodb.client.model.Aggregates.sort
import com.mongodb.kotlin.client.coroutine.MongoClient
import config.getConfig
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance

import com.mongodb.kotlin.client.model.Filters.eq
import com.mongodb.kotlin.client.model.Filters.all
import com.mongodb.kotlin.client.model.Indexes
import com.mongodb.kotlin.client.model.Projections.excludeId
import com.mongodb.kotlin.client.model.Projections.fields
import com.mongodb.kotlin.client.model.Projections.include
import com.mongodb.client.model.Sorts.orderBy
import com.mongodb.kotlin.client.model.Accumulators.avg
import com.mongodb.kotlin.client.model.Sorts

import com.mongodb.kotlin.client.model.Filters.gte
import com.mongodb.kotlin.client.model.Updates.addToSet
import com.mongodb.kotlin.client.model.Updates.combine
import com.mongodb.kotlin.client.model.Updates.max
import kotlin.test.assertEquals
import kotlin.test.assertTrue

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
internal class BuildersDataClassTest {

companion object {
val config = getConfig()
val client = MongoClient.create(config.connectionUri)
val database = client.getDatabase("school")

@AfterAll
@JvmStatic
fun afterAll() {
runBlocking {
client.close()
}
}
}

@AfterEach
fun afterEach() {
runBlocking {
database.drop()
}
}

// :snippet-start: data-class
data class Student(
val name: String,
val teachers: List<String>,
val gradeAverage: Double
)
// :snippet-end:


@Test
fun filtersTest() = runBlocking {

val collection = database.getCollection<Student>("students")

// :snippet-start: filters-data-class
val student = Student(
"Sandra Nook",
listOf("Alvarez", "Gruber"),
85.7
)

// Equivalent equality queries
Student::name.eq(student.name)
eq(Student::name, student.name)
Student::name eq student.name // Infix notation

// Equivalent array queries
all(Student::teachers, student.teachers)
Student::teachers.all(student.teachers)
Student::teachers all student.teachers // Infix notation
// :snippet-end:

collection.insertOne(student)
val filter = eq(Student::name, student.name)
val result = collection.find(filter).firstOrNull()
Assertions.assertEquals(student, result)
}

@Test
fun indexesTest() = runBlocking {

val collection = database.getCollection<Student>("students")

// :snippet-start: indexes-data-class
val ascendingIdx = Indexes.ascending(Student::name)
val descendingIdx = Indexes.descending(Student::teachers)

val ascIdxName = collection.createIndex(ascendingIdx)
val descIdxName = collection.createIndex(descendingIdx)
// :snippet-end:

assertEquals("name_1", ascIdxName)
}

@Test
fun projectionsTest() = runBlocking {

val collection = database.getCollection<Student>("students")

val student = Student(
"Sandra Nook",
listOf("Alvarez", "Gruber"),
85.7
)
collection.insertOne(student)

// :snippet-start: projections-data-class
val combinedProj = fields(
include(Student::name, Student::gradeAverage),
excludeId()
)

collection.find().projection(combinedProj)
// :snippet-end:

data class Result(val name: String, val gradeAverage: Double)
val result = collection.find<Result>().projection(combinedProj).firstOrNull()

if (result != null) {
assertEquals(85.7, result.gradeAverage)
}
}

@Test
fun sortsTest() = runBlocking {

val collection = database.getCollection<Student>("students")

val student1 = Student(
"Sandra Nook",
listOf("Alvarez", "Gruber"),
85.7
)
val student2 = Student(
"Paolo Sanchez",
listOf("Gruber", "Piselli"),
89.3
)
collection.insertMany(listOf(student1, student2))

// :snippet-start: sorts-data-class
val sort = orderBy(
Sorts.descending(Student::gradeAverage),
Sorts.ascending(Student::name)
)

collection.find().sort(sort)
// :snippet-end:

val result = collection.find().sort(sort).firstOrNull()

if (result != null) {
assertEquals(89.3, result.gradeAverage)
}
}

@Test
fun updatesTest() = runBlocking {

val collection = database.getCollection<Student>("students")

val students = listOf(
Student("Sandra Nook", listOf("Alvarez", "Gruber"),85.7),
Student("Paolo Sanchez", listOf("Gruber", "Piselli"),89.3)
)
collection.insertMany(students)

// :snippet-start: updates-data-class
val filter = Student::gradeAverage gte 85.0
val update = combine(
addToSet(Student::teachers, "Soto"),
Student::gradeAverage.max(90.0)
)
collection.updateMany(filter, update)
// :snippet-end:

val result = collection.find().firstOrNull()

if (result != null) {
assertTrue("Soto" in result.teachers)
assertEquals(result.gradeAverage, 90.0)
}
}

@Test
fun aggregatesTest() = runBlocking {

val collection = database.getCollection<Student>("students")

val students = listOf(
Student("Sandra Nook", listOf("Alvarez", "Gruber"),85.7),
Student("Paolo Sanchez", listOf("Gruber", "Piselli"),89.3),
Student("Katerina Jakobsen", listOf("Alvarez", "Ender"),97.3),
Student("Emma Frank", listOf("Piselli", "Harbour"),93.4),
Student("Qasim Haq", listOf("Gruber", "Harbour"),80.6)
)
collection.insertMany(students)

// :snippet-start: aggregates-data-class
// Data class to store aggregation result
data class Summary ( val average: Double )

val pipeline = listOf(
// Sorts grades from high to low
sort(Sorts.descending(Student::gradeAverage)),
// Selects the top 3 students
limit(3),
// Calculates the average of their grades and stores value in a Summary instance
group(null, avg(Summary::average, "\$${Student::gradeAverage.name}"))
)

val result = collection.aggregate<Summary>(pipeline)
// :snippet-end:

val r = result.firstOrNull()
if (r != null) {
assertEquals(93.33333333333333, r.average)
}
}
}
2 changes: 1 addition & 1 deletion examples/src/test/kotlin/DataClassTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ internal class DataClassTest {
.withDocumentClass<NewDataStorage>()
.findOneAndUpdate(filter, update, options)

println("Updated document: ${result}")
println("Updated document: $result")
// :snippet-end:
}

Expand Down
17 changes: 11 additions & 6 deletions source/api-documentation.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,21 @@ API Documentation
:maxdepth: 1

BSON kotlinx.serialization <{+api+}/apidocs/bson-kotlinx/index.html>
Core <{+api+}/apidocs/mongodb-driver-core/index.html>
Kotlin Coroutine Driver <{+api+}/apidocs/mongodb-driver-kotlin-coroutine/index.html>
Kotlin Sync Driver <{+api+}/apidocs/mongodb-driver-kotlin-sync/index.html>
{+language+} Driver Extensions <{+api+}/apidocs/mongodb-driver-kotlin-extensions/index.html>
Driver Core <{+api+}/apidocs/mongodb-driver-core/index.html>
{+language+} Coroutine Driver <{+api+}/apidocs/mongodb-driver-kotlin-coroutine/index.html>
{+language+} Sync Driver <{+api+}/apidocs/mongodb-driver-kotlin-sync/index.html>

- `BSON kotlinx.serialization <{+api+}/apidocs/bson-kotlinx/index.html>`__ -
classes for encoding and decoding between Kotlin data classes and the BSON data
format using :github:`kotlinx.serialization <Kotlin/kotlinx.serialization>`.
- `Core <{+api+}/apidocs/mongodb-driver-core/index.html>`__ - classes that
- `{+language+} Driver Extensions
<{+api+}/apidocs/mongodb-driver-kotlin-extensions/index.html>`__ -
classes that extend the core builder classes to support :ref:`data
classes <kotlin-builders-data-classes>`.
- `Driver Core <{+api+}/apidocs/mongodb-driver-core/index.html>`__ - classes that
contain essential driver functionality.
- `Kotlin Coroutine Driver <{+api+}/apidocs/mongodb-driver-kotlin-coroutine/index.html>`__ -
- `{+language+} Coroutine Driver <{+api+}/apidocs/mongodb-driver-kotlin-coroutine/index.html>`__ -
classes for the current driver API using coroutines.
- `Kotlin Sync Driver <{+api+}/apidocs/mongodb-driver-kotlin-sync/index.html>`__ -
- `{+language+} Sync Driver <{+api+}/apidocs/mongodb-driver-kotlin-sync/index.html>`__ -
classes for the current synchronous driver API.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Data class to store aggregation result
data class Summary ( val average: Double )

val pipeline = listOf(
// Sorts grades from high to low
sort(Sorts.descending(Student::gradeAverage)),
// Selects the top 3 students
limit(3),
// Calculates the average of their grades and stores value in a Summary instance
group(null, avg(Summary::average, "\$${Student::gradeAverage.name}"))
)

val result = collection.aggregate<Summary>(pipeline)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
data class Student(
val name: String,
val teachers: List<String>,
val gradeAverage: Double
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
val student = Student(
"Sandra Nook",
listOf("Alvarez", "Gruber"),
85.7
)

// Equivalent equality queries
Student::name.eq(student.name)
eq(Student::name, student.name)
Student::name eq student.name // Infix notation

// Equivalent array queries
all(Student::teachers, student.teachers)
Student::teachers.all(student.teachers)
Student::teachers all student.teachers // Infix notation
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
val ascendingIdx = Indexes.ascending(Student::name)
val descendingIdx = Indexes.descending(Student::teachers)

val ascIdxName = collection.createIndex(ascendingIdx)
val descIdxName = collection.createIndex(descendingIdx)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
val combinedProj = fields(
include(Student::name, Student::gradeAverage),
excludeId()
)

collection.find().projection(combinedProj)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
val sort = orderBy(
Sorts.descending(Student::gradeAverage),
Sorts.ascending(Student::name)
)

collection.find().sort(sort)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
val filter = Student::gradeAverage gte 85.0
val update = combine(
addToSet(Student::teachers, "Soto"),
Student::gradeAverage.max(90.0)
)
collection.updateMany(filter, update)
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ val result = collection
.withDocumentClass<NewDataStorage>()
.findOneAndUpdate(filter, update, options)

println("Updated document: ${result}")
println("Updated document: $result")
Loading
Loading