Skip to content

Commit

Permalink
Expand bounds for distinct and MongoIterable#map (mongodb#1352)
Browse files Browse the repository at this point in the history
Allow for nullable types in those scenarios.

JAVA-5358
  • Loading branch information
rozza committed Apr 3, 2024
1 parent 81a30b7 commit 53a0f16
Show file tree
Hide file tree
Showing 7 changed files with 200 additions and 9 deletions.
1 change: 1 addition & 0 deletions driver-kotlin-coroutine/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ dependencies {
testImplementation("io.github.classgraph:classgraph:4.8.154")

integrationTestImplementation("org.jetbrains.kotlin:kotlin-test-junit")
integrationTestImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test")
integrationTestImplementation(project(path = ":driver-sync"))
integrationTestImplementation(project(path = ":driver-core"))
integrationTestImplementation(project(path = ":bson"))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Copyright 2008-present MongoDB, Inc.
*
* 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 com.mongodb.kotlin.client.coroutine

import com.mongodb.client.Fixture.getDefaultDatabaseName
import com.mongodb.client.Fixture.getMongoClientSettings
import kotlin.test.assertContentEquals
import kotlin.test.assertEquals
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.flow.toSet
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import org.bson.Document
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test

@OptIn(ExperimentalCoroutinesApi::class)
class SmokeTests {

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

@Test
@DisplayName("distinct and return nulls")
fun testDistinctNullable() = runTest {
collection!!.insertMany(
listOf(
Document.parse("{_id: 1, a: 0}"),
Document.parse("{_id: 2, a: 1}"),
Document.parse("{_id: 3, a: 0}"),
Document.parse("{_id: 4, a: null}")))

// nulls are auto excluded in reactive streams!
val actual = collection!!.distinct<Int>("a").toSet()
assertEquals(setOf(0, 1), actual)
}

@Test
@DisplayName("mapping can return nulls")
fun testMongoIterableMap() = runTest {
collection!!.insertMany(
listOf(
Document.parse("{_id: 1, a: 0}"),
Document.parse("{_id: 2, a: 1}"),
Document.parse("{_id: 3, a: 0}"),
Document.parse("{_id: 4, a: null}")))

val actual = collection!!.find().map { it["a"] as Int? }.toList()
assertContentEquals(listOf(0, 1, 0, null), actual)
}

companion object {

private var mongoClient: MongoClient? = null
private var database: MongoDatabase? = null
private var collection: MongoCollection<Document>? = null

@BeforeAll
@JvmStatic
internal fun beforeAll() {
runBlocking {
mongoClient = MongoClient.create(getMongoClientSettings())
database = mongoClient?.getDatabase(getDefaultDatabaseName())
database?.drop()
collection = database?.getCollection("SmokeTests")
}
}

@AfterAll
@JvmStatic
internal fun afterAll() {
runBlocking {
collection = null
database?.drop()
database = null
mongoClient?.close()
mongoClient = null
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Copyright 2008-present MongoDB, Inc.
*
* 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 com.mongodb.kotlin.client

import com.mongodb.client.Fixture.getDefaultDatabaseName
import com.mongodb.client.Fixture.getMongoClientSettings
import kotlin.test.assertContentEquals
import org.bson.Document
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test

class SmokeTests {

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

@Test
@DisplayName("distinct and return nulls")
fun testDistinctNullable() {
collection!!.insertMany(
listOf(
Document.parse("{_id: 1, a: 0}"),
Document.parse("{_id: 2, a: 1}"),
Document.parse("{_id: 3, a: 0}"),
Document.parse("{_id: 4, a: null}")))

val actual = collection!!.distinct<Int?>("a").toList().toSet()
assertEquals(setOf(null, 0, 1), actual)
}

@Test
@DisplayName("mapping can return nulls")
fun testMongoIterableMap() {
collection!!.insertMany(
listOf(
Document.parse("{_id: 1, a: 0}"),
Document.parse("{_id: 2, a: 1}"),
Document.parse("{_id: 3, a: 0}"),
Document.parse("{_id: 4, a: null}")))

val actual = collection!!.find().map { it["a"] }.toList()
assertContentEquals(listOf(0, 1, 0, null), actual)
}

companion object {

private var mongoClient: MongoClient? = null
private var database: MongoDatabase? = null
private var collection: MongoCollection<Document>? = null

@BeforeAll
@JvmStatic
internal fun beforeAll() {
mongoClient = MongoClient.create(getMongoClientSettings())
database = mongoClient?.getDatabase(getDefaultDatabaseName())
database?.drop()
collection = database?.getCollection("SmokeTests")
}

@AfterAll
@JvmStatic
internal fun afterAll() {
collection = null
database?.drop()
database = null
mongoClient?.close()
mongoClient = null
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import org.bson.conversions.Bson
* @param T The type of the result.
* @see [Distinct command](https://www.mongodb.com/docs/manual/reference/command/distinct/)
*/
public class DistinctIterable<T : Any>(private val wrapped: JDistinctIterable<T>) : MongoIterable<T>(wrapped) {
public class DistinctIterable<T : Any?>(private val wrapped: JDistinctIterable<T>) : MongoIterable<T>(wrapped) {
/**
* Sets the number of documents to return per batch.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ public class MongoCollection<T : Any>(private val wrapped: JMongoCollection<T>)
* @return an iterable of distinct values
* @see [Distinct command](https://www.mongodb.com/docs/manual/reference/command/distinct/)
*/
public fun <R : Any> distinct(
public fun <R : Any?> distinct(
fieldName: String,
filter: Bson = BsonDocument(),
resultClass: Class<R>
Expand All @@ -236,7 +236,7 @@ public class MongoCollection<T : Any>(private val wrapped: JMongoCollection<T>)
* @return an iterable of distinct values
* @see [Distinct command](https://www.mongodb.com/docs/manual/reference/command/distinct/)
*/
public fun <R : Any> distinct(
public fun <R : Any?> distinct(
clientSession: ClientSession,
fieldName: String,
filter: Bson = BsonDocument(),
Expand All @@ -252,7 +252,7 @@ public class MongoCollection<T : Any>(private val wrapped: JMongoCollection<T>)
* @return an iterable of distinct values
* @see [Distinct command](https://www.mongodb.com/docs/manual/reference/command/distinct/)
*/
public inline fun <reified R : Any> distinct(
public inline fun <reified R : Any?> distinct(
fieldName: String,
filter: Bson = BsonDocument()
): DistinctIterable<R> = distinct(fieldName, filter, R::class.java)
Expand All @@ -267,7 +267,7 @@ public class MongoCollection<T : Any>(private val wrapped: JMongoCollection<T>)
* @return an iterable of distinct values
* @see [Distinct command](https://www.mongodb.com/docs/manual/reference/command/distinct/)
*/
public inline fun <reified R : Any> distinct(
public inline fun <reified R : Any?> distinct(
clientSession: ClientSession,
fieldName: String,
filter: Bson = BsonDocument()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import org.bson.BsonDocument
*
* @param T The type of documents the cursor contains
*/
public sealed interface MongoCursor<T : Any> : Iterator<T>, Closeable {
public sealed interface MongoCursor<T : Any?> : Iterator<T>, Closeable {

/**
* Gets the number of results available locally without blocking, which may be 0.
Expand Down Expand Up @@ -90,7 +90,7 @@ public sealed interface MongoChangeStreamCursor<T : Any> : MongoCursor<T> {
public val resumeToken: BsonDocument?
}

internal class MongoCursorImpl<T : Any>(private val wrapped: JMongoCursor<T>) : MongoCursor<T> {
internal class MongoCursorImpl<T : Any?>(private val wrapped: JMongoCursor<T>) : MongoCursor<T> {

override fun hasNext(): Boolean = wrapped.hasNext()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import com.mongodb.client.MongoIterable as JMongoIterable
*
* @param T The type that this iterable will decode documents to.
*/
public open class MongoIterable<T : Any>(private val delegate: JMongoIterable<T>) {
public open class MongoIterable<T : Any?>(private val delegate: JMongoIterable<T>) {

/**
* Returns a cursor used for iterating over elements of type `T. The cursor is primarily used for change streams.
Expand Down Expand Up @@ -71,7 +71,7 @@ public open class MongoIterable<T : Any>(private val delegate: JMongoIterable<T>
* @param transform a function that maps from the source to the target document type
* @return an iterable which maps T to U
*/
public fun <R : Any> map(transform: (T) -> R): MongoIterable<R> = MongoIterable(delegate.map(transform))
public fun <R : Any?> map(transform: (T) -> R): MongoIterable<R> = MongoIterable(delegate.map(transform))

/** Performs the given [action] on each element and safely closes the cursor. */
public fun forEach(action: (T) -> Unit): Unit = use { it.forEach(action) }
Expand Down

0 comments on commit 53a0f16

Please sign in to comment.