diff --git a/.evergreen/run-kotlin-tests.sh b/.evergreen/run-kotlin-tests.sh index 836a48516b7..6cbc9a3a40d 100755 --- a/.evergreen/run-kotlin-tests.sh +++ b/.evergreen/run-kotlin-tests.sh @@ -34,4 +34,4 @@ fi echo "Running Kotlin tests" ./gradlew -version -./gradlew :driver-kotlin-sync:kCheck -Dorg.mongodb.test.uri=${MONGODB_URI} ${MULTI_MONGOS_URI_SYSTEM_PROPERTY} +./gradlew kCheck -Dorg.mongodb.test.uri=${MONGODB_URI} ${MULTI_MONGOS_URI_SYSTEM_PROPERTY} diff --git a/config/detekt/baseline.xml b/config/detekt/baseline.xml index 2297ba60044..0f6b17d2460 100644 --- a/config/detekt/baseline.xml +++ b/config/detekt/baseline.xml @@ -4,10 +4,13 @@ IteratorNotThrowingNoSuchElementException:MongoCursor.kt$MongoCursor<T : Any> : IteratorCloseable LargeClass:MongoCollectionTest.kt$MongoCollectionTest + LongMethod:FindFlowTest.kt$FindFlowTest$@Suppress("DEPRECATION") @Test fun shouldCallTheUnderlyingMethods() LongMethod:FindIterableTest.kt$FindIterableTest$@Suppress("DEPRECATION") @Test fun shouldCallTheUnderlyingMethods() + MaxLineLength:MapReduceFlow.kt$MapReduceFlow$* MaxLineLength:MapReduceIterable.kt$MapReduceIterable$* SwallowedException:MockitoHelper.kt$MockitoHelper.DeepReflectionEqMatcher$e: Throwable TooManyFunctions:ClientSession.kt$ClientSession : jClientSession + TooManyFunctions:FindFlow.kt$FindFlow<T : Any> : Flow TooManyFunctions:FindIterable.kt$FindIterable<T : Any> : MongoIterable TooManyFunctions:MongoCollection.kt$MongoCollection<T : Any> TooManyFunctions:MongoDatabase.kt$MongoDatabase diff --git a/config/spotbugs/exclude.xml b/config/spotbugs/exclude.xml index 7e17f2529fc..eaabc79836d 100644 --- a/config/spotbugs/exclude.xml +++ b/config/spotbugs/exclude.xml @@ -175,6 +175,11 @@ + + + + + @@ -252,4 +257,20 @@ + + + + + + + + + + + + + + diff --git a/driver-kotlin-coroutine/build.gradle.kts b/driver-kotlin-coroutine/build.gradle.kts new file mode 100644 index 00000000000..7321930ee4e --- /dev/null +++ b/driver-kotlin-coroutine/build.gradle.kts @@ -0,0 +1,189 @@ +/* + * 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. + */ +import io.gitlab.arturbosch.detekt.Detekt +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + id("org.jetbrains.kotlin.jvm") version "1.8.10" + `java-library` + + // Test based plugins + id("com.diffplug.spotless") + id("org.jetbrains.dokka") version "1.7.20" + id("io.gitlab.arturbosch.detekt") version "1.21.0" +} + +repositories { + mavenCentral() + google() +} + +base.archivesName.set("mongodb-driver-kotlin-coroutine") + +description = "The MongoDB Kotlin Coroutine Driver" + +ext.set("pomName", "MongoDB Kotlin Coroutine Driver") + +sourceSets { + create("integrationTest") { + kotlin.srcDir("$projectDir/src/integration/kotlin") + compileClasspath += sourceSets.main.get().output + runtimeClasspath += sourceSets.main.get().output + compileClasspath += project(":driver-sync").sourceSets.test.get().output + runtimeClasspath += project(":driver-sync").sourceSets.test.get().output + compileClasspath += project(":driver-core").sourceSets.test.get().output + runtimeClasspath += project(":driver-core").sourceSets.test.get().output + compileClasspath += project(":bson").sourceSets.test.get().output + runtimeClasspath += project(":bson").sourceSets.test.get().output + } +} + +val integrationTestImplementation: Configuration by + configurations.getting { extendsFrom(configurations.testImplementation.get()) } + +dependencies { + // Align versions of all Kotlin components + implementation(platform("org.jetbrains.kotlin:kotlin-bom")) + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") + + implementation(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.6.4")) + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactive") + + api(project(path = ":bson", configuration = "default")) + api(project(path = ":driver-reactive-streams", configuration = "default")) + + testImplementation("org.jetbrains.kotlin:kotlin-reflect") + testImplementation("org.jetbrains.kotlin:kotlin-test-junit") + testImplementation("org.mockito.kotlin:mockito-kotlin:4.1.0") + testImplementation("org.mockito:mockito-junit-jupiter:4.11.0") + testImplementation("org.assertj:assertj-core:3.24.2") + testImplementation("io.github.classgraph:classgraph:4.8.154") + + integrationTestImplementation("org.jetbrains.kotlin:kotlin-test-junit") + integrationTestImplementation(project(path = ":driver-sync")) + integrationTestImplementation(project(path = ":driver-core")) + integrationTestImplementation(project(path = ":bson")) +} + +kotlin { explicitApi() } + +tasks.withType { kotlinOptions.jvmTarget = "1.8" } + +// =========================== +// Code Quality checks +// =========================== +spotless { + kotlinGradle { + ktfmt("0.39").dropboxStyle().configure { it.setMaxWidth(120) } + trimTrailingWhitespace() + indentWithSpaces() + endWithNewline() + licenseHeaderFile(rootProject.file("config/mongodb.license"), "(group|plugins|import|buildscript|rootProject)") + } + + kotlin { + target("**/*.kt") + ktfmt().dropboxStyle().configure { it.setMaxWidth(120) } + trimTrailingWhitespace() + indentWithSpaces() + endWithNewline() + licenseHeaderFile(rootProject.file("config/mongodb.license")) + } + + format("extraneous") { + target("*.xml", "*.yml", "*.md") + trimTrailingWhitespace() + indentWithSpaces() + endWithNewline() + } +} + +tasks.named("check") { dependsOn("spotlessApply") } + +detekt { + allRules = true // fail build on any finding + buildUponDefaultConfig = true // preconfigure defaults + config = rootProject.files("config/detekt/detekt.yml") // point to your custom config defining rules to run, + // overwriting default behavior + baseline = rootProject.file("config/detekt/baseline.xml") // a way of suppressing issues before introducing detekt + source = + files( + file("src/main/kotlin"), + file("src/test/kotlin"), + file("src/integrationTest/kotlin"), + ) +} + +tasks.withType().configureEach { + reports { + html.required.set(true) // observe findings in your browser with structure and code snippets + xml.required.set(true) // checkstyle like format mainly for integrations like Jenkins + txt.required.set(false) // similar to the console output, contains issue signature to manually edit + } +} + +spotbugs { + showProgress.set(true) + + tasks.getByName("spotbugsIntegrationTest") { enabled = false } +} + +// =========================== +// Test Configuration +// =========================== +val integrationTest = + tasks.create("integrationTest", Test::class) { + description = "Runs the integration tests." + group = "verification" + + testClassesDirs = sourceSets["integrationTest"].output.classesDirs + classpath = sourceSets["integrationTest"].runtimeClasspath + } + +tasks.create("kCheck") { + description = "Runs all the kotlin checks" + group = "verification" + + dependsOn("clean", "check", integrationTest) + tasks.findByName("check")?.mustRunAfter("clean") + tasks.findByName("integrationTest")?.mustRunAfter("check") +} + +tasks.test { useJUnitPlatform() } + +// =========================== +// Dokka Configuration +// =========================== +val dokkaOutputDir = "${rootProject.buildDir}/docs/${base.archivesName.get()}" + +tasks.dokkaHtml.configure { + outputDirectory.set(file(dokkaOutputDir)) + moduleName.set(base.archivesName.get()) +} + +val cleanDokka by tasks.register("cleanDokka") { delete(dokkaOutputDir) } + +project.parent?.tasks?.named("docs") { + dependsOn(tasks.dokkaHtml) + mustRunAfter(cleanDokka) +} + +tasks.javadocJar.configure { + dependsOn(cleanDokka, tasks.dokkaHtml) + archiveClassifier.set("javadoc") + from(dokkaOutputDir) +} diff --git a/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/CrudTest.kt b/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/CrudTest.kt new file mode 100644 index 00000000000..2deaca02b66 --- /dev/null +++ b/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/CrudTest.kt @@ -0,0 +1,49 @@ +/* + * 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.AbstractCrudTest +import com.mongodb.client.Fixture +import com.mongodb.client.MongoDatabase +import com.mongodb.event.CommandListener +import com.mongodb.kotlin.client.coroutine.syncadapter.SyncMongoClient +import org.bson.BsonArray +import org.bson.BsonDocument + +data class CrudTest( + val filename: String, + val description: String, + val databaseName: String, + val collectionName: String, + val data: BsonArray, + val definition: BsonDocument, + val skipTest: Boolean +) : AbstractCrudTest(filename, description, databaseName, collectionName, data, definition, skipTest) { + + private var mongoClient: SyncMongoClient? = null + + override fun createMongoClient(commandListener: CommandListener) { + mongoClient = + SyncMongoClient( + MongoClient.create(Fixture.getMongoClientSettingsBuilder().addCommandListener(commandListener).build())) + } + + override fun getDatabase(databaseName: String): MongoDatabase = mongoClient!!.getDatabase(databaseName) + + override fun cleanUp() { + mongoClient?.close() + } +} diff --git a/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/UnifiedCrudTest.kt b/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/UnifiedCrudTest.kt new file mode 100644 index 00000000000..9896fb280e0 --- /dev/null +++ b/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/UnifiedCrudTest.kt @@ -0,0 +1,52 @@ +/* + * 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 java.io.IOException +import java.net.URISyntaxException +import org.bson.BsonArray +import org.bson.BsonDocument +import org.junit.Assume.assumeFalse +import org.junit.runners.Parameterized + +internal class UnifiedCrudTest( + fileDescription: String?, + testDescription: String, + schemaVersion: String, + runOnRequirements: BsonArray?, + entitiesArray: BsonArray, + initialData: BsonArray, + definition: BsonDocument +) : UnifiedTest(fileDescription, schemaVersion, runOnRequirements, entitiesArray, initialData, definition) { + + init { + assumeFalse(testDescription == "Unacknowledged findOneAndReplace with hint string on 4.4+ server") + assumeFalse(testDescription == "Unacknowledged findOneAndReplace with hint document on 4.4+ server") + assumeFalse(testDescription == "Unacknowledged findOneAndUpdate with hint string on 4.4+ server") + assumeFalse(testDescription == "Unacknowledged findOneAndUpdate with hint document on 4.4+ server") + assumeFalse(testDescription == "Unacknowledged findOneAndDelete with hint string on 4.4+ server") + assumeFalse(testDescription == "Unacknowledged findOneAndDelete with hint document on 4.4+ server") + } + + companion object { + @JvmStatic + @Parameterized.Parameters(name = "{0}: {1}") + @Throws(URISyntaxException::class, IOException::class) + fun data(): Collection?>? { + return getTestData("unified-test-format/crud") + } + } +} diff --git a/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/UnifiedTest.kt b/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/UnifiedTest.kt new file mode 100644 index 00000000000..b8eb32da0f5 --- /dev/null +++ b/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/UnifiedTest.kt @@ -0,0 +1,51 @@ +/* + * 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.ClientEncryptionSettings +import com.mongodb.MongoClientSettings +import com.mongodb.client.MongoClient as JMongoClient +import com.mongodb.client.MongoDatabase as JMongoDatabase +import com.mongodb.client.gridfs.GridFSBucket +import com.mongodb.client.unified.UnifiedTest as JUnifiedTest +import com.mongodb.client.vault.ClientEncryption +import com.mongodb.kotlin.client.coroutine.syncadapter.SyncMongoClient +import org.bson.BsonArray +import org.bson.BsonDocument + +internal abstract class UnifiedTest( + fileDescription: String?, + schemaVersion: String, + runOnRequirements: BsonArray?, + entitiesArray: BsonArray, + initialData: BsonArray, + definition: BsonDocument +) : JUnifiedTest(fileDescription, schemaVersion, runOnRequirements, entitiesArray, initialData, definition) { + + override fun createMongoClient(settings: MongoClientSettings): JMongoClient = + SyncMongoClient(MongoClient.create(settings)) + + override fun createGridFSBucket(database: JMongoDatabase?): GridFSBucket { + TODO("Not yet implemented - JAVA-4893") + } + + override fun createClientEncryption( + keyVaultClient: JMongoClient?, + clientEncryptionSettings: ClientEncryptionSettings? + ): ClientEncryption { + TODO("Not yet implemented - JAVA-4896") + } +} diff --git a/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncAggregateIterable.kt b/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncAggregateIterable.kt new file mode 100644 index 00000000000..e4c3a3eb31a --- /dev/null +++ b/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncAggregateIterable.kt @@ -0,0 +1,73 @@ +/* + * 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.syncadapter + +import com.mongodb.ExplainVerbosity +import com.mongodb.client.AggregateIterable as JAggregateIterable +import com.mongodb.client.model.Collation +import com.mongodb.kotlin.client.coroutine.AggregateFlow +import java.util.concurrent.TimeUnit +import kotlinx.coroutines.runBlocking +import org.bson.BsonValue +import org.bson.Document +import org.bson.conversions.Bson + +data class SyncAggregateIterable(val wrapped: AggregateFlow) : + JAggregateIterable, SyncMongoIterable(wrapped) { + override fun batchSize(batchSize: Int): SyncAggregateIterable = apply { wrapped.batchSize(batchSize) } + + override fun toCollection() = runBlocking { wrapped.toCollection() } + + override fun allowDiskUse(allowDiskUse: Boolean?): SyncAggregateIterable = apply { + wrapped.allowDiskUse(allowDiskUse) + } + + override fun maxTime(maxTime: Long, timeUnit: TimeUnit): SyncAggregateIterable = apply { + wrapped.maxTime(maxTime, timeUnit) + } + + override fun maxAwaitTime(maxAwaitTime: Long, timeUnit: TimeUnit): SyncAggregateIterable = apply { + wrapped.maxAwaitTime(maxAwaitTime, timeUnit) + } + + override fun bypassDocumentValidation(bypassDocumentValidation: Boolean?): SyncAggregateIterable = apply { + wrapped.bypassDocumentValidation(bypassDocumentValidation) + } + + override fun collation(collation: Collation?): SyncAggregateIterable = apply { wrapped.collation(collation) } + + override fun comment(comment: String?): SyncAggregateIterable = apply { wrapped.comment(comment) } + + override fun comment(comment: BsonValue?): SyncAggregateIterable = apply { wrapped.comment(comment) } + + override fun hint(hint: Bson?): SyncAggregateIterable = apply { wrapped.hint(hint) } + + override fun hintString(hint: String?): SyncAggregateIterable = apply { wrapped.hintString(hint) } + + override fun let(variables: Bson?): SyncAggregateIterable = apply { wrapped.let(variables) } + + override fun explain(): Document = runBlocking { wrapped.explain() } + + override fun explain(verbosity: ExplainVerbosity): Document = runBlocking { wrapped.explain(verbosity) } + + override fun explain(explainResultClass: Class): E = runBlocking { + wrapped.explain(explainResultClass) + } + + override fun explain(explainResultClass: Class, verbosity: ExplainVerbosity): E = runBlocking { + wrapped.explain(explainResultClass, verbosity) + } +} diff --git a/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncChangeStreamIterable.kt b/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncChangeStreamIterable.kt new file mode 100644 index 00000000000..3f5269cd10d --- /dev/null +++ b/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncChangeStreamIterable.kt @@ -0,0 +1,63 @@ +/* + * 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.syncadapter + +import com.mongodb.client.ChangeStreamIterable as JChangeStreamIterable +import com.mongodb.client.MongoIterable +import com.mongodb.client.model.Collation +import com.mongodb.client.model.changestream.ChangeStreamDocument +import com.mongodb.client.model.changestream.FullDocument +import com.mongodb.client.model.changestream.FullDocumentBeforeChange +import com.mongodb.kotlin.client.coroutine.ChangeStreamFlow +import java.util.concurrent.TimeUnit +import kotlinx.coroutines.runBlocking +import org.bson.BsonDocument +import org.bson.BsonTimestamp +import org.bson.BsonValue + +data class SyncChangeStreamIterable(val wrapped: ChangeStreamFlow) : + JChangeStreamIterable, SyncMongoIterable>(wrapped) { + override fun withDocumentClass(clazz: Class): MongoIterable = runBlocking { + SyncMongoIterable(wrapped.withDocumentClass(clazz)) + } + + override fun batchSize(batchSize: Int): SyncChangeStreamIterable = apply { wrapped.batchSize(batchSize) } + override fun collation(collation: Collation?): SyncChangeStreamIterable = apply { wrapped.collation(collation) } + override fun comment(comment: BsonValue?): SyncChangeStreamIterable = apply { wrapped.comment(comment) } + override fun comment(comment: String?): SyncChangeStreamIterable = apply { wrapped.comment(comment) } + override fun cursor(): SyncMongoChangeStreamCursor> = SyncMongoChangeStreamCursor(wrapped) + override fun fullDocument(fullDocument: FullDocument): SyncChangeStreamIterable = apply { + wrapped.fullDocument(fullDocument) + } + override fun fullDocumentBeforeChange( + fullDocumentBeforeChange: FullDocumentBeforeChange + ): SyncChangeStreamIterable = apply { wrapped.fullDocumentBeforeChange(fullDocumentBeforeChange) } + override fun maxAwaitTime(maxAwaitTime: Long, timeUnit: TimeUnit): SyncChangeStreamIterable = apply { + wrapped.maxAwaitTime(maxAwaitTime, timeUnit) + } + override fun resumeAfter(resumeToken: BsonDocument): SyncChangeStreamIterable = apply { + wrapped.resumeAfter(resumeToken) + } + override fun showExpandedEvents(showExpandedEvents: Boolean): SyncChangeStreamIterable = apply { + wrapped.showExpandedEvents(showExpandedEvents) + } + override fun startAfter(startAfter: BsonDocument): SyncChangeStreamIterable = apply { + wrapped.startAfter(startAfter) + } + override fun startAtOperationTime(startAtOperationTime: BsonTimestamp): SyncChangeStreamIterable = apply { + wrapped.startAtOperationTime(startAtOperationTime) + } +} diff --git a/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncClientSession.kt b/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncClientSession.kt new file mode 100644 index 00000000000..c29f227d5d6 --- /dev/null +++ b/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncClientSession.kt @@ -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.coroutine.syncadapter + +import com.mongodb.ClientSessionOptions +import com.mongodb.ServerAddress +import com.mongodb.TransactionOptions +import com.mongodb.client.ClientSession as JClientSession +import com.mongodb.client.TransactionBody +import com.mongodb.kotlin.client.coroutine.ClientSession +import com.mongodb.session.ServerSession +import kotlinx.coroutines.runBlocking +import org.bson.BsonDocument +import org.bson.BsonTimestamp + +class SyncClientSession(internal val wrapped: ClientSession, private val originator: Any) : JClientSession { + override fun close(): Unit = wrapped.close() + + override fun getPinnedServerAddress(): ServerAddress? = wrapped.pinnedServerAddress + + override fun getTransactionContext(): Any? = wrapped.transactionContext + + override fun setTransactionContext(address: ServerAddress, transactionContext: Any): Unit = + wrapped.setTransactionContext(address, transactionContext) + + override fun clearTransactionContext(): Unit = wrapped.clearTransactionContext() + + override fun getRecoveryToken(): BsonDocument? = wrapped.recoveryToken + + override fun setRecoveryToken(recoveryToken: BsonDocument): Unit = wrapped.setRecoveryToken(recoveryToken) + + override fun getOptions(): ClientSessionOptions = wrapped.options + + override fun isCausallyConsistent(): Boolean = wrapped.isCausallyConsistent + + override fun getOriginator(): Any = originator + + override fun getServerSession(): ServerSession = wrapped.serverSession + + override fun getOperationTime(): BsonTimestamp = wrapped.operationTime + + override fun advanceOperationTime(operationTime: BsonTimestamp?): Unit = wrapped.advanceOperationTime(operationTime) + + override fun advanceClusterTime(clusterTime: BsonDocument?): Unit = wrapped.advanceClusterTime(clusterTime) + + override fun setSnapshotTimestamp(snapshotTimestamp: BsonTimestamp?) { + wrapped.snapshotTimestamp = snapshotTimestamp + } + + override fun getSnapshotTimestamp(): BsonTimestamp? = wrapped.snapshotTimestamp + + override fun getClusterTime(): BsonDocument = wrapped.clusterTime + + override fun hasActiveTransaction(): Boolean = wrapped.hasActiveTransaction() + + override fun notifyMessageSent(): Boolean = wrapped.notifyMessageSent() + + override fun notifyOperationInitiated(operation: Any): Unit = wrapped.notifyOperationInitiated(operation) + + override fun getTransactionOptions(): TransactionOptions = wrapped.getTransactionOptions() + + override fun startTransaction(): Unit = wrapped.startTransaction() + + override fun startTransaction(transactionOptions: TransactionOptions): Unit = + wrapped.startTransaction(transactionOptions) + + override fun commitTransaction(): Unit = runBlocking { wrapped.commitTransaction() } + + override fun abortTransaction(): Unit = runBlocking { wrapped.abortTransaction() } + + override fun withTransaction(transactionBody: TransactionBody): T = + throw UnsupportedOperationException() + + override fun withTransaction(transactionBody: TransactionBody, options: TransactionOptions): T = + throw UnsupportedOperationException() +} diff --git a/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncDistinctIterable.kt b/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncDistinctIterable.kt new file mode 100644 index 00000000000..4f412c253a0 --- /dev/null +++ b/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncDistinctIterable.kt @@ -0,0 +1,35 @@ +/* + * 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.syncadapter + +import com.mongodb.client.DistinctIterable as JDistinctIterable +import com.mongodb.client.model.Collation +import com.mongodb.kotlin.client.coroutine.DistinctFlow +import java.util.concurrent.TimeUnit +import org.bson.BsonValue +import org.bson.conversions.Bson + +data class SyncDistinctIterable(val wrapped: DistinctFlow) : + JDistinctIterable, SyncMongoIterable(wrapped) { + override fun batchSize(batchSize: Int): SyncDistinctIterable = apply { wrapped.batchSize(batchSize) } + override fun filter(filter: Bson?): SyncDistinctIterable = apply { wrapped.filter(filter) } + override fun maxTime(maxTime: Long, timeUnit: TimeUnit): SyncDistinctIterable = apply { + wrapped.maxTime(maxTime, timeUnit) + } + override fun collation(collation: Collation?): SyncDistinctIterable = apply { wrapped.collation(collation) } + override fun comment(comment: String?): SyncDistinctIterable = apply { wrapped.comment(comment) } + override fun comment(comment: BsonValue?): SyncDistinctIterable = apply { wrapped.comment(comment) } +} diff --git a/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncFindIterable.kt b/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncFindIterable.kt new file mode 100644 index 00000000000..49ba1d49f58 --- /dev/null +++ b/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncFindIterable.kt @@ -0,0 +1,95 @@ +/* + * 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.syncadapter + +import com.mongodb.CursorType +import com.mongodb.ExplainVerbosity +import com.mongodb.client.FindIterable as JFindIterable +import com.mongodb.client.model.Collation +import com.mongodb.kotlin.client.coroutine.FindFlow +import java.util.concurrent.TimeUnit +import kotlinx.coroutines.runBlocking +import org.bson.BsonValue +import org.bson.Document +import org.bson.conversions.Bson + +@Suppress("DEPRECATION") +data class SyncFindIterable(val wrapped: FindFlow) : JFindIterable, SyncMongoIterable(wrapped) { + override fun batchSize(batchSize: Int): SyncFindIterable = apply { wrapped.batchSize(batchSize) } + override fun filter(filter: Bson?): SyncFindIterable = apply { wrapped.filter(filter) } + + override fun limit(limit: Int): SyncFindIterable = apply { wrapped.limit(limit) } + + override fun skip(skip: Int): SyncFindIterable = apply { wrapped.skip(skip) } + + override fun allowDiskUse(allowDiskUse: Boolean?): SyncFindIterable = apply { + wrapped.allowDiskUse(allowDiskUse) + } + + override fun maxTime(maxTime: Long, timeUnit: TimeUnit): SyncFindIterable = apply { + wrapped.maxTime(maxTime, timeUnit) + } + + override fun maxAwaitTime(maxAwaitTime: Long, timeUnit: TimeUnit): SyncFindIterable = apply { + wrapped.maxAwaitTime(maxAwaitTime, timeUnit) + } + + override fun projection(projection: Bson?): SyncFindIterable = apply { wrapped.projection(projection) } + + override fun sort(sort: Bson?): SyncFindIterable = apply { wrapped.sort(sort) } + + override fun noCursorTimeout(noCursorTimeout: Boolean): SyncFindIterable = apply { + wrapped.noCursorTimeout(noCursorTimeout) + } + + @Suppress("OVERRIDE_DEPRECATION") + override fun oplogReplay(oplogReplay: Boolean): SyncFindIterable = apply { wrapped.oplogReplay(oplogReplay) } + + override fun partial(partial: Boolean): SyncFindIterable = apply { wrapped.partial(partial) } + + override fun cursorType(cursorType: CursorType): SyncFindIterable = apply { wrapped.cursorType(cursorType) } + + override fun collation(collation: Collation?): SyncFindIterable = apply { wrapped.collation(collation) } + + override fun comment(comment: String?): SyncFindIterable = apply { wrapped.comment(comment) } + + override fun comment(comment: BsonValue?): SyncFindIterable = apply { wrapped.comment(comment) } + + override fun hint(hint: Bson?): SyncFindIterable = apply { wrapped.hint(hint) } + + override fun hintString(hint: String?): SyncFindIterable = apply { wrapped.hintString(hint) } + + override fun let(variables: Bson?): SyncFindIterable = apply { wrapped.let(variables) } + override fun max(max: Bson?): SyncFindIterable = apply { wrapped.max(max) } + + override fun min(min: Bson?): SyncFindIterable = apply { wrapped.min(min) } + + override fun returnKey(returnKey: Boolean): SyncFindIterable = apply { wrapped.returnKey(returnKey) } + + override fun showRecordId(showRecordId: Boolean): SyncFindIterable = apply { wrapped.showRecordId(showRecordId) } + + override fun explain(): Document = runBlocking { wrapped.explain() } + + override fun explain(verbosity: ExplainVerbosity): Document = runBlocking { wrapped.explain(verbosity) } + + override fun explain(explainResultClass: Class): E = runBlocking { + wrapped.explain(explainResultClass) + } + + override fun explain(explainResultClass: Class, verbosity: ExplainVerbosity): E = runBlocking { + wrapped.explain(explainResultClass, verbosity) + } +} diff --git a/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncListCollectionsIterable.kt b/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncListCollectionsIterable.kt new file mode 100644 index 00000000000..4193e0f04f8 --- /dev/null +++ b/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncListCollectionsIterable.kt @@ -0,0 +1,36 @@ +/* + * 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.syncadapter + +import com.mongodb.client.ListCollectionsIterable as JListCollectionsIterable +import com.mongodb.kotlin.client.coroutine.ListCollectionsFlow +import java.util.concurrent.TimeUnit +import org.bson.BsonValue +import org.bson.conversions.Bson + +data class SyncListCollectionsIterable(val wrapped: ListCollectionsFlow) : + JListCollectionsIterable, SyncMongoIterable(wrapped) { + + override fun batchSize(batchSize: Int): SyncListCollectionsIterable = apply { wrapped.batchSize(batchSize) } + + override fun maxTime(maxTime: Long, timeUnit: TimeUnit): SyncListCollectionsIterable = apply { + wrapped.maxTime(maxTime, timeUnit) + } + + override fun filter(filter: Bson?): SyncListCollectionsIterable = apply { wrapped.filter(filter) } + override fun comment(comment: String?): SyncListCollectionsIterable = apply { wrapped.comment(comment) } + override fun comment(comment: BsonValue?): SyncListCollectionsIterable = apply { wrapped.comment(comment) } +} diff --git a/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncListDatabasesIterable.kt b/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncListDatabasesIterable.kt new file mode 100644 index 00000000000..3acd5581f1b --- /dev/null +++ b/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncListDatabasesIterable.kt @@ -0,0 +1,44 @@ +/* + * 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.syncadapter + +import com.mongodb.client.ListDatabasesIterable as JListDatabasesIterable +import com.mongodb.kotlin.client.coroutine.ListDatabasesFlow +import java.util.concurrent.TimeUnit +import org.bson.BsonValue +import org.bson.conversions.Bson + +data class SyncListDatabasesIterable(val wrapped: ListDatabasesFlow) : + JListDatabasesIterable, SyncMongoIterable(wrapped) { + + override fun batchSize(batchSize: Int): SyncListDatabasesIterable = apply { wrapped.batchSize(batchSize) } + + override fun maxTime(maxTime: Long, timeUnit: TimeUnit): SyncListDatabasesIterable = apply { + wrapped.maxTime(maxTime, timeUnit) + } + + override fun filter(filter: Bson?): SyncListDatabasesIterable = apply { wrapped.filter(filter) } + + override fun nameOnly(nameOnly: Boolean?): SyncListDatabasesIterable = apply { wrapped.nameOnly(nameOnly) } + + override fun authorizedDatabasesOnly(authorizedDatabasesOnly: Boolean?): SyncListDatabasesIterable = apply { + wrapped.authorizedDatabasesOnly(authorizedDatabasesOnly) + } + + override fun comment(comment: String?): SyncListDatabasesIterable = apply { wrapped.comment(comment) } + + override fun comment(comment: BsonValue?): SyncListDatabasesIterable = apply { wrapped.comment(comment) } +} diff --git a/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncListIndexesIterable.kt b/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncListIndexesIterable.kt new file mode 100644 index 00000000000..030b89bb1bf --- /dev/null +++ b/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncListIndexesIterable.kt @@ -0,0 +1,31 @@ +/* + * 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.syncadapter + +import com.mongodb.client.ListIndexesIterable as JListIndexesIterable +import com.mongodb.kotlin.client.coroutine.ListIndexesFlow +import java.util.concurrent.TimeUnit +import org.bson.BsonValue + +data class SyncListIndexesIterable(val wrapped: ListIndexesFlow) : + JListIndexesIterable, SyncMongoIterable(wrapped) { + override fun batchSize(batchSize: Int): SyncListIndexesIterable = apply { wrapped.batchSize(batchSize) } + override fun maxTime(maxTime: Long, timeUnit: TimeUnit): SyncListIndexesIterable = apply { + wrapped.maxTime(maxTime, timeUnit) + } + override fun comment(comment: String?): SyncListIndexesIterable = apply { wrapped.comment(comment) } + override fun comment(comment: BsonValue?): SyncListIndexesIterable = apply { wrapped.comment(comment) } +} diff --git a/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncMapReduceIterable.kt b/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncMapReduceIterable.kt new file mode 100644 index 00000000000..39532a85660 --- /dev/null +++ b/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncMapReduceIterable.kt @@ -0,0 +1,64 @@ +/* + * 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. + */ +@file:Suppress("DEPRECATION") + +package com.mongodb.kotlin.client.coroutine.syncadapter + +import com.mongodb.client.MapReduceIterable as JMapReduceIterable +import com.mongodb.client.model.Collation +import com.mongodb.client.model.MapReduceAction +import com.mongodb.kotlin.client.coroutine.MapReduceFlow +import java.util.concurrent.TimeUnit +import kotlinx.coroutines.runBlocking +import org.bson.conversions.Bson + +data class SyncMapReduceIterable(val wrapped: MapReduceFlow) : + JMapReduceIterable, SyncMongoIterable(wrapped) { + override fun batchSize(batchSize: Int): SyncMapReduceIterable = apply { wrapped.batchSize(batchSize) } + override fun toCollection() = runBlocking { wrapped.toCollection() } + override fun collectionName(collectionName: String): SyncMapReduceIterable = apply { + wrapped.collectionName(collectionName) + } + + override fun finalizeFunction(finalizeFunction: String?): SyncMapReduceIterable = apply { + wrapped.finalizeFunction(finalizeFunction) + } + + override fun scope(scope: Bson?): SyncMapReduceIterable = apply { wrapped.scope(scope) } + override fun sort(sort: Bson?): SyncMapReduceIterable = apply { wrapped.sort(sort) } + override fun filter(filter: Bson?): SyncMapReduceIterable = apply { wrapped.filter(filter) } + override fun limit(limit: Int): SyncMapReduceIterable = apply { wrapped.limit(limit) } + override fun jsMode(jsMode: Boolean): SyncMapReduceIterable = apply { wrapped.jsMode(jsMode) } + override fun verbose(verbose: Boolean): SyncMapReduceIterable = apply { wrapped.verbose(verbose) } + + override fun maxTime(maxTime: Long, timeUnit: TimeUnit): SyncMapReduceIterable = apply { + wrapped.maxTime(maxTime, timeUnit) + } + override fun action(action: MapReduceAction): SyncMapReduceIterable = apply { wrapped.action(action) } + override fun databaseName(databaseName: String?): SyncMapReduceIterable = apply { + wrapped.databaseName(databaseName) + } + @Suppress("OVERRIDE_DEPRECATION") + override fun sharded(sharded: Boolean): SyncMapReduceIterable = apply { wrapped.sharded(sharded) } + @Suppress("OVERRIDE_DEPRECATION") + override fun nonAtomic(nonAtomic: Boolean): SyncMapReduceIterable = apply { wrapped.nonAtomic(nonAtomic) } + + override fun bypassDocumentValidation(bypassDocumentValidation: Boolean?): SyncMapReduceIterable = apply { + wrapped.bypassDocumentValidation(bypassDocumentValidation) + } + + override fun collation(collation: Collation?): SyncMapReduceIterable = apply { wrapped.collation(collation) } +} diff --git a/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncMongoChangeStreamCursor.kt b/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncMongoChangeStreamCursor.kt new file mode 100644 index 00000000000..5a4fb636f47 --- /dev/null +++ b/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncMongoChangeStreamCursor.kt @@ -0,0 +1,25 @@ +/* + * 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.syncadapter + +import com.mongodb.client.MongoChangeStreamCursor as JMongoChangeStreamCursor +import kotlinx.coroutines.flow.Flow +import org.bson.BsonDocument + +data class SyncMongoChangeStreamCursor(val wrapped: Flow) : + JMongoChangeStreamCursor, SyncMongoCursor(wrapped) { + override fun getResumeToken(): BsonDocument? = throw UnsupportedOperationException() +} diff --git a/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncMongoClient.kt b/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncMongoClient.kt new file mode 100644 index 00000000000..9cf01ce186f --- /dev/null +++ b/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncMongoClient.kt @@ -0,0 +1,90 @@ +/* + * 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.syncadapter + +import com.mongodb.ClientSessionOptions +import com.mongodb.client.ChangeStreamIterable +import com.mongodb.client.ClientSession +import com.mongodb.client.ListDatabasesIterable +import com.mongodb.client.MongoClient as JMongoClient +import com.mongodb.client.MongoDatabase +import com.mongodb.client.MongoIterable +import com.mongodb.connection.ClusterDescription +import com.mongodb.kotlin.client.coroutine.MongoClient +import kotlinx.coroutines.runBlocking +import org.bson.Document +import org.bson.conversions.Bson + +data class SyncMongoClient(val wrapped: MongoClient) : JMongoClient { + override fun close(): Unit = wrapped.close() + + override fun getDatabase(databaseName: String): MongoDatabase = SyncMongoDatabase(wrapped.getDatabase(databaseName)) + + override fun startSession(): ClientSession = SyncClientSession(runBlocking { wrapped.startSession() }, this) + + override fun startSession(options: ClientSessionOptions): ClientSession = + SyncClientSession(runBlocking { wrapped.startSession(options) }, this) + + override fun listDatabaseNames(): MongoIterable = SyncMongoIterable(wrapped.listDatabaseNames()) + + override fun listDatabaseNames(clientSession: ClientSession): MongoIterable = + SyncMongoIterable(wrapped.listDatabaseNames(clientSession.unwrapped())) + + override fun listDatabases(): ListDatabasesIterable = SyncListDatabasesIterable(wrapped.listDatabases()) + + override fun listDatabases(clientSession: ClientSession): ListDatabasesIterable = + SyncListDatabasesIterable(wrapped.listDatabases(clientSession.unwrapped())) + + override fun listDatabases(resultClass: Class): ListDatabasesIterable = + SyncListDatabasesIterable(wrapped.listDatabases(resultClass)) + + override fun listDatabases( + clientSession: ClientSession, + resultClass: Class + ): ListDatabasesIterable = + SyncListDatabasesIterable(wrapped.listDatabases(clientSession.unwrapped(), resultClass)) + + override fun watch(): ChangeStreamIterable = SyncChangeStreamIterable(wrapped.watch()) + + override fun watch(resultClass: Class): ChangeStreamIterable = + SyncChangeStreamIterable(wrapped.watch(resultClass = resultClass)) + + override fun watch(pipeline: MutableList): ChangeStreamIterable = + SyncChangeStreamIterable(wrapped.watch(pipeline)) + + override fun watch(pipeline: MutableList, resultClass: Class): ChangeStreamIterable = + SyncChangeStreamIterable(wrapped.watch(pipeline, resultClass)) + + override fun watch(clientSession: ClientSession): ChangeStreamIterable = + SyncChangeStreamIterable(wrapped.watch(clientSession.unwrapped())) + + override fun watch(clientSession: ClientSession, resultClass: Class): ChangeStreamIterable = + SyncChangeStreamIterable(wrapped.watch(clientSession.unwrapped(), resultClass = resultClass)) + + override fun watch(clientSession: ClientSession, pipeline: MutableList): ChangeStreamIterable = + SyncChangeStreamIterable(wrapped.watch(clientSession.unwrapped(), pipeline)) + + override fun watch( + clientSession: ClientSession, + pipeline: MutableList, + resultClass: Class + ): ChangeStreamIterable = + SyncChangeStreamIterable(wrapped.watch(clientSession.unwrapped(), pipeline, resultClass)) + + override fun getClusterDescription(): ClusterDescription = wrapped.getClusterDescription() + + private fun ClientSession.unwrapped() = (this as SyncClientSession).wrapped +} diff --git a/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncMongoCollection.kt b/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncMongoCollection.kt new file mode 100644 index 00000000000..0572bf2e3a6 --- /dev/null +++ b/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncMongoCollection.kt @@ -0,0 +1,605 @@ +/* + * 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. + */ +@file:Suppress("DEPRECATION") + +package com.mongodb.kotlin.client.coroutine.syncadapter + +import com.mongodb.MongoNamespace +import com.mongodb.ReadConcern +import com.mongodb.ReadPreference +import com.mongodb.WriteConcern +import com.mongodb.bulk.BulkWriteResult +import com.mongodb.client.AggregateIterable +import com.mongodb.client.ChangeStreamIterable +import com.mongodb.client.ClientSession +import com.mongodb.client.DistinctIterable +import com.mongodb.client.FindIterable +import com.mongodb.client.ListIndexesIterable +import com.mongodb.client.MapReduceIterable +import com.mongodb.client.MongoCollection as JMongoCollection +import com.mongodb.client.model.BulkWriteOptions +import com.mongodb.client.model.CountOptions +import com.mongodb.client.model.CreateIndexOptions +import com.mongodb.client.model.DeleteOptions +import com.mongodb.client.model.DropCollectionOptions +import com.mongodb.client.model.DropIndexOptions +import com.mongodb.client.model.EstimatedDocumentCountOptions +import com.mongodb.client.model.FindOneAndDeleteOptions +import com.mongodb.client.model.FindOneAndReplaceOptions +import com.mongodb.client.model.FindOneAndUpdateOptions +import com.mongodb.client.model.IndexModel +import com.mongodb.client.model.IndexOptions +import com.mongodb.client.model.InsertManyOptions +import com.mongodb.client.model.InsertOneOptions +import com.mongodb.client.model.RenameCollectionOptions +import com.mongodb.client.model.ReplaceOptions +import com.mongodb.client.model.UpdateOptions +import com.mongodb.client.model.WriteModel +import com.mongodb.client.result.DeleteResult +import com.mongodb.client.result.InsertManyResult +import com.mongodb.client.result.InsertOneResult +import com.mongodb.client.result.UpdateResult +import com.mongodb.kotlin.client.coroutine.MongoCollection +import kotlinx.coroutines.flow.toCollection +import kotlinx.coroutines.runBlocking +import org.bson.Document +import org.bson.codecs.configuration.CodecRegistry +import org.bson.conversions.Bson + +@Suppress("OVERRIDE_DEPRECATION") +data class SyncMongoCollection(val wrapped: MongoCollection) : JMongoCollection { + override fun getNamespace(): MongoNamespace = wrapped.namespace + + override fun getDocumentClass(): Class = wrapped.documentClass + + override fun getCodecRegistry(): CodecRegistry = wrapped.codecRegistry + + override fun getReadPreference(): ReadPreference = wrapped.readPreference + + override fun getWriteConcern(): WriteConcern = wrapped.writeConcern + + override fun getReadConcern(): ReadConcern = wrapped.readConcern + + override fun withDocumentClass(clazz: Class): SyncMongoCollection = + SyncMongoCollection(wrapped.withDocumentClass(clazz)) + + override fun withCodecRegistry(codecRegistry: CodecRegistry): SyncMongoCollection = + SyncMongoCollection(wrapped.withCodecRegistry(codecRegistry)) + + override fun withReadPreference(readPreference: ReadPreference): SyncMongoCollection = + SyncMongoCollection(wrapped.withReadPreference(readPreference)) + + override fun withWriteConcern(writeConcern: WriteConcern): SyncMongoCollection = + SyncMongoCollection(wrapped.withWriteConcern(writeConcern)) + + override fun withReadConcern(readConcern: ReadConcern): SyncMongoCollection = + SyncMongoCollection(wrapped.withReadConcern(readConcern)) + + override fun countDocuments(): Long = runBlocking { wrapped.countDocuments() } + + override fun countDocuments(filter: Bson): Long = runBlocking { wrapped.countDocuments(filter) } + + override fun countDocuments(filter: Bson, options: CountOptions): Long = runBlocking { + wrapped.countDocuments(filter, options) + } + + override fun countDocuments(clientSession: ClientSession): Long = runBlocking { + wrapped.countDocuments(clientSession.unwrapped()) + } + + override fun countDocuments(clientSession: ClientSession, filter: Bson): Long = runBlocking { + wrapped.countDocuments(clientSession.unwrapped(), filter) + } + + override fun countDocuments(clientSession: ClientSession, filter: Bson, options: CountOptions): Long = runBlocking { + wrapped.countDocuments(clientSession.unwrapped(), filter, options) + } + + override fun estimatedDocumentCount(): Long = runBlocking { wrapped.estimatedDocumentCount() } + + override fun estimatedDocumentCount(options: EstimatedDocumentCountOptions): Long = runBlocking { + wrapped.estimatedDocumentCount(options) + } + + override fun distinct(fieldName: String, resultClass: Class): DistinctIterable = + SyncDistinctIterable(wrapped.distinct(fieldName, resultClass = resultClass)) + + override fun distinct(fieldName: String, filter: Bson, resultClass: Class): DistinctIterable = + SyncDistinctIterable(wrapped.distinct(fieldName, filter, resultClass = resultClass)) + + override fun distinct( + clientSession: ClientSession, + fieldName: String, + resultClass: Class + ): DistinctIterable = + SyncDistinctIterable(wrapped.distinct(clientSession.unwrapped(), fieldName, resultClass = resultClass)) + + override fun distinct( + clientSession: ClientSession, + fieldName: String, + filter: Bson, + resultClass: Class + ): DistinctIterable = + SyncDistinctIterable(wrapped.distinct(clientSession.unwrapped(), fieldName, filter, resultClass)) + + override fun find(): FindIterable = SyncFindIterable(wrapped.find()) + + override fun find(resultClass: Class): FindIterable = + SyncFindIterable(wrapped.find(resultClass = resultClass)) + + override fun find(filter: Bson): FindIterable = SyncFindIterable(wrapped.find(filter)) + + override fun find(filter: Bson, resultClass: Class): FindIterable = + SyncFindIterable(wrapped.find(filter, resultClass)) + + override fun find(clientSession: ClientSession): FindIterable = + SyncFindIterable(wrapped.find(clientSession.unwrapped())) + + override fun find(clientSession: ClientSession, resultClass: Class): FindIterable = + SyncFindIterable(wrapped.find(clientSession.unwrapped(), resultClass = resultClass)) + + override fun find(clientSession: ClientSession, filter: Bson): FindIterable = + SyncFindIterable(wrapped.find(clientSession.unwrapped(), filter)) + + override fun find(clientSession: ClientSession, filter: Bson, resultClass: Class): FindIterable = + SyncFindIterable(wrapped.find(clientSession.unwrapped(), filter, resultClass)) + + override fun aggregate(pipeline: MutableList): AggregateIterable = + SyncAggregateIterable(wrapped.aggregate(pipeline)) + + override fun aggregate(pipeline: MutableList, resultClass: Class): AggregateIterable = + SyncAggregateIterable(wrapped.aggregate(pipeline, resultClass)) + + override fun aggregate(clientSession: ClientSession, pipeline: MutableList): AggregateIterable = + SyncAggregateIterable(wrapped.aggregate(clientSession.unwrapped(), pipeline)) + + override fun aggregate( + clientSession: ClientSession, + pipeline: MutableList, + resultClass: Class + ): AggregateIterable = SyncAggregateIterable(wrapped.aggregate(clientSession.unwrapped(), pipeline, resultClass)) + + override fun watch(): ChangeStreamIterable = SyncChangeStreamIterable(wrapped.watch()) + + override fun watch(resultClass: Class): ChangeStreamIterable = + SyncChangeStreamIterable(wrapped.watch(resultClass = resultClass)) + + override fun watch(pipeline: MutableList): ChangeStreamIterable = + SyncChangeStreamIterable(wrapped.watch(pipeline)) + + override fun watch(pipeline: MutableList, resultClass: Class): ChangeStreamIterable = + SyncChangeStreamIterable(wrapped.watch(pipeline, resultClass)) + + override fun watch(clientSession: ClientSession): ChangeStreamIterable = + SyncChangeStreamIterable(wrapped.watch(clientSession.unwrapped())) + + override fun watch(clientSession: ClientSession, resultClass: Class): ChangeStreamIterable = + SyncChangeStreamIterable(wrapped.watch(clientSession.unwrapped(), resultClass = resultClass)) + + override fun watch(clientSession: ClientSession, pipeline: MutableList): ChangeStreamIterable = + SyncChangeStreamIterable(wrapped.watch(clientSession.unwrapped(), pipeline)) + + override fun watch( + clientSession: ClientSession, + pipeline: MutableList, + resultClass: Class + ): ChangeStreamIterable = + SyncChangeStreamIterable(wrapped.watch(clientSession.unwrapped(), pipeline, resultClass)) + + override fun mapReduce(mapFunction: String, reduceFunction: String): MapReduceIterable = + SyncMapReduceIterable(wrapped.mapReduce(mapFunction, reduceFunction)) + + override fun mapReduce( + mapFunction: String, + reduceFunction: String, + resultClass: Class + ): MapReduceIterable = SyncMapReduceIterable(wrapped.mapReduce(mapFunction, reduceFunction, resultClass)) + + override fun mapReduce( + clientSession: ClientSession, + mapFunction: String, + reduceFunction: String + ): MapReduceIterable = + SyncMapReduceIterable(wrapped.mapReduce(clientSession.unwrapped(), mapFunction, reduceFunction)) + + override fun mapReduce( + clientSession: ClientSession, + mapFunction: String, + reduceFunction: String, + resultClass: Class + ): MapReduceIterable = + SyncMapReduceIterable(wrapped.mapReduce(clientSession.unwrapped(), mapFunction, reduceFunction, resultClass)) + + override fun deleteOne(filter: Bson): DeleteResult = runBlocking { wrapped.deleteOne(filter) } + + override fun deleteOne(filter: Bson, options: DeleteOptions): DeleteResult = runBlocking { + wrapped.deleteOne(filter, options) + } + + override fun deleteOne(clientSession: ClientSession, filter: Bson): DeleteResult = runBlocking { + wrapped.deleteOne(clientSession.unwrapped(), filter) + } + + override fun deleteOne(clientSession: ClientSession, filter: Bson, options: DeleteOptions): DeleteResult = + runBlocking { + wrapped.deleteOne(clientSession.unwrapped(), filter, options) + } + + override fun deleteMany(filter: Bson): DeleteResult = runBlocking { wrapped.deleteMany(filter) } + + override fun deleteMany(filter: Bson, options: DeleteOptions): DeleteResult = runBlocking { + wrapped.deleteMany(filter, options) + } + + override fun deleteMany(clientSession: ClientSession, filter: Bson): DeleteResult = runBlocking { + wrapped.deleteMany(clientSession.unwrapped(), filter) + } + + override fun deleteMany(clientSession: ClientSession, filter: Bson, options: DeleteOptions): DeleteResult = + runBlocking { + wrapped.deleteMany(clientSession.unwrapped(), filter, options) + } + + override fun updateOne(filter: Bson, update: Bson): UpdateResult = runBlocking { wrapped.updateOne(filter, update) } + + override fun updateOne(filter: Bson, update: Bson, updateOptions: UpdateOptions): UpdateResult = runBlocking { + wrapped.updateOne(filter, update, updateOptions) + } + + override fun updateOne(clientSession: ClientSession, filter: Bson, update: Bson): UpdateResult = runBlocking { + wrapped.updateOne(clientSession.unwrapped(), filter, update) + } + + override fun updateOne( + clientSession: ClientSession, + filter: Bson, + update: Bson, + updateOptions: UpdateOptions + ): UpdateResult = runBlocking { wrapped.updateOne(clientSession.unwrapped(), filter, update, updateOptions) } + + override fun updateOne(filter: Bson, update: MutableList): UpdateResult = runBlocking { + wrapped.updateOne(filter, update) + } + + override fun updateOne(filter: Bson, update: MutableList, updateOptions: UpdateOptions): UpdateResult = + runBlocking { + wrapped.updateOne(filter, update, updateOptions) + } + + override fun updateOne(clientSession: ClientSession, filter: Bson, update: MutableList): UpdateResult = + runBlocking { + wrapped.updateOne(clientSession.unwrapped(), filter, update) + } + + override fun updateOne( + clientSession: ClientSession, + filter: Bson, + update: MutableList, + updateOptions: UpdateOptions + ): UpdateResult = runBlocking { wrapped.updateOne(clientSession.unwrapped(), filter, update, updateOptions) } + + override fun updateMany(filter: Bson, update: Bson): UpdateResult = runBlocking { + wrapped.updateMany(filter, update) + } + + override fun updateMany(filter: Bson, update: Bson, updateOptions: UpdateOptions): UpdateResult = runBlocking { + wrapped.updateMany(filter, update, updateOptions) + } + + override fun updateMany(clientSession: ClientSession, filter: Bson, update: Bson): UpdateResult = runBlocking { + wrapped.updateMany(clientSession.unwrapped(), filter, update) + } + + override fun updateMany( + clientSession: ClientSession, + filter: Bson, + update: Bson, + updateOptions: UpdateOptions + ): UpdateResult = runBlocking { wrapped.updateMany(clientSession.unwrapped(), filter, update, updateOptions) } + + override fun updateMany(filter: Bson, update: MutableList): UpdateResult = runBlocking { + wrapped.updateMany(filter, update) + } + + override fun updateMany(filter: Bson, update: MutableList, updateOptions: UpdateOptions): UpdateResult = + runBlocking { + wrapped.updateMany(filter, update, updateOptions) + } + + override fun updateMany(clientSession: ClientSession, filter: Bson, update: MutableList): UpdateResult = + runBlocking { + wrapped.updateMany(clientSession.unwrapped(), filter, update) + } + + override fun updateMany( + clientSession: ClientSession, + filter: Bson, + update: MutableList, + updateOptions: UpdateOptions + ): UpdateResult = runBlocking { wrapped.updateMany(clientSession.unwrapped(), filter, update, updateOptions) } + + override fun findOneAndDelete(filter: Bson): T? = runBlocking { wrapped.findOneAndDelete(filter) } + + override fun findOneAndDelete(filter: Bson, options: FindOneAndDeleteOptions): T? = runBlocking { + wrapped.findOneAndDelete(filter, options) + } + + override fun findOneAndDelete(clientSession: ClientSession, filter: Bson): T? = runBlocking { + wrapped.findOneAndDelete(clientSession.unwrapped(), filter) + } + + override fun findOneAndDelete(clientSession: ClientSession, filter: Bson, options: FindOneAndDeleteOptions): T? = + runBlocking { + wrapped.findOneAndDelete(clientSession.unwrapped(), filter, options) + } + + override fun findOneAndUpdate(filter: Bson, update: Bson): T? = runBlocking { + wrapped.findOneAndUpdate(filter, update) + } + + override fun findOneAndUpdate(filter: Bson, update: Bson, options: FindOneAndUpdateOptions): T? = runBlocking { + wrapped.findOneAndUpdate(filter, update, options) + } + + override fun findOneAndUpdate(clientSession: ClientSession, filter: Bson, update: Bson): T? = runBlocking { + wrapped.findOneAndUpdate(clientSession.unwrapped(), filter, update) + } + + override fun findOneAndUpdate( + clientSession: ClientSession, + filter: Bson, + update: Bson, + options: FindOneAndUpdateOptions + ): T? = runBlocking { wrapped.findOneAndUpdate(clientSession.unwrapped(), filter, update, options) } + + override fun findOneAndUpdate(filter: Bson, update: MutableList): T? = runBlocking { + wrapped.findOneAndUpdate(filter, update) + } + + override fun findOneAndUpdate(filter: Bson, update: MutableList, options: FindOneAndUpdateOptions): T? = + runBlocking { + wrapped.findOneAndUpdate(filter, update, options) + } + + override fun findOneAndUpdate(clientSession: ClientSession, filter: Bson, update: MutableList): T? = + runBlocking { + wrapped.findOneAndUpdate(clientSession.unwrapped(), filter, update) + } + + override fun findOneAndUpdate( + clientSession: ClientSession, + filter: Bson, + update: MutableList, + options: FindOneAndUpdateOptions + ): T? = runBlocking { wrapped.findOneAndUpdate(clientSession.unwrapped(), filter, update, options) } + + override fun drop() = runBlocking { wrapped.drop() } + + override fun drop(clientSession: ClientSession) = runBlocking { wrapped.drop(clientSession.unwrapped()) } + + override fun drop(dropCollectionOptions: DropCollectionOptions) = runBlocking { + wrapped.drop(dropCollectionOptions) + } + + override fun drop(clientSession: ClientSession, dropCollectionOptions: DropCollectionOptions) = runBlocking { + wrapped.drop(clientSession.unwrapped(), dropCollectionOptions) + } + + override fun createIndex(keys: Bson): String = runBlocking { wrapped.createIndex(keys) } + + override fun createIndex(keys: Bson, indexOptions: IndexOptions): String = runBlocking { + wrapped.createIndex(keys, indexOptions) + } + + override fun createIndex(clientSession: ClientSession, keys: Bson): String = runBlocking { + wrapped.createIndex(clientSession.unwrapped(), keys) + } + + override fun createIndex(clientSession: ClientSession, keys: Bson, indexOptions: IndexOptions): String = + runBlocking { + wrapped.createIndex(clientSession.unwrapped(), keys, indexOptions) + } + + override fun createIndexes(indexes: MutableList): MutableList = runBlocking { + wrapped.createIndexes(indexes).toCollection(mutableListOf()) + } + + override fun createIndexes( + indexes: MutableList, + createIndexOptions: CreateIndexOptions + ): MutableList = runBlocking { + wrapped.createIndexes(indexes, createIndexOptions).toCollection(mutableListOf()) + } + + override fun createIndexes(clientSession: ClientSession, indexes: MutableList): MutableList = + runBlocking { + wrapped.createIndexes(clientSession.unwrapped(), indexes).toCollection(mutableListOf()) + } + + override fun createIndexes( + clientSession: ClientSession, + indexes: MutableList, + createIndexOptions: CreateIndexOptions + ): MutableList = runBlocking { + wrapped.createIndexes(clientSession.unwrapped(), indexes, createIndexOptions).toCollection(mutableListOf()) + } + + override fun listIndexes(): ListIndexesIterable = SyncListIndexesIterable(wrapped.listIndexes()) + + override fun listIndexes(resultClass: Class): ListIndexesIterable = + SyncListIndexesIterable(wrapped.listIndexes(resultClass = resultClass)) + + override fun listIndexes(clientSession: ClientSession): ListIndexesIterable = + SyncListIndexesIterable(wrapped.listIndexes(clientSession.unwrapped())) + + override fun listIndexes(clientSession: ClientSession, resultClass: Class): ListIndexesIterable = + SyncListIndexesIterable(wrapped.listIndexes(clientSession.unwrapped(), resultClass)) + + override fun dropIndex(indexName: String) = runBlocking { wrapped.dropIndex(indexName) } + + override fun dropIndex(indexName: String, dropIndexOptions: DropIndexOptions) = runBlocking { + wrapped.dropIndex(indexName, dropIndexOptions) + } + + override fun dropIndex(keys: Bson) = runBlocking { wrapped.dropIndex(keys) } + + override fun dropIndex(keys: Bson, dropIndexOptions: DropIndexOptions) = runBlocking { + wrapped.dropIndex(keys, dropIndexOptions) + } + + override fun dropIndex(clientSession: ClientSession, indexName: String) = runBlocking { + wrapped.dropIndex(clientSession.unwrapped(), indexName) + } + + override fun dropIndex(clientSession: ClientSession, keys: Bson) = runBlocking { + wrapped.dropIndex(clientSession.unwrapped(), keys) + } + override fun dropIndex(clientSession: ClientSession, indexName: String, dropIndexOptions: DropIndexOptions) = + runBlocking { + wrapped.dropIndex(clientSession.unwrapped(), indexName, dropIndexOptions) + } + + override fun dropIndex(clientSession: ClientSession, keys: Bson, dropIndexOptions: DropIndexOptions) = runBlocking { + wrapped.dropIndex(clientSession.unwrapped(), keys, dropIndexOptions) + } + + override fun dropIndexes() = runBlocking { wrapped.dropIndexes() } + + override fun dropIndexes(clientSession: ClientSession) = runBlocking { + wrapped.dropIndexes(clientSession.unwrapped()) + } + + override fun dropIndexes(dropIndexOptions: DropIndexOptions) = runBlocking { wrapped.dropIndexes(dropIndexOptions) } + + override fun dropIndexes(clientSession: ClientSession, dropIndexOptions: DropIndexOptions) = runBlocking { + wrapped.dropIndexes(clientSession.unwrapped(), dropIndexOptions) + } + + override fun renameCollection(newCollectionNamespace: MongoNamespace) = runBlocking { + wrapped.renameCollection(newCollectionNamespace) + } + + override fun renameCollection( + newCollectionNamespace: MongoNamespace, + renameCollectionOptions: RenameCollectionOptions + ) = runBlocking { wrapped.renameCollection(newCollectionNamespace, renameCollectionOptions) } + + override fun renameCollection(clientSession: ClientSession, newCollectionNamespace: MongoNamespace) = runBlocking { + wrapped.renameCollection(clientSession.unwrapped(), newCollectionNamespace) + } + + override fun renameCollection( + clientSession: ClientSession, + newCollectionNamespace: MongoNamespace, + renameCollectionOptions: RenameCollectionOptions + ) = runBlocking { + wrapped.renameCollection(clientSession.unwrapped(), newCollectionNamespace, renameCollectionOptions) + } + + override fun findOneAndReplace( + clientSession: ClientSession, + filter: Bson, + replacement: T, + options: FindOneAndReplaceOptions + ): T? = runBlocking { wrapped.findOneAndReplace(clientSession.unwrapped(), filter, replacement, options) } + + override fun findOneAndReplace(clientSession: ClientSession, filter: Bson, replacement: T): T? = runBlocking { + wrapped.findOneAndReplace(clientSession.unwrapped(), filter, replacement) + } + + override fun findOneAndReplace(filter: Bson, replacement: T, options: FindOneAndReplaceOptions): T? = runBlocking { + wrapped.findOneAndReplace(filter, replacement, options) + } + + override fun findOneAndReplace(filter: Bson, replacement: T): T? = runBlocking { + wrapped.findOneAndReplace(filter, replacement) + } + + override fun replaceOne( + clientSession: ClientSession, + filter: Bson, + replacement: T, + replaceOptions: ReplaceOptions + ): UpdateResult = runBlocking { wrapped.replaceOne(clientSession.unwrapped(), filter, replacement, replaceOptions) } + + override fun replaceOne(clientSession: ClientSession, filter: Bson, replacement: T): UpdateResult = runBlocking { + wrapped.replaceOne(clientSession.unwrapped(), filter, replacement) + } + + override fun replaceOne(filter: Bson, replacement: T, replaceOptions: ReplaceOptions): UpdateResult = runBlocking { + wrapped.replaceOne(filter, replacement, replaceOptions) + } + + override fun replaceOne(filter: Bson, replacement: T): UpdateResult = runBlocking { + wrapped.replaceOne(filter, replacement) + } + + override fun insertMany( + clientSession: ClientSession, + documents: MutableList, + options: InsertManyOptions + ): InsertManyResult = runBlocking { wrapped.insertMany(clientSession.unwrapped(), documents, options) } + + override fun insertMany(clientSession: ClientSession, documents: MutableList): InsertManyResult = + runBlocking { + wrapped.insertMany(clientSession.unwrapped(), documents) + } + + override fun insertMany(documents: MutableList, options: InsertManyOptions): InsertManyResult = runBlocking { + wrapped.insertMany(documents, options) + } + + override fun insertMany(documents: MutableList): InsertManyResult = runBlocking { + wrapped.insertMany(documents) + } + + override fun insertOne(clientSession: ClientSession, document: T, options: InsertOneOptions): InsertOneResult = + runBlocking { + wrapped.insertOne(clientSession.unwrapped(), document, options) + } + + override fun insertOne(clientSession: ClientSession, document: T): InsertOneResult = runBlocking { + wrapped.insertOne(clientSession.unwrapped(), document) + } + + override fun insertOne(document: T, options: InsertOneOptions): InsertOneResult = runBlocking { + wrapped.insertOne(document, options) + } + + override fun insertOne(document: T): InsertOneResult = runBlocking { wrapped.insertOne(document) } + + override fun bulkWrite( + clientSession: ClientSession, + requests: MutableList>, + options: BulkWriteOptions + ): BulkWriteResult = runBlocking { wrapped.bulkWrite(clientSession.unwrapped(), requests, options) } + + override fun bulkWrite( + clientSession: ClientSession, + requests: MutableList> + ): BulkWriteResult = runBlocking { wrapped.bulkWrite(clientSession.unwrapped(), requests) } + + override fun bulkWrite(requests: MutableList>, options: BulkWriteOptions): BulkWriteResult = + runBlocking { + wrapped.bulkWrite(requests, options) + } + + override fun bulkWrite(requests: MutableList>): BulkWriteResult = runBlocking { + wrapped.bulkWrite(requests) + } + + private fun ClientSession.unwrapped() = (this as SyncClientSession).wrapped +} diff --git a/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncMongoCursor.kt b/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncMongoCursor.kt new file mode 100644 index 00000000000..fd96b6028c9 --- /dev/null +++ b/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncMongoCursor.kt @@ -0,0 +1,46 @@ +/* + * 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.syncadapter + +import com.mongodb.ServerAddress +import com.mongodb.ServerCursor +import com.mongodb.client.MongoCursor +import java.lang.UnsupportedOperationException +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.runBlocking + +open class SyncMongoCursor(private val delegate: Flow) : MongoCursor { + + val iterator: Iterator by lazy { runBlocking { delegate.toList() }.iterator() } + + override fun remove() { + TODO("Not yet implemented") + } + + override fun hasNext(): Boolean = iterator.hasNext() + @Suppress("UNCHECKED_CAST") override fun next(): T & Any = iterator.next() as (T & Any) + + override fun close() {} + + override fun available(): Int = throw UnsupportedOperationException() + + override fun tryNext(): T? = throw UnsupportedOperationException() + + override fun getServerCursor(): ServerCursor? = throw UnsupportedOperationException() + + override fun getServerAddress(): ServerAddress = throw UnsupportedOperationException() +} diff --git a/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncMongoDatabase.kt b/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncMongoDatabase.kt new file mode 100644 index 00000000000..0fb12bddc70 --- /dev/null +++ b/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncMongoDatabase.kt @@ -0,0 +1,212 @@ +/* + * 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.syncadapter + +import com.mongodb.ReadConcern +import com.mongodb.ReadPreference +import com.mongodb.WriteConcern +import com.mongodb.client.AggregateIterable +import com.mongodb.client.ChangeStreamIterable +import com.mongodb.client.ClientSession +import com.mongodb.client.ListCollectionsIterable +import com.mongodb.client.MongoCollection +import com.mongodb.client.MongoDatabase as JMongoDatabase +import com.mongodb.client.MongoIterable +import com.mongodb.client.model.CreateCollectionOptions +import com.mongodb.client.model.CreateViewOptions +import com.mongodb.kotlin.client.coroutine.MongoDatabase +import kotlinx.coroutines.runBlocking +import org.bson.Document +import org.bson.codecs.configuration.CodecRegistry +import org.bson.conversions.Bson + +data class SyncMongoDatabase(val wrapped: MongoDatabase) : JMongoDatabase { + override fun getName(): String = wrapped.name + + override fun getCodecRegistry(): CodecRegistry = wrapped.codecRegistry + + override fun getReadPreference(): ReadPreference = wrapped.readPreference + + override fun getWriteConcern(): WriteConcern = wrapped.writeConcern + + override fun getReadConcern(): ReadConcern = wrapped.readConcern + + override fun withCodecRegistry(codecRegistry: CodecRegistry): SyncMongoDatabase = + SyncMongoDatabase(wrapped.withCodecRegistry(codecRegistry)) + + override fun withReadPreference(readPreference: ReadPreference): SyncMongoDatabase = + SyncMongoDatabase(wrapped.withReadPreference(readPreference)) + + override fun withWriteConcern(writeConcern: WriteConcern): SyncMongoDatabase = + SyncMongoDatabase(wrapped.withWriteConcern(writeConcern)) + + override fun withReadConcern(readConcern: ReadConcern): SyncMongoDatabase = + SyncMongoDatabase(wrapped.withReadConcern(readConcern)) + + override fun getCollection(collectionName: String): MongoCollection = + SyncMongoCollection(wrapped.getCollection(collectionName, Document::class.java)) + + override fun getCollection(collectionName: String, documentClass: Class): MongoCollection = + SyncMongoCollection(wrapped.getCollection(collectionName, documentClass)) + + override fun runCommand(command: Bson): Document = runBlocking { wrapped.runCommand(command) } + + override fun runCommand(command: Bson, readPreference: ReadPreference): Document = runBlocking { + wrapped.runCommand(command, readPreference) + } + + override fun runCommand(command: Bson, resultClass: Class): T = runBlocking { + wrapped.runCommand(command, resultClass = resultClass) + } + + override fun runCommand(command: Bson, readPreference: ReadPreference, resultClass: Class): T = + runBlocking { + wrapped.runCommand(command, readPreference, resultClass) + } + + override fun runCommand(clientSession: ClientSession, command: Bson): Document = runBlocking { + wrapped.runCommand(clientSession.unwrapped(), command) + } + + override fun runCommand(clientSession: ClientSession, command: Bson, readPreference: ReadPreference): Document = + runBlocking { + wrapped.runCommand(clientSession.unwrapped(), command, readPreference) + } + + override fun runCommand(clientSession: ClientSession, command: Bson, resultClass: Class): T = + runBlocking { + wrapped.runCommand(clientSession.unwrapped(), command, resultClass = resultClass) + } + + override fun runCommand( + clientSession: ClientSession, + command: Bson, + readPreference: ReadPreference, + resultClass: Class + ): T = runBlocking { wrapped.runCommand(clientSession.unwrapped(), command, readPreference, resultClass) } + + override fun drop() = runBlocking { wrapped.drop() } + + override fun drop(clientSession: ClientSession) = runBlocking { wrapped.drop(clientSession.unwrapped()) } + + override fun listCollectionNames(): MongoIterable = SyncMongoIterable(wrapped.listCollectionNames()) + + override fun listCollectionNames(clientSession: ClientSession): MongoIterable = + SyncMongoIterable(wrapped.listCollectionNames(clientSession.unwrapped())) + + override fun listCollections(): ListCollectionsIterable = + SyncListCollectionsIterable(wrapped.listCollections()) + + override fun listCollections(resultClass: Class): ListCollectionsIterable = + SyncListCollectionsIterable(wrapped.listCollections(resultClass)) + + override fun listCollections(clientSession: ClientSession): ListCollectionsIterable = + SyncListCollectionsIterable(wrapped.listCollections(clientSession.unwrapped())) + + override fun listCollections( + clientSession: ClientSession, + resultClass: Class + ): ListCollectionsIterable = + SyncListCollectionsIterable(wrapped.listCollections(clientSession.unwrapped(), resultClass)) + + override fun createCollection(collectionName: String) { + runBlocking { wrapped.createCollection(collectionName) } + } + + override fun createCollection(collectionName: String, createCollectionOptions: CreateCollectionOptions) { + runBlocking { wrapped.createCollection(collectionName, createCollectionOptions) } + } + + override fun createCollection(clientSession: ClientSession, collectionName: String) { + runBlocking { wrapped.createCollection(clientSession.unwrapped(), collectionName) } + } + + override fun createCollection( + clientSession: ClientSession, + collectionName: String, + createCollectionOptions: CreateCollectionOptions + ) = runBlocking { wrapped.createCollection(clientSession.unwrapped(), collectionName, createCollectionOptions) } + + override fun createView(viewName: String, viewOn: String, pipeline: MutableList) = runBlocking { + wrapped.createView(viewName, viewOn, pipeline) + } + + override fun createView( + viewName: String, + viewOn: String, + pipeline: MutableList, + createViewOptions: CreateViewOptions + ) = runBlocking { wrapped.createView(viewName, viewOn, pipeline, createViewOptions) } + + override fun createView( + clientSession: ClientSession, + viewName: String, + viewOn: String, + pipeline: MutableList + ) = runBlocking { wrapped.createView(clientSession.unwrapped(), viewName, viewOn, pipeline) } + + override fun createView( + clientSession: ClientSession, + viewName: String, + viewOn: String, + pipeline: MutableList, + createViewOptions: CreateViewOptions + ) = runBlocking { wrapped.createView(clientSession.unwrapped(), viewName, viewOn, pipeline, createViewOptions) } + + override fun watch(): ChangeStreamIterable = SyncChangeStreamIterable(wrapped.watch()) + + override fun watch(resultClass: Class): ChangeStreamIterable = + SyncChangeStreamIterable(wrapped.watch(resultClass = resultClass)) + + override fun watch(pipeline: MutableList): ChangeStreamIterable = + SyncChangeStreamIterable(wrapped.watch(pipeline)) + + override fun watch(pipeline: MutableList, resultClass: Class): ChangeStreamIterable = + SyncChangeStreamIterable(wrapped.watch(pipeline, resultClass)) + + override fun watch(clientSession: ClientSession): ChangeStreamIterable = + SyncChangeStreamIterable(wrapped.watch(clientSession.unwrapped())) + + override fun watch(clientSession: ClientSession, resultClass: Class): ChangeStreamIterable = + SyncChangeStreamIterable(wrapped.watch(clientSession.unwrapped(), resultClass = resultClass)) + + override fun watch(clientSession: ClientSession, pipeline: MutableList): ChangeStreamIterable = + SyncChangeStreamIterable(wrapped.watch(clientSession.unwrapped(), pipeline)) + + override fun watch( + clientSession: ClientSession, + pipeline: MutableList, + resultClass: Class + ): ChangeStreamIterable = + SyncChangeStreamIterable(wrapped.watch(clientSession.unwrapped(), pipeline, resultClass)) + + override fun aggregate(pipeline: MutableList): AggregateIterable = + SyncAggregateIterable(wrapped.aggregate(pipeline)) + + override fun aggregate(pipeline: MutableList, resultClass: Class): AggregateIterable = + SyncAggregateIterable(wrapped.aggregate(pipeline, resultClass)) + + override fun aggregate(clientSession: ClientSession, pipeline: MutableList): AggregateIterable = + SyncAggregateIterable(wrapped.aggregate(clientSession.unwrapped(), pipeline)) + + override fun aggregate( + clientSession: ClientSession, + pipeline: MutableList, + resultClass: Class + ): AggregateIterable = SyncAggregateIterable(wrapped.aggregate(clientSession.unwrapped(), pipeline, resultClass)) + + private fun ClientSession.unwrapped() = (this as SyncClientSession).wrapped +} diff --git a/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncMongoIterable.kt b/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncMongoIterable.kt new file mode 100644 index 00000000000..e7a22506f0a --- /dev/null +++ b/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncMongoIterable.kt @@ -0,0 +1,49 @@ +/* + * 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.syncadapter + +import com.mongodb.Function +import com.mongodb.client.MongoCursor +import com.mongodb.client.MongoIterable as JMongoIterable +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.runBlocking + +open class SyncMongoIterable(private val delegate: Flow) : JMongoIterable { + private var batchSize: Int? = null + + override fun iterator(): MongoCursor = cursor() + + override fun cursor(): MongoCursor = SyncMongoCursor(delegate) + + override fun first(): T? = runBlocking { delegate.firstOrNull() } + + override fun batchSize(batchSize: Int): SyncMongoIterable = apply { + this@SyncMongoIterable.batchSize = batchSize + } + + @Suppress("UNCHECKED_CAST") + override fun ?> into(target: A): A & Any { + runBlocking { target?.addAll(delegate.toList()) } + return target as (A & Any) + } + + @Suppress("UNCHECKED_CAST") + override fun map(mapper: Function): SyncMongoIterable = + SyncMongoIterable(delegate.map { mapper.apply(it as (T & Any)) as (U & Any) }) +} diff --git a/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/AggregateFlow.kt b/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/AggregateFlow.kt new file mode 100644 index 00000000000..787f7fbd222 --- /dev/null +++ b/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/AggregateFlow.kt @@ -0,0 +1,203 @@ +/* + * 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.ExplainVerbosity +import com.mongodb.client.model.Collation +import com.mongodb.reactivestreams.client.AggregatePublisher +import java.util.concurrent.TimeUnit +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.FlowCollector +import kotlinx.coroutines.reactive.asFlow +import kotlinx.coroutines.reactive.awaitFirstOrNull +import kotlinx.coroutines.reactive.awaitSingle +import org.bson.BsonValue +import org.bson.Document +import org.bson.conversions.Bson + +/** + * Flow implementation for aggregate operations. + * + * @param T The type of the result. + * @see [Aggregation command](https://www.mongodb.com/docs/manual/reference/command/aggregate) + */ +public class AggregateFlow(private val wrapped: AggregatePublisher) : Flow { + + /** + * Sets the number of documents to return per batch. + * + * @param batchSize the batch size + * @return this + * @see [Batch Size](https://www.mongodb.com/docs/manual/reference/method/cursor.batchSize/#cursor.batchSize) + */ + public fun batchSize(batchSize: Int): AggregateFlow = apply { wrapped.batchSize(batchSize) } + + /** + * Aggregates documents according to the specified aggregation pipeline, which must end with a $out or $merge stage. + * + * @throws IllegalStateException if the pipeline does not end with a $out or $merge stage + * @see [$out stage](https://www.mongodb.com/docs/manual/reference/operator/aggregation/out/) + * @see [$merge stage](https://www.mongodb.com/docs/manual/reference/operator/aggregation/merge/) + */ + public suspend fun toCollection() { + wrapped.toCollection().awaitFirstOrNull() + } + + /** + * Enables writing to temporary files. A null value indicates that it's unspecified. + * + * @param allowDiskUse true if writing to temporary files is enabled + * @return this + * @see [Aggregation command](https://www.mongodb.com/docs/manual/reference/command/aggregate/) + */ + public fun allowDiskUse(allowDiskUse: Boolean?): AggregateFlow = apply { wrapped.allowDiskUse(allowDiskUse) } + + /** + * Sets the maximum execution time on the server for this operation. + * + * @param maxTime the max time + * @param timeUnit the time unit, defaults to Milliseconds + * @return this + * @see [Max Time](https://www.mongodb.com/docs/manual/reference/method/cursor.maxTimeMS/#cursor.maxTimeMS) + */ + public fun maxTime(maxTime: Long, timeUnit: TimeUnit = TimeUnit.MILLISECONDS): AggregateFlow = apply { + wrapped.maxTime(maxTime, timeUnit) + } + + /** + * The maximum amount of time for the server to wait on new documents to satisfy a `$changeStream` aggregation. + * + * A zero value will be ignored. + * + * @param maxAwaitTime the max await time + * @param timeUnit the time unit to return the result in, defaults to Milliseconds + * @return the maximum await execution time in the given time unit + */ + public fun maxAwaitTime(maxAwaitTime: Long, timeUnit: TimeUnit = TimeUnit.MILLISECONDS): AggregateFlow = apply { + wrapped.maxAwaitTime(maxAwaitTime, timeUnit) + } + + /** + * Sets the bypass document level validation flag. + * + * Note: This only applies when an $out or $merge stage is specified. + * + * @param bypassDocumentValidation If true, allows the write to opt-out of document level validation. + * @return this + * @see [Aggregation command](https://www.mongodb.com/docs/manual/reference/command/aggregate/) + */ + public fun bypassDocumentValidation(bypassDocumentValidation: Boolean?): AggregateFlow = apply { + wrapped.bypassDocumentValidation(bypassDocumentValidation) + } + + /** + * Sets the collation options + * + * A null value represents the server default. + * + * @param collation the collation options to use + * @return this + */ + public fun collation(collation: Collation?): AggregateFlow = apply { wrapped.collation(collation) } + + /** + * Sets the comment for this operation. A null value means no comment is set. + * + * @param comment the comment + * @return this + */ + public fun comment(comment: String?): AggregateFlow = apply { wrapped.comment(comment) } + + /** + * Sets the comment for this operation. A null value means no comment is set. + * + * The comment can be any valid BSON type for server versions 4.4 and above. Server versions between 3.6 and 4.2 + * only support string as comment, and providing a non-string type will result in a server-side error. + * + * @param comment the comment + * @return this + */ + public fun comment(comment: BsonValue?): AggregateFlow = apply { wrapped.comment(comment) } + + /** + * Sets the hint for which index to use. A null value means no hint is set. + * + * @param hint the hint + * @return this + */ + public fun hint(hint: Bson?): AggregateFlow = apply { wrapped.hint(hint) } + + /** + * Sets the hint to apply. + * + * Note: If [AggregateFlow.hint] is set that will be used instead of any hint string. + * + * @param hint the name of the index which should be used for the operation + * @return this + */ + public fun hintString(hint: String?): AggregateFlow = apply { wrapped.hintString(hint) } + + /** + * Add top-level variables to the aggregation. + * + * For MongoDB 5.0+, the aggregate command accepts a `let` option. This option is a document consisting of zero or + * more fields representing variables that are accessible to the aggregation pipeline. The key is the name of the + * variable and the value is a constant in the aggregate expression language. Each parameter name is then usable to + * access the value of the corresponding expression with the "$$" syntax within aggregate expression contexts which + * may require the use of $expr or a pipeline. + * + * @param variables the variables + * @return this + */ + public fun let(variables: Bson?): AggregateFlow = apply { wrapped.let(variables) } + + /** + * Explain the execution plan for this operation with the given verbosity level + * + * @param R the type of the document class + * @param verbosity the verbosity of the explanation + * @return the execution plan + * @see [Explain command](https://www.mongodb.com/docs/manual/reference/command/explain/) + */ + @JvmName("explainDocument") + public suspend fun explain(verbosity: ExplainVerbosity? = null): Document = explain(verbosity) + + /** + * Explain the execution plan for this operation with the given verbosity level + * + * @param R the type of the document class + * @param resultClass the result document type. + * @param verbosity the verbosity of the explanation + * @return the execution plan + * @see [Explain command](https://www.mongodb.com/docs/manual/reference/command/explain/) + */ + public suspend fun explain(resultClass: Class, verbosity: ExplainVerbosity? = null): R = + if (verbosity == null) wrapped.explain(resultClass).awaitSingle() + else wrapped.explain(resultClass, verbosity).awaitSingle() + + /** + * Explain the execution plan for this operation with the given verbosity level + * + * @param R the type of the document class + * @param verbosity the verbosity of the explanation + * @return the execution plan + * @see [Explain command](https://www.mongodb.com/docs/manual/reference/command/explain/) + */ + public suspend inline fun explain(verbosity: ExplainVerbosity? = null): R = + explain(R::class.java, verbosity) + + public override suspend fun collect(collector: FlowCollector): Unit = wrapped.asFlow().collect(collector) +} diff --git a/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/ChangeStreamFlow.kt b/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/ChangeStreamFlow.kt new file mode 100644 index 00000000000..4a214d6282c --- /dev/null +++ b/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/ChangeStreamFlow.kt @@ -0,0 +1,178 @@ +/* + * 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.model.Collation +import com.mongodb.client.model.changestream.ChangeStreamDocument +import com.mongodb.client.model.changestream.FullDocument +import com.mongodb.client.model.changestream.FullDocumentBeforeChange +import com.mongodb.reactivestreams.client.ChangeStreamPublisher +import java.util.concurrent.TimeUnit +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.FlowCollector +import kotlinx.coroutines.reactive.asFlow +import org.bson.BsonDocument +import org.bson.BsonTimestamp +import org.bson.BsonValue + +/** + * Flow implementation for change streams. + * + * Note: the [ChangeStreamDocument] class will not be applicable for all change stream outputs. If using custom + * pipelines that radically change the result, then the [withDocumentClass] method can be used to provide an alternative + * document format. + * + * @param T The type of the result. + */ +public class ChangeStreamFlow(private val wrapped: ChangeStreamPublisher) : Flow> { + + /** + * Sets the fullDocument value. + * + * @param fullDocument the fullDocument + * @return this + */ + public fun fullDocument(fullDocument: FullDocument): ChangeStreamFlow = apply { + wrapped.fullDocument(fullDocument) + } + + /** + * Sets the fullDocumentBeforeChange value. + * + * @param fullDocumentBeforeChange the fullDocumentBeforeChange + * @return this + */ + public fun fullDocumentBeforeChange(fullDocumentBeforeChange: FullDocumentBeforeChange): ChangeStreamFlow = + apply { + wrapped.fullDocumentBeforeChange(fullDocumentBeforeChange) + } + + /** + * Sets the logical starting point for the new change stream. + * + * @param resumeToken the resume token + * @return this + */ + public fun resumeAfter(resumeToken: BsonDocument): ChangeStreamFlow = apply { wrapped.resumeAfter(resumeToken) } + + /** + * Sets the number of documents to return per batch. + * + * @param batchSize the batch size + * @return this + * @see [Batch Size](https://www.mongodb.com/docs/manual/reference/method/cursor.batchSize/#cursor.batchSize) + */ + public fun batchSize(batchSize: Int): ChangeStreamFlow = apply { wrapped.batchSize(batchSize) } + + /** + * Sets the maximum await execution time on the server for this operation. + * + * @param maxAwaitTime the max await time. A zero value will be ignored, and indicates that the driver should + * respect the server's default value + * @param timeUnit the time unit, which defaults to MILLISECONDS + * @return this + */ + public fun maxAwaitTime(maxAwaitTime: Long, timeUnit: TimeUnit = TimeUnit.MILLISECONDS): ChangeStreamFlow = + apply { + wrapped.maxAwaitTime(maxAwaitTime, timeUnit) + } + + /** + * Sets the collation options + * + * A null value represents the server default. + * + * @param collation the collation options to use + * @return this + */ + public fun collation(collation: Collation?): ChangeStreamFlow = apply { wrapped.collation(collation) } + + /** + * Returns a `MongoIterable` containing the results of the change stream based on the document class provided. + * + * @param R the Mongo Iterable type + * @param resultClass the target document type of the iterable. + * @return the new Mongo Iterable + */ + public fun withDocumentClass(resultClass: Class): Flow = + wrapped.withDocumentClass(resultClass).asFlow() + + /** + * Returns a `MongoIterable` containing the results of the change stream based on the document class provided. + * + * @param R the Mongo Iterable type + * @return the new Mongo Iterable + */ + public inline fun withDocumentClass(): Flow = withDocumentClass(R::class.java) + + /** + * The change stream will only provide changes that occurred at or after the specified timestamp. + * + * Any command run against the server will return an operation time that can be used here. + * + * The default value is an operation time obtained from the server before the change stream was created. + * + * @param startAtOperationTime the start at operation time + * @return this + */ + public fun startAtOperationTime(startAtOperationTime: BsonTimestamp): ChangeStreamFlow = apply { + wrapped.startAtOperationTime(startAtOperationTime) + } + + /** + * Similar to `resumeAfter`, this option takes a resume token and starts a new change stream returning the first + * notification after the token. + * + * This will allow users to watch collections that have been dropped and recreated or newly renamed collections + * without missing any notifications. + * + * Note: The server will report an error if both `startAfter` and `resumeAfter` are specified. + * + * @param startAfter the startAfter resumeToken + * @return this + * @see [Start After](https://www.mongodb.com/docs/manual/changeStreams/#change-stream-start-after) + */ + public fun startAfter(startAfter: BsonDocument): ChangeStreamFlow = apply { wrapped.startAfter(startAfter) } + + /** + * Sets the comment for this operation. A null value means no comment is set. + * + * @param comment the comment + */ + public fun comment(comment: String?): ChangeStreamFlow = apply { wrapped.comment(comment) } + /** + * Sets the comment for this operation. A null value means no comment is set. + * + * The comment can be any valid BSON type for server versions 4.4 and above. Server versions between 3.6 and 4.2 + * only support string as comment, and providing a non-string type will result in a server-side error. + * + * @param comment the comment + */ + public fun comment(comment: BsonValue?): ChangeStreamFlow = apply { wrapped.comment(comment) } + + /** + * Sets whether to include expanded change stream events, which are: createIndexes, dropIndexes, modify, create, + * shardCollection, reshardCollection, refineCollectionShardKey. False by default. + * + * @param showExpandedEvents true to include expanded events + * @return this + */ + public fun showExpandedEvents(showExpandedEvents: Boolean): ChangeStreamFlow = apply { + wrapped.showExpandedEvents(showExpandedEvents) + } + public override suspend fun collect(collector: FlowCollector>): Unit = + wrapped.asFlow().collect(collector) +} diff --git a/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/ClientSession.kt b/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/ClientSession.kt new file mode 100644 index 00000000000..6809b0b2777 --- /dev/null +++ b/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/ClientSession.kt @@ -0,0 +1,226 @@ +/* + * 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.ClientSessionOptions +import com.mongodb.ServerAddress +import com.mongodb.TransactionOptions +import com.mongodb.reactivestreams.client.ClientSession as reactiveClientSession +import com.mongodb.session.ClientSession as jClientSession +import com.mongodb.session.ServerSession +import java.util.concurrent.TimeUnit +import kotlinx.coroutines.reactive.awaitFirstOrNull +import org.bson.BsonDocument +import org.bson.BsonTimestamp + +/** A client session that supports transactions. */ +public class ClientSession(public val wrapped: reactiveClientSession) : jClientSession { + + public override fun close(): Unit = wrapped.close() + + /** + * Returns true if there is an active transaction on this session, and false otherwise + * + * @return true if there is an active transaction on this session + */ + public fun hasActiveTransaction(): Boolean = wrapped.hasActiveTransaction() + + /** + * Notify the client session that a message has been sent. + * + * For internal use only + * + * @return true if this is the first message sent, false otherwise + */ + public fun notifyMessageSent(): Boolean = wrapped.notifyMessageSent() + + /** + * Notify the client session that command execution is being initiated. This should be called before server + * selection occurs. + * + * For internal use only + * + * @param operation the operation + */ + public fun notifyOperationInitiated(operation: Any): Unit = wrapped.notifyOperationInitiated(operation) + + /** + * Get the server address of the pinned mongos on this session. For internal use only. + * + * @return the server address of the pinned mongos + */ + public override fun getPinnedServerAddress(): ServerAddress? = wrapped.pinnedServerAddress + + /** + * Gets the transaction context. + * + * For internal use only + * + * @return the transaction context + */ + public override fun getTransactionContext(): Any? = wrapped.transactionContext + + /** + * Sets the transaction context. + * + * For internal use only + * + * Implementations may place additional restrictions on the type of the transaction context + * + * @param address the server address + * @param transactionContext the transaction context + */ + public override fun setTransactionContext(address: ServerAddress, transactionContext: Any): Unit = + wrapped.setTransactionContext(address, transactionContext) + + /** + * Clears the transaction context. + * + * For internal use only + */ + public override fun clearTransactionContext(): Unit = wrapped.clearTransactionContext() + + /** + * Get the recovery token from the latest outcome in a sharded transaction. For internal use only. + * + * @return the recovery token @mongodb.server.release 4.2 + * @since 3.11 + */ + public override fun getRecoveryToken(): BsonDocument? = wrapped.recoveryToken + + /** + * Set the recovery token. For internal use only. + * + * @param recoveryToken the recovery token + */ + public override fun setRecoveryToken(recoveryToken: BsonDocument) { + wrapped.recoveryToken = recoveryToken + } + + /** + * Get the options for this session. + * + * @return the options, which may not be null + */ + public override fun getOptions(): ClientSessionOptions = wrapped.options + + /** + * Returns true if operations in this session must be causally consistent + * + * @return whether operations in this session must be causally consistent. + */ + public override fun isCausallyConsistent(): Boolean = wrapped.isCausallyConsistent + + /** + * Gets the originator for the session. + * + * Important because sessions must only be used by their own originator. + * + * @return the sessions originator + */ + public override fun getOriginator(): Any = wrapped.originator + + /** @return the server session */ + public override fun getServerSession(): ServerSession = wrapped.serverSession + + /** + * Gets the operation time of the last operation executed in this session. + * + * @return the operation time + */ + public override fun getOperationTime(): BsonTimestamp = wrapped.operationTime + + /** + * Set the operation time of the last operation executed in this session. + * + * @param operationTime the operation time + */ + public override fun advanceOperationTime(operationTime: BsonTimestamp?): Unit = + wrapped.advanceOperationTime(operationTime) + + /** @param clusterTime the cluster time to advance to */ + public override fun advanceClusterTime(clusterTime: BsonDocument?): Unit = wrapped.advanceClusterTime(clusterTime) + + /** + * For internal use only. + * + * @param snapshotTimestamp the snapshot timestamp + */ + public override fun setSnapshotTimestamp(snapshotTimestamp: BsonTimestamp?) { + wrapped.snapshotTimestamp = snapshotTimestamp + } + + /** + * For internal use only. + * + * @return the snapshot timestamp + */ + public override fun getSnapshotTimestamp(): BsonTimestamp? = wrapped.snapshotTimestamp + + /** @return the latest cluster time seen by this session */ + public override fun getClusterTime(): BsonDocument = wrapped.clusterTime + + /** + * Gets the transaction options. Only call this method of the session has an active transaction + * + * @return the transaction options + */ + public fun getTransactionOptions(): TransactionOptions = wrapped.transactionOptions + + /** + * Start a transaction in the context of this session with default transaction options. A transaction can not be + * started if there is already an active transaction on this session. + */ + public fun startTransaction(): Unit = wrapped.startTransaction() + + /** + * Start a transaction in the context of this session with the given transaction options. A transaction can not be + * started if there is already an active transaction on this session. + * + * @param transactionOptions the options to apply to the transaction + */ + public fun startTransaction(transactionOptions: TransactionOptions): Unit = + wrapped.startTransaction(transactionOptions) + + /** + * Commit a transaction in the context of this session. A transaction can only be commmited if one has first been + * started. + * + * @return an empty publisher that indicates when the operation has completed + */ + public suspend fun commitTransaction() { + wrapped.commitTransaction().awaitFirstOrNull() + } + + /** + * Abort a transaction in the context of this session. A transaction can only be aborted if one has first been + * started. + * + * @return an empty publisher that indicates when the operation has completed + */ + public suspend fun abortTransaction() { + wrapped.abortTransaction().awaitFirstOrNull() + } +} + +/** + * maxCommitTime extension function + * + * @param maxCommitTime time in milliseconds + * @return the options + */ +public fun TransactionOptions.Builder.maxCommitTime(maxCommitTime: Long): TransactionOptions.Builder = + this.apply { maxCommitTime(maxCommitTime, TimeUnit.MILLISECONDS) } diff --git a/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/DistinctFlow.kt b/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/DistinctFlow.kt new file mode 100644 index 00000000000..edca50a58b0 --- /dev/null +++ b/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/DistinctFlow.kt @@ -0,0 +1,91 @@ +/* + * 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.model.Collation +import com.mongodb.reactivestreams.client.DistinctPublisher +import java.util.concurrent.TimeUnit +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.FlowCollector +import kotlinx.coroutines.reactive.asFlow +import org.bson.BsonValue +import org.bson.conversions.Bson + +/** + * Flow implementation for distinct operations. + * + * @param T The type of the result. + * @see [Distinct command](https://www.mongodb.com/docs/manual/reference/command/distinct/) + */ +public class DistinctFlow(private val wrapped: DistinctPublisher) : Flow { + + /** + * Sets the number of documents to return per batch. + * + * @param batchSize the batch size + * @return this + * @see [Batch Size](https://www.mongodb.com/docs/manual/reference/method/cursor.batchSize/#cursor.batchSize) + */ + public fun batchSize(batchSize: Int): DistinctFlow = apply { wrapped.batchSize(batchSize) } + + /** + * Sets the query filter to apply to the query. + * + * @param filter the filter, which may be null. + * @return this + * @see [Filter results](https://www.mongodb.com/docs/manual/reference/method/db.collection.find/) + */ + public fun filter(filter: Bson?): DistinctFlow = apply { wrapped.filter(filter) } + + /** + * Sets the maximum execution time on the server for this operation. + * + * @param maxTime the max time + * @param timeUnit the time unit, which defaults to Milliseconds + * @return this + */ + public fun maxTime(maxTime: Long, timeUnit: TimeUnit = TimeUnit.MILLISECONDS): DistinctFlow = apply { + wrapped.maxTime(maxTime, timeUnit) + } + + /** + * Sets the collation options + * + * A null value represents the server default. + * + * @param collation the collation options to use + * @return this + */ + public fun collation(collation: Collation?): DistinctFlow = apply { wrapped.collation(collation) } + + /** + * Sets the comment for this operation. A null value means no comment is set. + * + * @param comment the comment + * @return this + */ + public fun comment(comment: String?): DistinctFlow = apply { wrapped.comment(comment) } + + /** + * Sets the comment for this operation. A null value means no comment is set. + * + * @param comment the comment + * @return this + */ + public fun comment(comment: BsonValue?): DistinctFlow = apply { wrapped.comment(comment) } + + public override suspend fun collect(collector: FlowCollector): Unit = wrapped.asFlow().collect(collector) +} diff --git a/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/FindFlow.kt b/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/FindFlow.kt new file mode 100644 index 00000000000..5f5381a85f3 --- /dev/null +++ b/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/FindFlow.kt @@ -0,0 +1,297 @@ +/* + * 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.CursorType +import com.mongodb.ExplainVerbosity +import com.mongodb.client.model.Collation +import com.mongodb.reactivestreams.client.FindPublisher +import java.util.concurrent.TimeUnit +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.FlowCollector +import kotlinx.coroutines.reactive.asFlow +import kotlinx.coroutines.reactive.awaitSingle +import org.bson.BsonValue +import org.bson.Document +import org.bson.conversions.Bson + +/** + * Flow implementation for find operations. + * + * @param T The type of the result. + * @see [Collection filter](https://www.mongodb.com/docs/manual/reference/method/db.collection.find/) + */ +public class FindFlow(private val wrapped: FindPublisher) : Flow { + + /** + * Sets the number of documents to return per batch. + * + * @param batchSize the batch size + * @return this + * @see [Batch Size](https://www.mongodb.com/docs/manual/reference/method/cursor.batchSize/#cursor.batchSize) + */ + public fun batchSize(batchSize: Int): FindFlow = apply { wrapped.batchSize(batchSize) } + + /** + * Sets the query filter to apply to the query. + * + * @param filter the filter. + * @return this + * @see [Collection filter](https://www.mongodb.com/docs/manual/reference/method/db.collection.find/) + */ + public fun filter(filter: Bson?): FindFlow = apply { wrapped.filter(filter) } + + /** + * Sets the limit to apply. + * + * @param limit the limit, which may be 0 + * @return this + * @see [Cursor limit](https://www.mongodb.com/docs/manual/reference/method/cursor.limit/#cursor.limit) + */ + public fun limit(limit: Int): FindFlow = apply { wrapped.limit(limit) } + + /** + * Sets the number of documents to skip. + * + * @param skip the number of documents to skip + * @return this + * @see [Cursor skip](https://www.mongodb.com/docs/manual/reference/method/cursor.skip/#cursor.skip) + */ + public fun skip(skip: Int): FindFlow = apply { wrapped.skip(skip) } + + /** + * Sets the maximum execution time on the server for this operation. + * + * @param maxTime the max time + * @param timeUnit the time unit, which defaults to Milliseconds + * @return this + */ + public fun maxTime(maxTime: Long, timeUnit: TimeUnit = TimeUnit.MILLISECONDS): FindFlow = apply { + wrapped.maxTime(maxTime, timeUnit) + } + + /** + * The maximum amount of time for the server to wait on new documents to satisfy a tailable cursor query. This only + * applies to a TAILABLE_AWAIT cursor. When the cursor is not a TAILABLE_AWAIT cursor, this option is ignored. + * + * On servers >= 3.2, this option will be specified on the getMore command as "maxTimeMS". The default is no value: + * no "maxTimeMS" is sent to the server with the getMore command. + * + * On servers < 3.2, this option is ignored, and indicates that the driver should respect the server's default value + * + * A zero value will be ignored. + * + * @param maxAwaitTime the max await time + * @param timeUnit the time unit to return results in, which defaults to Milliseconds + * @return the maximum await execution time in the given time unit + * @see [Max Time](https://www.mongodb.com/docs/manual/reference/method/cursor.maxTimeMS/#cursor.maxTimeMS) + */ + public fun maxAwaitTime(maxAwaitTime: Long, timeUnit: TimeUnit = TimeUnit.MILLISECONDS): FindFlow = apply { + wrapped.maxAwaitTime(maxAwaitTime, timeUnit) + } + + /** + * Sets a document describing the fields to return for all matching documents. + * + * @param projection the project document. + * @return this + */ + public fun projection(projection: Bson?): FindFlow = apply { wrapped.projection(projection) } + + /** + * Sets the sort criteria to apply to the query. + * + * @param sort the sort criteria. + * @return this + * @see [Cursor sort](https://www.mongodb.com/docs/manual/reference/method/cursor.sort/) + */ + public fun sort(sort: Bson?): FindFlow = apply { wrapped.sort(sort) } + + /** + * The server normally times out idle cursors after an inactivity period (10 minutes) to prevent excess memory use. + * Set this option to prevent that. + * + * @param noCursorTimeout true if cursor timeout is disabled + * @return this + */ + public fun noCursorTimeout(noCursorTimeout: Boolean): FindFlow = apply { + wrapped.noCursorTimeout(noCursorTimeout) + } + + /** + * Users should not set this under normal circumstances. + * + * @param oplogReplay if oplog replay is enabled + * @return this + * @deprecated oplogReplay has been deprecated in MongoDB 4.4. + */ + @Suppress("DEPRECATION") + @Deprecated("oplogReplay has been deprecated in MongoDB 4.4", replaceWith = ReplaceWith("")) + public fun oplogReplay(oplogReplay: Boolean): FindFlow = apply { wrapped.oplogReplay(oplogReplay) } + + /** + * Get partial results from a sharded cluster if one or more shards are unreachable (instead of throwing an error). + * + * @param partial if partial results for sharded clusters is enabled + * @return this + */ + public fun partial(partial: Boolean): FindFlow = apply { wrapped.partial(partial) } + + /** + * Sets the cursor type. + * + * @param cursorType the cursor type + * @return this + */ + public fun cursorType(cursorType: CursorType): FindFlow = apply { wrapped.cursorType(cursorType) } + + /** + * Sets the collation options + * + * A null value represents the server default. + * + * @param collation the collation options to use + * @return this + */ + public fun collation(collation: Collation?): FindFlow = apply { wrapped.collation(collation) } + + /** + * Sets the comment for this operation. A null value means no comment is set. + * + * @param comment the comment + * @return this + */ + public fun comment(comment: String?): FindFlow = apply { wrapped.comment(comment) } + + /** + * Sets the comment for this operation. A null value means no comment is set. + * + * The comment can be any valid BSON type for server versions 4.4 and above. Server versions between 3.6 and 4.2 + * only support string as comment, and providing a non-string type will result in a server-side error. + * + * @param comment the comment + * @return this + */ + public fun comment(comment: BsonValue?): FindFlow = apply { wrapped.comment(comment) } + + /** + * Sets the hint for which index to use. A null value means no hint is set. + * + * @param hint the hint + * @return this + */ + public fun hint(hint: Bson?): FindFlow = apply { wrapped.hint(hint) } + + /** + * Sets the hint to apply. + * + * Note: If [FindFlow.hint] is set that will be used instead of any hint string. + * + * @param hint the name of the index which should be used for the operation + * @return this + */ + public fun hintString(hint: String?): FindFlow = apply { wrapped.hintString(hint) } + + /** + * Add top-level variables to the operation. A null value means no variables are set. + * + * Allows for improved command readability by separating the variables from the query text. + * + * @param variables for find operation + * @return this + */ + public fun let(variables: Bson?): FindFlow = apply { wrapped.let(variables) } + + /** + * Sets the exclusive upper bound for a specific index. A null value means no max is set. + * + * @param max the max + * @return this + */ + public fun max(max: Bson?): FindFlow = apply { wrapped.max(max) } + + /** + * Sets the minimum inclusive lower bound for a specific index. A null value means no max is set. + * + * @param min the min + * @return this + */ + public fun min(min: Bson?): FindFlow = apply { wrapped.min(min) } + + /** + * Sets the returnKey. If true the find operation will return only the index keys in the resulting documents. + * + * @param returnKey the returnKey + * @return this + */ + public fun returnKey(returnKey: Boolean): FindFlow = apply { wrapped.returnKey(returnKey) } + + /** + * Sets the showRecordId. Set to true to add a field `$recordId` to the returned documents. + * + * @param showRecordId the showRecordId + * @return this + */ + public fun showRecordId(showRecordId: Boolean): FindFlow = apply { wrapped.showRecordId(showRecordId) } + + /** + * Enables writing to temporary files on the server. When set to true, the server can write temporary data to disk + * while executing the find operation. + * + * This option is sent only if the caller explicitly sets it to true. + * + * @param allowDiskUse the allowDiskUse + * @return this + */ + public fun allowDiskUse(allowDiskUse: Boolean?): FindFlow = apply { wrapped.allowDiskUse(allowDiskUse) } + + /** + * Explain the execution plan for this operation with the given verbosity level + * + * @param R the type of the document class + * @param verbosity the verbosity of the explanation + * @return the execution plan + * @see [Explain command](https://www.mongodb.com/docs/manual/reference/command/explain/) + */ + @JvmName("explainDocument") + public suspend fun explain(verbosity: ExplainVerbosity? = null): Document = explain(verbosity) + + /** + * Explain the execution plan for this operation with the given verbosity level + * + * @param R the type of the document class + * @param resultClass the result document type. + * @param verbosity the verbosity of the explanation + * @return the execution plan + * @see [Explain command](https://www.mongodb.com/docs/manual/reference/command/explain/) + */ + public suspend fun explain(resultClass: Class, verbosity: ExplainVerbosity? = null): R = + if (verbosity == null) wrapped.explain(resultClass).awaitSingle() + else wrapped.explain(resultClass, verbosity).awaitSingle() + + /** + * Explain the execution plan for this operation with the given verbosity level + * + * @param R the type of the document class + * @param verbosity the verbosity of the explanation + * @return the execution plan + * @see [Explain command](https://www.mongodb.com/docs/manual/reference/command/explain/) + */ + public suspend inline fun explain(verbosity: ExplainVerbosity? = null): R = + explain(R::class.java, verbosity) + + public override suspend fun collect(collector: FlowCollector): Unit = wrapped.asFlow().collect(collector) +} diff --git a/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/ListCollectionsFlow.kt b/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/ListCollectionsFlow.kt new file mode 100644 index 00000000000..a35273046ac --- /dev/null +++ b/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/ListCollectionsFlow.kt @@ -0,0 +1,79 @@ +/* + * 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.reactivestreams.client.ListCollectionsPublisher +import java.util.concurrent.TimeUnit +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.FlowCollector +import kotlinx.coroutines.reactive.asFlow +import org.bson.BsonValue +import org.bson.conversions.Bson + +/** + * Flow implementation for list collection operations. + * + * @param T The type of the result. + * @see [List collections](https://www.mongodb.com/docs/manual/reference/command/listCollections/) + */ +public class ListCollectionsFlow(private val wrapped: ListCollectionsPublisher) : Flow { + /** + * Sets the maximum execution time on the server for this operation. + * + * @param maxTime the max time + * @param timeUnit the time unit, defaults to Milliseconds + * @return this + * @see [Max Time](https://www.mongodb.com/docs/manual/reference/operator/meta/maxTimeMS/) + */ + public fun maxTime(maxTime: Long, timeUnit: TimeUnit = TimeUnit.MILLISECONDS): ListCollectionsFlow = apply { + wrapped.maxTime(maxTime, timeUnit) + } + + /** + * Sets the number of documents to return per batch. + * + * @param batchSize the batch size + * @return this + * @see [Batch Size](https://www.mongodb.com/docs/manual/reference/method/cursor.batchSize/#cursor.batchSize) + */ + public fun batchSize(batchSize: Int): ListCollectionsFlow = apply { wrapped.batchSize(batchSize) } + + /** + * Sets the query filter to apply to the returned database names. + * + * @param filter the filter, which may be null. + * @return this + */ + public fun filter(filter: Bson?): ListCollectionsFlow = apply { wrapped.filter(filter) } + + /** + * Sets the comment for this operation. A null value means no comment is set. + * + * @param comment the comment + * @return this + */ + public fun comment(comment: String?): ListCollectionsFlow = apply { wrapped.comment(comment) } + + /** + * Sets the comment for this operation. A null value means no comment is set. + * + * @param comment the comment + * @return this + */ + public fun comment(comment: BsonValue?): ListCollectionsFlow = apply { wrapped.comment(comment) } + + public override suspend fun collect(collector: FlowCollector): Unit = wrapped.asFlow().collect(collector) +} diff --git a/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/ListDatabasesFlow.kt b/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/ListDatabasesFlow.kt new file mode 100644 index 00000000000..ad642da8d25 --- /dev/null +++ b/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/ListDatabasesFlow.kt @@ -0,0 +1,98 @@ +/* + * 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.reactivestreams.client.ListDatabasesPublisher +import java.util.concurrent.TimeUnit +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.FlowCollector +import kotlinx.coroutines.reactive.asFlow +import org.bson.BsonValue +import org.bson.conversions.Bson + +/** + * Flow implementation for list database operations. + * + * @param T The type of the result. + * @see [List databases](https://www.mongodb.com/docs/manual/reference/command/listDatabases/) + */ +public class ListDatabasesFlow(private val wrapped: ListDatabasesPublisher) : Flow { + /** + * Sets the maximum execution time on the server for this operation. + * + * @param maxTime the max time + * @param timeUnit the time unit, defaults to Milliseconds + * @return this + * @see [Max Time](https://www.mongodb.com/docs/manual/reference/operator/meta/maxTimeMS/) + */ + public fun maxTime(maxTime: Long, timeUnit: TimeUnit = TimeUnit.MILLISECONDS): ListDatabasesFlow = apply { + wrapped.maxTime(maxTime, timeUnit) + } + + /** + * Sets the number of documents to return per batch. + * + * @param batchSize the batch size + * @return this + * @see [Batch Size](https://www.mongodb.com/docs/manual/reference/method/cursor.batchSize/#cursor.batchSize) + */ + public fun batchSize(batchSize: Int): ListDatabasesFlow = apply { wrapped.batchSize(batchSize) } + + /** + * Sets the query filter to apply to the returned database names. + * + * @param filter the filter, which may be null. + * @return this + */ + public fun filter(filter: Bson?): ListDatabasesFlow = apply { wrapped.filter(filter) } + /** + * Sets the nameOnly flag that indicates whether the command should return just the database names or return the + * database names and size information. + * + * @param nameOnly the nameOnly flag, which may be null + * @return this + */ + public fun nameOnly(nameOnly: Boolean?): ListDatabasesFlow = apply { wrapped.nameOnly(nameOnly) } + + /** + * Sets the authorizedDatabasesOnly flag that indicates whether the command should return just the databases which + * the user is authorized to see. + * + * @param authorizedDatabasesOnly the authorizedDatabasesOnly flag, which may be null + * @return this + */ + public fun authorizedDatabasesOnly(authorizedDatabasesOnly: Boolean?): ListDatabasesFlow = apply { + wrapped.authorizedDatabasesOnly(authorizedDatabasesOnly) + } + + /** + * Sets the comment for this operation. A null value means no comment is set. + * + * @param comment the comment + * @return this + */ + public fun comment(comment: String?): ListDatabasesFlow = apply { wrapped.comment(comment) } + + /** + * Sets the comment for this operation. A null value means no comment is set. + * + * @param comment the comment + * @return this + */ + public fun comment(comment: BsonValue?): ListDatabasesFlow = apply { wrapped.comment(comment) } + + public override suspend fun collect(collector: FlowCollector): Unit = wrapped.asFlow().collect(collector) +} diff --git a/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/ListIndexesFlow.kt b/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/ListIndexesFlow.kt new file mode 100644 index 00000000000..cacd0853a72 --- /dev/null +++ b/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/ListIndexesFlow.kt @@ -0,0 +1,70 @@ +/* + * 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.reactivestreams.client.ListIndexesPublisher +import java.util.concurrent.TimeUnit +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.FlowCollector +import kotlinx.coroutines.reactive.asFlow +import org.bson.BsonValue + +/** + * Flow implementation for list index operations. + * + * @param T The type of the result. + * @see [List indexes](https://www.mongodb.com/docs/manual/reference/command/listIndexes/) + */ +public class ListIndexesFlow(private val wrapped: ListIndexesPublisher) : Flow { + /** + * Sets the maximum execution time on the server for this operation. + * + * @param maxTime the max time + * @param timeUnit the time unit, defaults to Milliseconds + * @return this + * @see [Max Time](https://www.mongodb.com/docs/manual/reference/operator/meta/maxTimeMS/) + */ + public fun maxTime(maxTime: Long, timeUnit: TimeUnit = TimeUnit.MILLISECONDS): ListIndexesFlow = apply { + wrapped.maxTime(maxTime, timeUnit) + } + + /** + * Sets the number of documents to return per batch. + * + * @param batchSize the batch size + * @return this + * @see [Batch Size](https://www.mongodb.com/docs/manual/reference/method/cursor.batchSize/#cursor.batchSize) + */ + public fun batchSize(batchSize: Int): ListIndexesFlow = apply { wrapped.batchSize(batchSize) } + + /** + * Sets the comment for this operation. A null value means no comment is set. + * + * @param comment the comment + * @return this + */ + public fun comment(comment: String?): ListIndexesFlow = apply { wrapped.comment(comment) } + + /** + * Sets the comment for this operation. A null value means no comment is set. + * + * @param comment the comment + * @return this + */ + public fun comment(comment: BsonValue?): ListIndexesFlow = apply { wrapped.comment(comment) } + + public override suspend fun collect(collector: FlowCollector): Unit = wrapped.asFlow().collect(collector) +} diff --git a/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/MapReduceFlow.kt b/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/MapReduceFlow.kt new file mode 100644 index 00000000000..b43fd8e2818 --- /dev/null +++ b/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/MapReduceFlow.kt @@ -0,0 +1,214 @@ +/* + * 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. + */ +@file:Suppress("DEPRECATION") + +package com.mongodb.kotlin.client.coroutine + +import com.mongodb.client.model.Collation +import com.mongodb.client.model.MapReduceAction +import com.mongodb.reactivestreams.client.MapReducePublisher +import java.util.concurrent.TimeUnit +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.FlowCollector +import kotlinx.coroutines.reactive.asFlow +import kotlinx.coroutines.reactive.awaitFirstOrNull +import org.bson.conversions.Bson + +/** + * Flow implementation for map reduce operations. + * + * Note: Starting in MongoDB 5.0, map-reduce is deprecated, prefer Aggregation instead + * + * @param T The type of the result. + * @see [Map Reduce](https://www.mongodb.com/docs/manual/reference/command/mapReduce/) + */ +@Deprecated("Map Reduce has been deprecated. Use Aggregation instead", replaceWith = ReplaceWith("")) +public class MapReduceFlow(private val wrapped: MapReducePublisher) : Flow { + /** + * Sets the number of documents to return per batch. + * + * @param batchSize the batch size + * @return this + * @see [Batch Size](https://www.mongodb.com/docs/manual/reference/method/cursor.batchSize/#cursor.batchSize) + */ + public fun batchSize(batchSize: Int): MapReduceFlow = apply { wrapped.batchSize(batchSize) } + + /** + * Aggregates documents to a collection according to the specified map-reduce function with the given options, which + * must specify a non-inline result. + * + * @throws IllegalStateException if a collection name to write the results to has not been specified + */ + public suspend fun toCollection() { + wrapped.toCollection().awaitFirstOrNull() + } + + /** + * Sets the collectionName for the output of the MapReduce + * + * The default action is replace the collection if it exists, to change this use [.action]. + * + * @param collectionName the name of the collection that you want the map-reduce operation to write its output. + * @return this + */ + public fun collectionName(collectionName: String): MapReduceFlow = apply { + wrapped.collectionName(collectionName) + } + + /** + * Sets the JavaScript function that follows the reduce method and modifies the output. + * + * @param finalizeFunction the JavaScript function that follows the reduce method and modifies the output. + * @return this + * @see + * [Requirements for the finalize Function](https://www.mongodb.com/docs/manual/reference/command/mapReduce/#mapreduce-finalize-cmd) + */ + public fun finalizeFunction(finalizeFunction: String?): MapReduceFlow = apply { + wrapped.finalizeFunction(finalizeFunction) + } + + /** + * Sets the global variables that are accessible in the map, reduce and finalize functions. + * + * @param scope the global variables that are accessible in the map, reduce and finalize functions. + * @return this + * @see [mapReduce command](https://www.mongodb.com/docs/manual/reference/command/mapReduce) + */ + public fun scope(scope: Bson?): MapReduceFlow = apply { wrapped.scope(scope) } + + /** + * Sets the sort criteria to apply to the query. + * + * @param sort the sort criteria + * @return this + * @see [Sort results](https://www.mongodb.com/docs/manual/reference/method/cursor.sort/) + */ + public fun sort(sort: Bson?): MapReduceFlow = apply { wrapped.sort(sort) } + + /** + * Sets the query filter to apply to the query. + * + * @param filter the filter to apply to the query. + * @return this + * @see [Filter results](https://www.mongodb.com/docs/manual/reference/method/db.collection.find/) + */ + public fun filter(filter: Bson?): MapReduceFlow = apply { wrapped.filter(filter) } + + /** + * Sets the limit to apply. + * + * @param limit the limit + * @return this + * @see [Cursor limit](https://www.mongodb.com/docs/manual/reference/method/cursor.limit/#cursor.limit) + */ + public fun limit(limit: Int): MapReduceFlow = apply { wrapped.limit(limit) } + + /** + * Sets the flag that specifies whether to convert intermediate data into BSON format between the execution of the + * map and reduce functions. Defaults to false. + * + * @param jsMode the flag that specifies whether to convert intermediate data into BSON format between the execution + * of the map and reduce functions + * @return jsMode + * @see [mapReduce command](https://www.mongodb.com/docs/manual/reference/command/mapReduce) + */ + public fun jsMode(jsMode: Boolean): MapReduceFlow = apply { wrapped.jsMode(jsMode) } + + /** + * Sets whether to include the timing information in the result information. + * + * @param verbose whether to include the timing information in the result information. + * @return this + */ + public fun verbose(verbose: Boolean): MapReduceFlow = apply { wrapped.verbose(verbose) } + + /** + * Sets the maximum execution time on the server for this operation. + * + * @param maxTime the max time + * @param timeUnit the time unit, defaults to Milliseconds + * @return this + * @see [Max Time](https://www.mongodb.com/docs/manual/reference/method/cursor.maxTimeMS/#cursor.maxTimeMS) + */ + public fun maxTime(maxTime: Long, timeUnit: TimeUnit = TimeUnit.MILLISECONDS): MapReduceFlow = apply { + wrapped.maxTime(maxTime, timeUnit) + } + + /** + * Specify the `MapReduceAction` to be used when writing to a collection. + * + * @param action an [com.mongodb.client.model.MapReduceAction] to perform on the collection + * @return this + */ + public fun action(action: MapReduceAction): MapReduceFlow = apply { wrapped.action(action) } + + /** + * Sets the name of the database to output into. + * + * @param databaseName the name of the database to output into. + * @return this + * @see + * [output with an action](https://www.mongodb.com/docs/manual/reference/command/mapReduce/#output-to-a-collection-with-an-action) + */ + public fun databaseName(databaseName: String?): MapReduceFlow = apply { wrapped.databaseName(databaseName) } + + /** + * Sets if the output database is sharded + * + * @param sharded if the output database is sharded + * @return this + * @see + * [output with an action](https://www.mongodb.com/docs/manual/reference/command/mapReduce/#output-to-a-collection-with-an-action) + */ + public fun sharded(sharded: Boolean): MapReduceFlow = apply { wrapped.sharded(sharded) } + + /** + * Sets if the post-processing step will prevent MongoDB from locking the database. + * + * Valid only with the `MapReduceAction.MERGE` or `MapReduceAction.REDUCE` actions. + * + * @param nonAtomic if the post-processing step will prevent MongoDB from locking the database. + * @return this + * @see + * [output with an action](https://www.mongodb.com/docs/manual/reference/command/mapReduce/#output-to-a-collection-with-an-action) + */ + public fun nonAtomic(nonAtomic: Boolean): MapReduceFlow = apply { wrapped.nonAtomic(nonAtomic) } + + /** + * Sets the bypass document level validation flag. + * + * Note: This only applies when an $out or $merge stage is specified. + * + * @param bypassDocumentValidation If true, allows the write to opt-out of document level validation. + * @return this + * @see [Aggregation command](https://www.mongodb.com/docs/manual/reference/command/aggregate/) + */ + public fun bypassDocumentValidation(bypassDocumentValidation: Boolean?): MapReduceFlow = apply { + wrapped.bypassDocumentValidation(bypassDocumentValidation) + } + + /** + * Sets the collation options + * + * A null value represents the server default. + * + * @param collation the collation options to use + * @return this + */ + public fun collation(collation: Collation?): MapReduceFlow = apply { wrapped.collation(collation) } + + public override suspend fun collect(collector: FlowCollector): Unit = wrapped.asFlow().collect(collector) +} diff --git a/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/MongoClient.kt b/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/MongoClient.kt new file mode 100644 index 00000000000..fc97c2e3bb4 --- /dev/null +++ b/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/MongoClient.kt @@ -0,0 +1,287 @@ +/* + * 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.ClientSessionOptions +import com.mongodb.ConnectionString +import com.mongodb.MongoClientSettings +import com.mongodb.MongoDriverInformation +import com.mongodb.connection.ClusterDescription +import com.mongodb.lang.Nullable +import com.mongodb.reactivestreams.client.MongoClient as JMongoClient +import com.mongodb.reactivestreams.client.MongoClients as JMongoClients +import java.io.Closeable +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.reactive.asFlow +import kotlinx.coroutines.reactive.awaitSingle +import org.bson.Document +import org.bson.conversions.Bson + +/** + * A client-side representation of a MongoDB cluster. + * + * Instances can represent either a standalone MongoDB instance, a replica set, or a sharded cluster. Instance of this + * class are responsible for maintaining an up-to-date state of the cluster, and possibly cache resources related to + * this, including background threads for monitoring, and connection pools. + * + * Instances of this class serve as factories for [MongoDatabase] instances. Instances of this class can be created via + * the [MongoClient.create] helpers + * + * @see MongoClient.create + */ +public class MongoClient(private val wrapped: JMongoClient) : Closeable { + + /** + * A factory for [MongoClient] instances. + * + * @see MongoClient + * @since 4.10 + */ + public companion object Factory { + /** + * Create a new client with the given connection string as if by a call to [create]. + * + * @param connectionString the connection + * @return the client + */ + public fun create(connectionString: String): MongoClient = create(ConnectionString(connectionString)) + + /** + * Create a new client with the given connection string. + * + * @param connectionString the connection string, defaults to `mongodb://localhost`. + * @param mongoDriverInformation any driver information to associate with the MongoClient + * @return the client + */ + public fun create( + connectionString: ConnectionString = ConnectionString("mongodb://localhost"), + @Nullable mongoDriverInformation: MongoDriverInformation? = null + ): MongoClient { + return create( + MongoClientSettings.builder().applyConnectionString(connectionString).build(), mongoDriverInformation) + } + + /** + * Create a new client with the given connection string. + * + * For each of the settings classed configurable via [MongoClientSettings], the connection string is applied by + * calling the `applyConnectionString` method on an instance of setting's builder class, building the setting, + * and adding it to an instance of [com.mongodb.MongoClientSettings.Builder]. + * + * @param settings the client settings + * @param mongoDriverInformation any driver information to associate with the MongoClient + * @return + */ + public fun create( + settings: MongoClientSettings, + @Nullable mongoDriverInformation: MongoDriverInformation? = null + ): MongoClient { + val builder = + if (mongoDriverInformation == null) MongoDriverInformation.builder() + else MongoDriverInformation.builder(mongoDriverInformation) + return MongoClient(JMongoClients.create(settings, builder.driverName("kotlin").build())) + } + } + + public override fun close(): Unit = wrapped.close() + + /** + * Gets the current cluster description. + * + * This method will not block, meaning that it may return a [ClusterDescription] whose `clusterType` is unknown and + * whose [com.mongodb.connection.ServerDescription]s are all in the connecting state. If the application requires + * notifications after the driver has connected to a member of the cluster, it should register a + * [com.mongodb.event.ClusterListener] via the [com.mongodb.connection.ClusterSettings] in + * [com.mongodb.MongoClientSettings]. + * + * @return the current cluster description + * @see com.mongodb.connection.ClusterSettings.Builder.addClusterListener + * @see com.mongodb.MongoClientSettings.Builder.applyToClusterSettings + */ + public fun getClusterDescription(): ClusterDescription = wrapped.clusterDescription + + /** + * Gets a [MongoDatabase] instance for the given database name. + * + * @param databaseName the name of the database to retrievecom.mongodb.connection. + * @return a `MongoDatabase` representing the specified database + * @throws IllegalArgumentException if databaseName is invalid + * @see com.mongodb.MongoNamespace.checkDatabaseNameValidity + */ + public fun getDatabase(databaseName: String): MongoDatabase = MongoDatabase(wrapped.getDatabase(databaseName)) + + /** + * Creates a client session. + * + * Note: A ClientSession instance can not be used concurrently in multiple operations. + * + * @param options the options for the client session + * @return the client session + */ + public suspend fun startSession( + options: ClientSessionOptions = ClientSessionOptions.builder().build() + ): ClientSession = ClientSession(wrapped.startSession(options).awaitSingle()) + + /** + * Get a list of the database names + * + * @return an iterable containing all the names of all the databases + * @see [List Databases](https://www.mongodb.com/docs/manual/reference/command/listDatabases) + */ + public fun listDatabaseNames(): Flow = wrapped.listDatabaseNames().asFlow() + + /** + * Gets the list of databases + * + * @param clientSession the client session with which to associate this operation + * @return the list databases iterable interface + * @see [List Databases](https://www.mongodb.com/docs/manual/reference/command/listDatabases) + */ + public fun listDatabaseNames(clientSession: ClientSession): Flow = + wrapped.listDatabaseNames(clientSession.wrapped).asFlow() + + /** + * Gets the list of databases + * + * @return the list databases iterable interface + */ + @JvmName("listDatabasesAsDocument") + public fun listDatabases(): ListDatabasesFlow = listDatabases() + + /** + * Gets the list of databases + * + * @param clientSession the client session with which to associate this operation + * @return the list databases iterable interface + */ + @JvmName("listDatabasesAsDocumentWithSession") + public fun listDatabases(clientSession: ClientSession): ListDatabasesFlow = + listDatabases(clientSession) + + /** + * Gets the list of databases + * + * @param T the type of the class to use + * @param resultClass the target document type of the iterable. + * @return the list databases iterable interface + */ + public fun listDatabases(resultClass: Class): ListDatabasesFlow = + ListDatabasesFlow(wrapped.listDatabases(resultClass)) + + /** + * Gets the list of databases + * + * @param T the type of the class to use + * @param clientSession the client session with which to associate this operation + * @param resultClass the target document type of the iterable. + * @return the list databases iterable interface + */ + public fun listDatabases(clientSession: ClientSession, resultClass: Class): ListDatabasesFlow = + ListDatabasesFlow(wrapped.listDatabases(clientSession.wrapped, resultClass)) + + /** + * Gets the list of databases + * + * @param T the type of the class to use + * @return the list databases iterable interface + */ + public inline fun listDatabases(): ListDatabasesFlow = listDatabases(T::class.java) + + /** + * Gets the list of databases + * + * @param clientSession the client session with which to associate this operation + * @param T the type of the class to use + * @return the list databases iterable interface + */ + public inline fun listDatabases(clientSession: ClientSession): ListDatabasesFlow = + listDatabases(clientSession, T::class.java) + + /** + * Creates a change stream for this client. + * + * @param pipeline the aggregation pipeline to apply to the change stream, defaults to an empty pipeline. + * @return the change stream iterable + * @see [Change Streams](https://dochub.mongodb.org/changestreams] + */ + @JvmName("watchAsDocument") + public fun watch(pipeline: List = emptyList()): ChangeStreamFlow = watch(pipeline) + + /** + * Creates a change stream for this client. + * + * @param clientSession the client session with which to associate this operation + * @param pipeline the aggregation pipeline to apply to the change stream, defaults to an empty pipeline. + * @return the change stream iterable + * @see [Change Streams](https://dochub.mongodb.org/changestreams] + */ + @JvmName("watchAsDocumentWithSession") + public fun watch(clientSession: ClientSession, pipeline: List = emptyList()): ChangeStreamFlow = + watch(clientSession, pipeline) + + /** + * Creates a change stream for this client. + * + * @param T the target document type of the iterable. + * @param pipeline the aggregation pipeline to apply to the change stream, defaults to an empty pipeline. + * @param resultClass the target document type of the iterable. + * @return the change stream iterable + * @see [Change Streams](https://dochub.mongodb.org/changestreams] + */ + public fun watch(pipeline: List = emptyList(), resultClass: Class): ChangeStreamFlow = + ChangeStreamFlow(wrapped.watch(pipeline, resultClass)) + + /** + * Creates a change stream for this client. + * + * @param T the target document type of the iterable. + * @param clientSession the client session with which to associate this operation + * @param pipeline the aggregation pipeline to apply to the change stream, defaults to an empty pipeline. + * @param resultClass the target document type of the iterable. + * @return the change stream iterable + * @see [Change Streams](https://dochub.mongodb.org/changestreams] + */ + public fun watch( + clientSession: ClientSession, + pipeline: List = emptyList(), + resultClass: Class + ): ChangeStreamFlow = ChangeStreamFlow(wrapped.watch(clientSession.wrapped, pipeline, resultClass)) + + /** + * Creates a change stream for this client. + * + * @param T the target document type of the iterable. + * @param pipeline the aggregation pipeline to apply to the change stream, defaults to an empty pipeline. + * @return the change stream iterable + * @see [Change Streams](https://dochub.mongodb.org/changestreams] + */ + public inline fun watch(pipeline: List = emptyList()): ChangeStreamFlow = + watch(pipeline, T::class.java) + + /** + * Creates a change stream for this client. + * + * @param T the target document type of the iterable. + * @param clientSession the client session with which to associate this operation + * @param pipeline the aggregation pipeline to apply to the change stream, defaults to an empty pipeline. + * @return the change stream iterable + * @see [Change Streams](https://dochub.mongodb.org/changestreams] + */ + public inline fun watch( + clientSession: ClientSession, + pipeline: List = emptyList() + ): ChangeStreamFlow = watch(clientSession, pipeline, T::class.java) +} diff --git a/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/MongoCollection.kt b/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/MongoCollection.kt new file mode 100644 index 00000000000..2d6cd62f8e8 --- /dev/null +++ b/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/MongoCollection.kt @@ -0,0 +1,1526 @@ +/* + * 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.MongoNamespace +import com.mongodb.ReadConcern +import com.mongodb.ReadPreference +import com.mongodb.WriteConcern +import com.mongodb.bulk.BulkWriteResult +import com.mongodb.client.model.BulkWriteOptions +import com.mongodb.client.model.CountOptions +import com.mongodb.client.model.CreateIndexOptions +import com.mongodb.client.model.DeleteOptions +import com.mongodb.client.model.DropCollectionOptions +import com.mongodb.client.model.DropIndexOptions +import com.mongodb.client.model.EstimatedDocumentCountOptions +import com.mongodb.client.model.FindOneAndDeleteOptions +import com.mongodb.client.model.FindOneAndReplaceOptions +import com.mongodb.client.model.FindOneAndUpdateOptions +import com.mongodb.client.model.IndexModel +import com.mongodb.client.model.IndexOptions +import com.mongodb.client.model.InsertManyOptions +import com.mongodb.client.model.InsertOneOptions +import com.mongodb.client.model.RenameCollectionOptions +import com.mongodb.client.model.ReplaceOptions +import com.mongodb.client.model.UpdateOptions +import com.mongodb.client.model.WriteModel +import com.mongodb.client.result.DeleteResult +import com.mongodb.client.result.InsertManyResult +import com.mongodb.client.result.InsertOneResult +import com.mongodb.client.result.UpdateResult +import com.mongodb.reactivestreams.client.MongoCollection as JMongoCollection +import java.util.concurrent.TimeUnit +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.reactive.asFlow +import kotlinx.coroutines.reactive.awaitFirstOrNull +import kotlinx.coroutines.reactive.awaitSingle +import org.bson.BsonDocument +import org.bson.Document +import org.bson.codecs.configuration.CodecRegistry +import org.bson.conversions.Bson + +/** + * The MongoCollection representation. + * + * Note: Additions to this interface will not be considered to break binary compatibility. + * + * @param T The type that this collection will encode documents from and decode documents to. + */ +public class MongoCollection(private val wrapped: JMongoCollection) { + + /** The class of documents stored in this collection. */ + public val documentClass: Class + get() = wrapped.documentClass + + /** The namespace of this collection. */ + public val namespace: MongoNamespace + get() = wrapped.namespace + + /** The codec registry for the collection. */ + public val codecRegistry: CodecRegistry + get() = wrapped.codecRegistry + + /** the read preference for the collection. */ + public val readPreference: ReadPreference + get() = wrapped.readPreference + + /** The read concern for the collection. */ + public val readConcern: ReadConcern + get() = wrapped.readConcern + + /** The write concern for the collection. */ + public val writeConcern: WriteConcern + get() = wrapped.writeConcern + + /** + * Create a new collection instance with a different default class to cast any documents returned from the database + * into. + * + * @param R the default class to cast any documents returned from the database into. + * @param resultClass the target document type for the collection. + * @return a new MongoCollection instance with the different default class + */ + public fun withDocumentClass(resultClass: Class): MongoCollection = + MongoCollection(wrapped.withDocumentClass(resultClass)) + + /** + * Create a new collection instance with a different default class to cast any documents returned from the database + * into. + * + * @param R the default class to cast any documents returned from the database into. + * @return a new MongoCollection instance with the different default class + */ + public inline fun withDocumentClass(): MongoCollection = withDocumentClass(R::class.java) + + /** + * Create a new collection instance with a different codec registry. + * + * The [CodecRegistry] configured by this method is effectively treated by the driver as an instance of + * [org.bson.codecs.configuration.CodecProvider], which [CodecRegistry] extends. So there is no benefit to defining + * a class that implements [CodecRegistry]. Rather, an application should always create [CodecRegistry] instances + * using the factory methods in [org.bson.codecs.configuration.CodecRegistries]. + * + * @param newCodecRegistry the new [org.bson.codecs.configuration.CodecRegistry] for the collection + * @return a new MongoCollection instance with the different codec registry + * @see org.bson.codecs.configuration.CodecRegistries + */ + public fun withCodecRegistry(newCodecRegistry: CodecRegistry): MongoCollection = + MongoCollection(wrapped.withCodecRegistry(newCodecRegistry)) + + /** + * Create a new collection instance with a different read preference. + * + * @param newReadPreference the new [com.mongodb.ReadPreference] for the collection + * @return a new MongoCollection instance with the different readPreference + */ + public fun withReadPreference(newReadPreference: ReadPreference): MongoCollection = + MongoCollection(wrapped.withReadPreference(newReadPreference)) + + /** + * Create a new collection instance with a different read concern. + * + * @param newReadConcern the new [ReadConcern] for the collection + * @return a new MongoCollection instance with the different ReadConcern + * @see [Read Concern](https://www.mongodb.com/docs/manual/reference/readConcern/) + */ + public fun withReadConcern(newReadConcern: ReadConcern): MongoCollection = + MongoCollection(wrapped.withReadConcern(newReadConcern)) + + /** + * Create a new collection instance with a different write concern. + * + * @param newWriteConcern the new [com.mongodb.WriteConcern] for the collection + * @return a new MongoCollection instance with the different writeConcern + */ + public fun withWriteConcern(newWriteConcern: WriteConcern): MongoCollection = + MongoCollection(wrapped.withWriteConcern(newWriteConcern)) + + /** + * Counts the number of documents in the collection. + * + * Note: For a fast count of the total documents in a collection see [estimatedDocumentCount]. When migrating from + * `count()` to `countDocuments()` the following query operators must be replaced: + * ``` + * +-------------+--------------------------------+ + * | Operator | Replacement | + * +=============+================================+ + * | $where | $expr | + * +-------------+--------------------------------+ + * | $near | $geoWithin with $center | + * +-------------+--------------------------------+ + * | $nearSphere | $geoWithin with $centerSphere | + * +-------------+--------------------------------+ + * ``` + * + * @return the number of documents in the collection + */ + public suspend fun countDocuments(filter: Bson = BsonDocument(), options: CountOptions = CountOptions()): Long = + wrapped.countDocuments(filter, options).awaitSingle() + + /** + * Counts the number of documents in the collection according to the given options. + * + * Note: For a fast count of the total documents in a collection see [estimatedDocumentCount]. When migrating from + * `count()` to `countDocuments()` the following query operators must be replaced: + * ``` + * +-------------+--------------------------------+ + * | Operator | Replacement | + * +=============+================================+ + * | $where | $expr | + * +-------------+--------------------------------+ + * | $near | $geoWithin with $center | + * +-------------+--------------------------------+ + * | $nearSphere | $geoWithin with $centerSphere | + * +-------------+--------------------------------+ + * ``` + * + * @param clientSession the client session with which to associate this operation + * @param filter the query filter + * @param options the options describing the count + * @return the number of documents in the collection + */ + public suspend fun countDocuments( + clientSession: ClientSession, + filter: Bson = BsonDocument(), + options: CountOptions = CountOptions() + ): Long = wrapped.countDocuments(clientSession.wrapped, filter, options).awaitSingle() + + /** + * Gets an estimate of the count of documents in a collection using collection metadata. + * + * Implementation note: this method is implemented using the MongoDB server's count command + * + * @param options the options describing the count + * @return the number of documents in the collection + * @see [Count behaviour](https://www.mongodb.com/docs/manual/reference/command/count/#behavior) + */ + public suspend fun estimatedDocumentCount( + options: EstimatedDocumentCountOptions = EstimatedDocumentCountOptions() + ): Long = wrapped.estimatedDocumentCount(options).awaitSingle() + + /** + * Gets the distinct values of the specified field name. + * + * @param R the target type of the iterable. + * @param fieldName the field name + * @param filter the query filter + * @param resultClass the target document type of the iterable. + * @return an iterable of distinct values + * @see [Distinct command](https://www.mongodb.com/docs/manual/reference/command/distinct/) + */ + public fun distinct( + fieldName: String, + filter: Bson = BsonDocument(), + resultClass: Class + ): DistinctFlow = DistinctFlow(wrapped.distinct(fieldName, filter, resultClass)) + + /** + * Gets the distinct values of the specified field name. + * + * @param R the target type of the iterable. + * @param clientSession the client session with which to associate this operation + * @param fieldName the field name + * @param filter the query filter + * @param resultClass the target document type of the iterable. + * @return an iterable of distinct values + * @see [Distinct command](https://www.mongodb.com/docs/manual/reference/command/distinct/) + */ + public fun distinct( + clientSession: ClientSession, + fieldName: String, + filter: Bson = BsonDocument(), + resultClass: Class + ): DistinctFlow = DistinctFlow(wrapped.distinct(clientSession.wrapped, fieldName, filter, resultClass)) + + /** + * Gets the distinct values of the specified field name. + * + * @param R the target type of the iterable. + * @param fieldName the field name + * @param filter the query filter + * @return an iterable of distinct values + * @see [Distinct command](https://www.mongodb.com/docs/manual/reference/command/distinct/) + */ + public inline fun distinct(fieldName: String, filter: Bson = BsonDocument()): DistinctFlow = + distinct(fieldName, filter, R::class.java) + + /** + * Gets the distinct values of the specified field name. + * + * @param R the target type of the iterable. + * @param clientSession the client session with which to associate this operation + * @param fieldName the field name + * @param filter the query filter + * @return an iterable of distinct values + * @see [Distinct command](https://www.mongodb.com/docs/manual/reference/command/distinct/) + */ + public inline fun distinct( + clientSession: ClientSession, + fieldName: String, + filter: Bson = BsonDocument() + ): DistinctFlow = distinct(clientSession, fieldName, filter, R::class.java) + + /** + * Finds all documents in the collection. + * + * @param filter the query filter + * @return the find iterable interface + * @see [Query Documents](https://www.mongodb.com/docs/manual/tutorial/query-documents/) + */ + @JvmName("findAsT") public fun find(filter: Bson = BsonDocument()): FindFlow = find(filter, documentClass) + + /** + * Finds all documents in the collection. + * + * @param clientSession the client session with which to associate this operation + * @param filter the query filter + * @return the find iterable interface + * @see [Query Documents](https://www.mongodb.com/docs/manual/tutorial/query-documents/) + */ + @JvmName("findAsTWithSession") + public fun find(clientSession: ClientSession, filter: Bson = BsonDocument()): FindFlow = + find(clientSession, filter, documentClass) + + /** + * Finds all documents in the collection. + * + * @param R the class to decode each document into + * @param filter the query filter + * @param resultClass the target document type of the iterable. + * @return the find iterable interface + * @see [Query Documents](https://www.mongodb.com/docs/manual/tutorial/query-documents/) + */ + public fun find(filter: Bson = BsonDocument(), resultClass: Class): FindFlow = + FindFlow(wrapped.find(filter, resultClass)) + + /** + * Finds all documents in the collection. + * + * @param R the class to decode each document into + * @param clientSession the client session with which to associate this operation + * @param filter the query filter + * @param resultClass the target document type of the iterable. + * @return the find iterable interface + * @see [Query Documents](https://www.mongodb.com/docs/manual/tutorial/query-documents/) + */ + public fun find( + clientSession: ClientSession, + filter: Bson = BsonDocument(), + resultClass: Class + ): FindFlow = FindFlow(wrapped.find(clientSession.wrapped, filter, resultClass)) + + /** + * Finds all documents in the collection. + * + * @param R the class to decode each document into + * @param filter the query filter + * @return the find iterable interface + * @see [Query Documents](https://www.mongodb.com/docs/manual/tutorial/query-documents/) + */ + public inline fun find(filter: Bson = BsonDocument()): FindFlow = find(filter, R::class.java) + + /** + * Finds all documents in the collection. + * + * @param R the class to decode each document into + * @param clientSession the client session with which to associate this operation + * @param filter the query filter + * @return the find iterable interface + * @see [Query Documents](https://www.mongodb.com/docs/manual/tutorial/query-documents/) + */ + public inline fun find(clientSession: ClientSession, filter: Bson = BsonDocument()): FindFlow = + find(clientSession, filter, R::class.java) + + /** + * Aggregates documents according to the specified aggregation pipeline. + * + * @param pipeline the aggregation pipeline + * @return an iterable containing the result of the aggregation operation + * @see [Aggregate Command](https://www.mongodb.com/docs/manual/reference/command/aggregate/#dbcmd.aggregate/) + */ + @JvmName("aggregateAsT") + public fun aggregate(pipeline: List): AggregateFlow = + AggregateFlow(wrapped.aggregate(pipeline, documentClass)) + + /** + * Aggregates documents according to the specified aggregation pipeline. + * + * @param clientSession the client session with which to associate this operation + * @param pipeline the aggregation pipeline + * @return an iterable containing the result of the aggregation operation + * @see [Aggregate Command](https://www.mongodb.com/docs/manual/reference/command/aggregate/#dbcmd.aggregate/) + */ + @JvmName("aggregateAsTWithSession") + public fun aggregate(clientSession: ClientSession, pipeline: List): AggregateFlow = + AggregateFlow(wrapped.aggregate(clientSession.wrapped, pipeline, documentClass)) + + /** + * Aggregates documents according to the specified aggregation pipeline. + * + * @param R the class to decode each document into + * @param pipeline the aggregation pipeline + * @param resultClass the target document type of the iterable. + * @return an iterable containing the result of the aggregation operation + * @see [Aggregate Command](https://www.mongodb.com/docs/manual/reference/command/aggregate/#dbcmd.aggregate/) + */ + public fun aggregate(pipeline: List, resultClass: Class): AggregateFlow = + AggregateFlow(wrapped.aggregate(pipeline, resultClass)) + + /** + * Aggregates documents according to the specified aggregation pipeline. + * + * @param R the class to decode each document into + * @param clientSession the client session with which to associate this operation + * @param pipeline the aggregation pipeline + * @param resultClass the target document type of the iterable. + * @return an iterable containing the result of the aggregation operation + * @see [Aggregate Command](https://www.mongodb.com/docs/manual/reference/command/aggregate/#dbcmd.aggregate/) + */ + public fun aggregate( + clientSession: ClientSession, + pipeline: List, + resultClass: Class + ): AggregateFlow = AggregateFlow(wrapped.aggregate(clientSession.wrapped, pipeline, resultClass)) + + /** + * Aggregates documents according to the specified aggregation pipeline. + * + * @param R the class to decode each document into + * @param pipeline the aggregation pipeline + * @return an iterable containing the result of the aggregation operation + * @see [Aggregate Command](https://www.mongodb.com/docs/manual/reference/command/aggregate/#dbcmd.aggregate/) + */ + public inline fun aggregate(pipeline: List): AggregateFlow = + aggregate(pipeline, R::class.java) + + /** + * Aggregates documents according to the specified aggregation pipeline. + * + * @param R the class to decode each document into + * @param clientSession the client session with which to associate this operation + * @param pipeline the aggregation pipeline + * @return an iterable containing the result of the aggregation operation + * @see [Aggregate Command](https://www.mongodb.com/docs/manual/reference/command/aggregate/#dbcmd.aggregate/) + */ + public inline fun aggregate( + clientSession: ClientSession, + pipeline: List + ): AggregateFlow = aggregate(clientSession, pipeline, R::class.java) + + /** + * Creates a change stream for this collection. + * + * @param pipeline the aggregation pipeline to apply to the change stream, defaults to an empty pipeline. + * @return the change stream iterable + * @see [Change Streams](https://dochub.mongodb.org/changestreams] + */ + @JvmName("watchAsDocument") + public fun watch(pipeline: List = emptyList()): ChangeStreamFlow = watch(pipeline, documentClass) + + /** + * Creates a change stream for this collection. + * + * @param clientSession the client session with which to associate this operation + * @param pipeline the aggregation pipeline to apply to the change stream, defaults to an empty pipeline. + * @return the change stream iterable + * @see [Change Streams](https://dochub.mongodb.org/changestreams] + */ + @JvmName("watchAsDocumentWithSession") + public fun watch(clientSession: ClientSession, pipeline: List = emptyList()): ChangeStreamFlow = + watch(clientSession, pipeline, documentClass) + + /** + * Creates a change stream for this collection. + * + * @param R the target document type of the iterable. + * @param pipeline the aggregation pipeline to apply to the change stream, defaults to an empty pipeline. + * @param resultClass the target document type of the iterable. + * @return the change stream iterable + * @see [Change Streams](https://dochub.mongodb.org/changestreams] + */ + public fun watch(pipeline: List = emptyList(), resultClass: Class): ChangeStreamFlow = + ChangeStreamFlow(wrapped.watch(pipeline, resultClass)) + + /** + * Creates a change stream for this collection. + * + * @param R the target document type of the iterable. + * @param clientSession the client session with which to associate this operation + * @param pipeline the aggregation pipeline to apply to the change stream, defaults to an empty pipeline. + * @param resultClass the target document type of the iterable. + * @return the change stream iterable + * @see [Change Streams](https://dochub.mongodb.org/changestreams] + */ + public fun watch( + clientSession: ClientSession, + pipeline: List = emptyList(), + resultClass: Class + ): ChangeStreamFlow = ChangeStreamFlow(wrapped.watch(clientSession.wrapped, pipeline, resultClass)) + + /** + * Creates a change stream for this collection. + * + * @param R the target document type of the iterable. + * @param pipeline the aggregation pipeline to apply to the change stream, defaults to an empty pipeline. + * @return the change stream iterable + * @see [Change Streams](https://dochub.mongodb.org/changestreams] + */ + public inline fun watch(pipeline: List = emptyList()): ChangeStreamFlow = + watch(pipeline, R::class.java) + + /** + * Creates a change stream for this collection. + * + * @param R the target document type of the iterable. + * @param clientSession the client session with which to associate this operation + * @param pipeline the aggregation pipeline to apply to the change stream, defaults to an empty pipeline. + * @return the change stream iterable + * @see [Change Streams](https://dochub.mongodb.org/changestreams] + */ + public inline fun watch( + clientSession: ClientSession, + pipeline: List = emptyList() + ): ChangeStreamFlow = watch(clientSession, pipeline, R::class.java) + + /** + * Aggregates documents according to the specified map-reduce function. + * + * @param mapFunction A JavaScript function that associates or "maps" a value with a key and emits the key and value + * pair. + * @param reduceFunction A JavaScript function that "reduces" to a single object all the values associated with a + * particular key. + * @return an iterable containing the result of the map-reduce operation + * @see [map-reduce](https://www.mongodb.com/docs/manual/reference/command/mapReduce/) + */ + @Suppress("DEPRECATION") + @Deprecated("Map Reduce has been deprecated. Use Aggregation instead", replaceWith = ReplaceWith("")) + @JvmName("mapReduceAsT") + public fun mapReduce(mapFunction: String, reduceFunction: String): MapReduceFlow = + mapReduce(mapFunction, reduceFunction, documentClass) + + /** + * Aggregates documents according to the specified map-reduce function. + * + * @param clientSession the client session with which to associate this operation + * @param mapFunction A JavaScript function that associates or "maps" a value with a key and emits the key and value + * pair. + * @param reduceFunction A JavaScript function that "reduces" to a single object all the values associated with a + * particular key. + * @return an iterable containing the result of the map-reduce operation + * @see [map-reduce](https://www.mongodb.com/docs/manual/reference/command/mapReduce/) + */ + @Suppress("DEPRECATION") + @Deprecated("Map Reduce has been deprecated. Use Aggregation instead", replaceWith = ReplaceWith("")) + @JvmName("mapReduceAsTWithSession") + public fun mapReduce(clientSession: ClientSession, mapFunction: String, reduceFunction: String): MapReduceFlow = + mapReduce(clientSession, mapFunction, reduceFunction, documentClass) + + /** + * Aggregates documents according to the specified map-reduce function. + * + * @param R the class to decode each resulting document into. + * @param mapFunction A JavaScript function that associates or "maps" a value with a key and emits the key and value + * pair. + * @param reduceFunction A JavaScript function that "reduces" to a single object all the values associated with a + * particular key. + * @param resultClass the target document type of the iterable. + * @return an iterable containing the result of the map-reduce operation + * @see [map-reduce](https://www.mongodb.com/docs/manual/reference/command/mapReduce/) + */ + @Suppress("DEPRECATION") + @Deprecated("Map Reduce has been deprecated. Use Aggregation instead", replaceWith = ReplaceWith("")) + public fun mapReduce( + mapFunction: String, + reduceFunction: String, + resultClass: Class + ): MapReduceFlow = MapReduceFlow(wrapped.mapReduce(mapFunction, reduceFunction, resultClass)) + + /** + * Aggregates documents according to the specified map-reduce function. + * + * @param R the class to decode each resulting document into. + * @param clientSession the client session with which to associate this operation + * @param mapFunction A JavaScript function that associates or "maps" a value with a key and emits the key and value + * pair. + * @param reduceFunction A JavaScript function that "reduces" to a single object all the values associated with a + * particular key. + * @param resultClass the target document type of the iterable. + * @return an iterable containing the result of the map-reduce operation + * @see [map-reduce](https://www.mongodb.com/docs/manual/reference/command/mapReduce/) + */ + @Suppress("DEPRECATION") + @Deprecated("Map Reduce has been deprecated. Use Aggregation instead", replaceWith = ReplaceWith("")) + public fun mapReduce( + clientSession: ClientSession, + mapFunction: String, + reduceFunction: String, + resultClass: Class + ): MapReduceFlow = + MapReduceFlow(wrapped.mapReduce(clientSession.wrapped, mapFunction, reduceFunction, resultClass)) + + /** + * Aggregates documents according to the specified map-reduce function. + * + * @param R the class to decode each resulting document into. + * @param mapFunction A JavaScript function that associates or "maps" a value with a key and emits the key and value + * pair. + * @param reduceFunction A JavaScript function that "reduces" to a single object all the values associated with a + * particular key. + * @return an iterable containing the result of the map-reduce operation + * @see [map-reduce](https://www.mongodb.com/docs/manual/reference/command/mapReduce/) + */ + @Suppress("DEPRECATION") + @Deprecated("Map Reduce has been deprecated. Use Aggregation instead", replaceWith = ReplaceWith("")) + public inline fun mapReduce(mapFunction: String, reduceFunction: String): MapReduceFlow = + mapReduce(mapFunction, reduceFunction, R::class.java) + + /** + * Aggregates documents according to the specified map-reduce function. + * + * @param R the class to decode each resulting document into. + * @param clientSession the client session with which to associate this operation + * @param mapFunction A JavaScript function that associates or "maps" a value with a key and emits the key and value + * pair. + * @param reduceFunction A JavaScript function that "reduces" to a single object all the values associated with a + * particular key. + * @return an iterable containing the result of the map-reduce operation + * @see [map-reduce](https://www.mongodb.com/docs/manual/reference/command/mapReduce/) + */ + @Suppress("DEPRECATION") + @Deprecated("Map Reduce has been deprecated. Use Aggregation instead", replaceWith = ReplaceWith("")) + public inline fun mapReduce( + clientSession: ClientSession, + mapFunction: String, + reduceFunction: String + ): MapReduceFlow = mapReduce(clientSession, mapFunction, reduceFunction, R::class.java) + + /** + * Inserts the provided document. If the document is missing an identifier, the driver should generate one. + * + * Note: Supports retryable writes on MongoDB server versions 3.6 or higher when the retryWrites setting is enabled. + * + * @param document the document to insert + * @param options the options to apply to the operation + * @return the insert one result + * @throws com.mongodb.MongoWriteException if the write failed due to some specific write exception + * @throws com.mongodb.MongoWriteConcernException if the write failed due to being unable to fulfil the write + * concern + * @throws com.mongodb.MongoCommandException if the write failed due to a specific command exception + * @throws com.mongodb.MongoException if the write failed due some other failure + */ + public suspend fun insertOne(document: T, options: InsertOneOptions = InsertOneOptions()): InsertOneResult = + wrapped.insertOne(document, options).awaitSingle() + + /** + * Inserts the provided document. If the document is missing an identifier, the driver should generate one. + * + * Note: Supports retryable writes on MongoDB server versions 3.6 or higher when the retryWrites setting is enabled. + * + * @param clientSession the client session with which to associate this operation + * @param document the document to insert + * @param options the options to apply to the operation + * @return the insert one result + * @throws com.mongodb.MongoWriteException if the write failed due to some specific write exception + * @throws com.mongodb.MongoWriteConcernException if the write failed due to being unable to fulfil the write + * concern + * @throws com.mongodb.MongoCommandException if the write failed due to a specific command exception + * @throws com.mongodb.MongoException if the write failed due some other failure + */ + public suspend fun insertOne( + clientSession: ClientSession, + document: T, + options: InsertOneOptions = InsertOneOptions() + ): InsertOneResult = wrapped.insertOne(clientSession.wrapped, document, options).awaitSingle() + + /** + * Inserts one or more documents. A call to this method is equivalent to a call to the `bulkWrite` method + * + * Note: Supports retryable writes on MongoDB server versions 3.6 or higher when the retryWrites setting is enabled. + * + * @param documents the documents to insert + * @param options the options to apply to the operation + * @return the insert many result + * @throws com.mongodb.MongoBulkWriteException if there's an exception in the bulk write operation + * @throws com.mongodb.MongoCommandException if the write failed due to a specific command exception + * @throws com.mongodb.MongoException if the write failed due some other failure + * @throws IllegalArgumentException if the documents list is null or empty, or any of the documents in the list are + * null + */ + public suspend fun insertMany( + documents: List, + options: InsertManyOptions = InsertManyOptions() + ): InsertManyResult = wrapped.insertMany(documents, options).awaitSingle() + + /** + * Inserts one or more documents. A call to this method is equivalent to a call to the `bulkWrite` method + * + * Note: Supports retryable writes on MongoDB server versions 3.6 or higher when the retryWrites setting is enabled. + * + * @param clientSession the client session with which to associate this operation + * @param documents the documents to insert + * @param options the options to apply to the operation + * @return the insert many result + * @throws com.mongodb.MongoBulkWriteException if there's an exception in the bulk write operation + * @throws com.mongodb.MongoCommandException if the write failed due to a specific command exception + * @throws com.mongodb.MongoException if the write failed due some other failure + * @throws IllegalArgumentException if the documents list is null or empty, or any of the documents in the list are + * null + */ + public suspend fun insertMany( + clientSession: ClientSession, + documents: List, + options: InsertManyOptions = InsertManyOptions() + ): InsertManyResult = wrapped.insertMany(clientSession.wrapped, documents, options).awaitSingle() + + /** + * Update a single document in the collection according to the specified arguments. + * + * Use this method to only update the corresponding fields in the document according to the update operators used in + * the update document. To replace the entire document with a new document, use the corresponding [replaceOne] + * method. + * + * Note: Supports retryable writes on MongoDB server versions 3.6 or higher when the retryWrites setting is enabled. + * + * @param filter a document describing the query filter, which may not be null. + * @param update a document describing the update, which may not be null. The update to apply must include at least + * one update operator. + * @param options the options to apply to the update operation + * @return the result of the update one operation + * @throws com.mongodb.MongoWriteException if the write failed due to some specific write exception + * @throws com.mongodb.MongoWriteConcernException if the write failed due to being unable to fulfil the write + * concern + * @throws com.mongodb.MongoCommandException if the write failed due to a specific command exception + * @throws com.mongodb.MongoException if the write failed due some other failure + * @see [Modify Documents](https://www.mongodb.com/docs/manual/tutorial/modify-documents/) + * @see [Update Operators](https://www.mongodb.com/docs/manual/reference/operator/update/) + * @see [Update Command Behaviors](https://www.mongodb.com/docs/manual/reference/command/update/) + * @see [replaceOne] + */ + public suspend fun updateOne(filter: Bson, update: Bson, options: UpdateOptions = UpdateOptions()): UpdateResult = + wrapped.updateOne(filter, update, options).awaitSingle() + + /** + * Update a single document in the collection according to the specified arguments. + * + * Use this method to only update the corresponding fields in the document according to the update operators used in + * the update document. To replace the entire document with a new document, use the corresponding [replaceOne] + * method. + * + * Note: Supports retryable writes on MongoDB server versions 3.6 or higher when the retryWrites setting is enabled. + * + * @param clientSession the client session with which to associate this operation + * @param filter a document describing the query filter, which may not be null. + * @param update a document describing the update, which may not be null. The update to apply must include at least + * one update operator. + * @param options the options to apply to the update operation + * @return the result of the update one operation + * @throws com.mongodb.MongoWriteException if the write failed due to some specific write exception + * @throws com.mongodb.MongoWriteConcernException if the write failed due to being unable to fulfil the write + * concern + * @throws com.mongodb.MongoCommandException if the write failed due to a specific command exception + * @throws com.mongodb.MongoException if the write failed due some other failure + * @see [Modify Documents](https://www.mongodb.com/docs/manual/tutorial/modify-documents/) + * @see [Update Operators](https://www.mongodb.com/docs/manual/reference/operator/update/) + * @see [Update Command](https://www.mongodb.com/docs/manual/reference/command/update/) + * @see com.mongodb.client.MongoCollection.replaceOne + */ + public suspend fun updateOne( + clientSession: ClientSession, + filter: Bson, + update: Bson, + options: UpdateOptions = UpdateOptions() + ): UpdateResult = wrapped.updateOne(clientSession.wrapped, filter, update, options).awaitSingle() + + /** + * Update a single document in the collection according to the specified arguments. + * + * Note: Supports retryable writes on MongoDB server versions 3.6 or higher when the retryWrites setting is enabled. + * + * @param filter a document describing the query filter, which may not be null. + * @param update a pipeline describing the update, which may not be null. + * @param options the options to apply to the update operation + * @return the result of the update one operation + * @throws com.mongodb.MongoWriteException if the write failed due some other failure specific to the update command + * @throws com.mongodb.MongoWriteConcernException if the write failed due being unable to fulfil the write concern + * @throws com.mongodb.MongoException if the write failed due some other failure + * @see [Modify Documents](https://www.mongodb.com/docs/manual/tutorial/modify-documents/) + * @see [Update Operators](https://www.mongodb.com/docs/manual/reference/operator/update/) + */ + public suspend fun updateOne( + filter: Bson, + update: List, + options: UpdateOptions = UpdateOptions() + ): UpdateResult = wrapped.updateOne(filter, update, options).awaitSingle() + + /** + * Update a single document in the collection according to the specified arguments. + * + * Note: Supports retryable writes on MongoDB server versions 3.6 or higher when the retryWrites setting is enabled. + * + * @param clientSession the client session with which to associate this operation + * @param filter a document describing the query filter, which may not be null. + * @param update a pipeline describing the update, which may not be null. + * @param options the options to apply to the update operation + * @return the result of the update one operation + * @throws com.mongodb.MongoWriteException if the write failed due some other failure specific to the update command + * @throws com.mongodb.MongoWriteConcernException if the write failed due being unable to fulfil the write concern + * @throws com.mongodb.MongoException if the write failed due some other failure + * @see [Modify Documents](https://www.mongodb.com/docs/manual/tutorial/modify-documents/) + * @see [Update Operators](https://www.mongodb.com/docs/manual/reference/operator/update/) + */ + public suspend fun updateOne( + clientSession: ClientSession, + filter: Bson, + update: List, + options: UpdateOptions = UpdateOptions() + ): UpdateResult = wrapped.updateOne(clientSession.wrapped, filter, update, options).awaitSingle() + + /** + * Update all documents in the collection according to the specified arguments. + * + * @param filter a document describing the query filter, which may not be null. + * @param update a document describing the update, which may not be null. The update to apply must include only + * update operators. + * @param options the options to apply to the update operation + * @return the result of the update many operation + * @throws com.mongodb.MongoWriteException if the write failed due to some specific write exception + * @throws com.mongodb.MongoWriteConcernException if the write failed due to being unable to fulfil the write + * concern + * @throws com.mongodb.MongoCommandException if the write failed due to a specific command exception + * @throws com.mongodb.MongoException if the write failed due some other failure + * @see [Modify Documents](https://www.mongodb.com/docs/manual/tutorial/modify-documents/) + * @see [Update Operators](https://www.mongodb.com/docs/manual/reference/operator/update/) + */ + public suspend fun updateMany(filter: Bson, update: Bson, options: UpdateOptions = UpdateOptions()): UpdateResult = + wrapped.updateMany(filter, update, options).awaitSingle() + + /** + * Update all documents in the collection according to the specified arguments. + * + * @param clientSession the client session with which to associate this operation + * @param filter a document describing the query filter, which may not be null. + * @param update a document describing the update, which may not be null. The update to apply must include only + * update operators. + * @param options the options to apply to the update operation + * @return the result of the update many operation + * @throws com.mongodb.MongoWriteException if the write failed due to some specific write exception + * @throws com.mongodb.MongoWriteConcernException if the write failed due to being unable to fulfil the write + * concern + * @throws com.mongodb.MongoCommandException if the write failed due to a specific command exception + * @throws com.mongodb.MongoException if the write failed due some other failure + * @see [Modify Documents](https://www.mongodb.com/docs/manual/tutorial/modify-documents/) + * @see [Update Operators](https://www.mongodb.com/docs/manual/reference/operator/update/) + */ + public suspend fun updateMany( + clientSession: ClientSession, + filter: Bson, + update: Bson, + options: UpdateOptions = UpdateOptions() + ): UpdateResult = wrapped.updateMany(clientSession.wrapped, filter, update, options).awaitSingle() + + /** + * Update all documents in the collection according to the specified arguments. + * + * @param filter a document describing the query filter, which may not be null. + * @param update a pipeline describing the update, which may not be null. + * @param options the options to apply to the update operation + * @return the result of the update many operation + * @throws com.mongodb.MongoWriteException if the write failed due some other failure specific to the update command + * @throws com.mongodb.MongoWriteConcernException if the write failed due being unable to fulfil the write concern + * @throws com.mongodb.MongoException if the write failed due some other failure + * @see [Modify Documents](https://www.mongodb.com/docs/manual/tutorial/modify-documents/) + * @see [Update Operators](https://www.mongodb.com/docs/manual/reference/operator/update/) + */ + public suspend fun updateMany( + filter: Bson, + update: List, + options: UpdateOptions = UpdateOptions() + ): UpdateResult = wrapped.updateMany(filter, update, options).awaitSingle() + + /** + * Update all documents in the collection according to the specified arguments. + * + * @param clientSession the client session with which to associate this operation + * @param filter a document describing the query filter, which may not be null. + * @param update a pipeline describing the update, which may not be null. + * @param options the options to apply to the update operation + * @return the result of the update many operation + * @throws com.mongodb.MongoWriteException if the write failed due some other failure specific to the update command + * @throws com.mongodb.MongoWriteConcernException if the write failed due being unable to fulfil the write concern + * @throws com.mongodb.MongoException if the write failed due some other failure + * @see [Modify Documents](https://www.mongodb.com/docs/manual/tutorial/modify-documents/) + * @see [Update Operators](https://www.mongodb.com/docs/manual/reference/operator/update/) + */ + public suspend fun updateMany( + clientSession: ClientSession, + filter: Bson, + update: List, + options: UpdateOptions = UpdateOptions() + ): UpdateResult = wrapped.updateMany(clientSession.wrapped, filter, update, options).awaitSingle() + + /** + * Replace a document in the collection according to the specified arguments. + * + * Use this method to replace a document using the specified replacement argument. To update the document with + * update operators, use the corresponding [updateOne] method. + * + * Note: Supports retryable writes on MongoDB server versions 3.6 or higher when the retryWrites setting is enabled. + * + * @param filter the query filter to apply the replace operation + * @param replacement the replacement document + * @param options the options to apply to the replace operation + * @return the result of the replace one operation + * @throws com.mongodb.MongoWriteException if the write failed due to some specific write exception + * @throws com.mongodb.MongoWriteConcernException if the write failed due to being unable to fulfil the write + * concern + * @throws com.mongodb.MongoCommandException if the write failed due to a specific command exception + * @throws com.mongodb.MongoException if the write failed due some other failure + * @see [Modify Documents](https://www.mongodb.com/docs/manual/tutorial/modify-documents/#replace-the-document/) + * @see [Update Command Behaviors](https://www.mongodb.com/docs/manual/reference/command/update/) + * @since 3.6 + */ + public suspend fun replaceOne( + filter: Bson, + replacement: T, + options: ReplaceOptions = ReplaceOptions() + ): UpdateResult = wrapped.replaceOne(filter, replacement, options).awaitSingle() + + /** + * Replace a document in the collection according to the specified arguments. + * + * Use this method to replace a document using the specified replacement argument. To update the document with + * update operators, use the corresponding [updateOne] method. + * + * Note: Supports retryable writes on MongoDB server versions 3.6 or higher when the retryWrites setting is enabled. + * + * @param clientSession the client session with which to associate this operation + * @param filter the query filter to apply the replace operation + * @param replacement the replacement document + * @param options the options to apply to the replace operation + * @return the result of the replace one operation + * @throws com.mongodb.MongoWriteException if the write failed due to some specific write exception + * @throws com.mongodb.MongoWriteConcernException if the write failed due to being unable to fulfil the write + * concern + * @throws com.mongodb.MongoCommandException if the write failed due to a specific command exception + * @throws com.mongodb.MongoException if the write failed due some other failure + * @see [Modify Documents](https://www.mongodb.com/docs/manual/tutorial/modify-documents/#replace-the-document/) + * @see [Update Command Behaviors](https://www.mongodb.com/docs/manual/reference/command/update/) + * @since 3.6 + */ + public suspend fun replaceOne( + clientSession: ClientSession, + filter: Bson, + replacement: T, + options: ReplaceOptions = ReplaceOptions() + ): UpdateResult = wrapped.replaceOne(clientSession.wrapped, filter, replacement, options).awaitSingle() + + /** + * Removes at most one document from the collection that matches the given filter. + * + * If no documents match, the collection is not modified. + * + * Note: Supports retryable writes on MongoDB server versions 3.6 or higher when the retryWrites setting is enabled. + * + * @param filter the query filter to apply the delete operation + * @param options the options to apply to the delete operation + * @return the result of the remove one operation + * @throws com.mongodb.MongoWriteException if the write failed due to some specific write exception + * @throws com.mongodb.MongoWriteConcernException if the write failed due to being unable to fulfil the write + * concern + * @throws com.mongodb.MongoCommandException if the write failed due to a specific command exception + * @throws com.mongodb.MongoException if the write failed due some other failure + */ + public suspend fun deleteOne(filter: Bson, options: DeleteOptions = DeleteOptions()): DeleteResult = + wrapped.deleteOne(filter, options).awaitSingle() + + /** + * Removes at most one document from the collection that matches the given filter. + * + * If no documents match, the collection is not modified. + * + * Note: Supports retryable writes on MongoDB server versions 3.6 or higher when the retryWrites setting is enabled. + * + * @param clientSession the client session with which to associate this operation + * @param filter the query filter to apply the delete operation + * @param options the options to apply to the delete operation + * @return the result of the remove one operation + * @throws com.mongodb.MongoWriteException if the write failed due to some specific write exception + * @throws com.mongodb.MongoWriteConcernException if the write failed due to being unable to fulfil the write + * concern + * @throws com.mongodb.MongoCommandException if the write failed due to a specific command exception + * @throws com.mongodb.MongoException if the write failed due some other failure + */ + public suspend fun deleteOne( + clientSession: ClientSession, + filter: Bson, + options: DeleteOptions = DeleteOptions() + ): DeleteResult = wrapped.deleteOne(clientSession.wrapped, filter, options).awaitSingle() + + /** + * Removes all documents from the collection that match the given query filter. + * + * If no documents match, the collection is not modified. + * + * @param filter the query filter to apply the delete operation + * @param options the options to apply to the delete operation + * @return the result of the remove many operation + * @throws com.mongodb.MongoWriteException if the write failed due to some specific write exception + * @throws com.mongodb.MongoWriteConcernException if the write failed due to being unable to fulfil the write + * concern + * @throws com.mongodb.MongoCommandException if the write failed due to a specific command exception + * @throws com.mongodb.MongoException if the write failed due some other failure + */ + public suspend fun deleteMany(filter: Bson, options: DeleteOptions = DeleteOptions()): DeleteResult = + wrapped.deleteMany(filter, options).awaitSingle() + + /** + * Removes all documents from the collection that match the given query filter. + * + * If no documents match, the collection is not modified. + * + * @param clientSession the client session with which to associate this operation + * @param filter the query filter to apply the delete operation + * @param options the options to apply to the delete operation + * @return the result of the remove many operation + * @throws com.mongodb.MongoWriteException if the write failed due to some specific write exception + * @throws com.mongodb.MongoWriteConcernException if the write failed due to being unable to fulfil the write + * concern + * @throws com.mongodb.MongoCommandException if the write failed due to a specific command exception + * @throws com.mongodb.MongoException if the write failed due some other failure + */ + public suspend fun deleteMany( + clientSession: ClientSession, + filter: Bson, + options: DeleteOptions = DeleteOptions() + ): DeleteResult = wrapped.deleteMany(clientSession.wrapped, filter, options).awaitSingle() + + /** + * Executes a mix of inserts, updates, replaces, and deletes. + * + * Note: Supports retryable writes on MongoDB server versions 3.6 or higher when the retryWrites setting is enabled. + * The eligibility for retryable write support for bulk operations is determined on the whole bulk write. If the + * `requests` contain any `UpdateManyModels` or `DeleteManyModels` then the bulk operation will not support + * retryable writes. + * + * @param requests the writes to execute + * @param options the options to apply to the bulk write operation + * @return the result of the bulk write + * @throws com.mongodb.MongoBulkWriteException if there's an exception in the bulk write operation + * @throws com.mongodb.MongoException if there's an exception running the operation + */ + public suspend fun bulkWrite( + requests: List>, + options: BulkWriteOptions = BulkWriteOptions() + ): BulkWriteResult = wrapped.bulkWrite(requests, options).awaitSingle() + + /** + * Executes a mix of inserts, updates, replaces, and deletes. + * + * Note: Supports retryable writes on MongoDB server versions 3.6 or higher when the retryWrites setting is enabled. + * The eligibility for retryable write support for bulk operations is determined on the whole bulk write. If the + * `requests` contain any `UpdateManyModels` or `DeleteManyModels` then the bulk operation will not support + * retryable writes. + * + * @param clientSession the client session with which to associate this operation + * @param requests the writes to execute + * @param options the options to apply to the bulk write operation + * @return the result of the bulk write + * @throws com.mongodb.MongoBulkWriteException if there's an exception in the bulk write operation + * @throws com.mongodb.MongoException if there's an exception running the operation + */ + public suspend fun bulkWrite( + clientSession: ClientSession, + requests: List>, + options: BulkWriteOptions = BulkWriteOptions() + ): BulkWriteResult = wrapped.bulkWrite(clientSession.wrapped, requests, options).awaitSingle() + + /** + * Atomically find a document and remove it. + * + * Note: Supports retryable writes on MongoDB server versions 3.6 or higher when the retryWrites setting is enabled. + * + * @param filter the query filter to find the document with + * @param options the options to apply to the operation + * @return the document that was removed. If no documents matched the query filter, then null will be returned + */ + public suspend fun findOneAndDelete( + filter: Bson, + options: FindOneAndDeleteOptions = FindOneAndDeleteOptions() + ): T? = wrapped.findOneAndDelete(filter, options).awaitFirstOrNull() + + /** + * Atomically find a document and remove it. + * + * Note: Supports retryable writes on MongoDB server versions 3.6 or higher when the retryWrites setting is enabled. + * + * @param clientSession the client session with which to associate this operation + * @param filter the query filter to find the document with + * @param options the options to apply to the operation + * @return the document that was removed. If no documents matched the query filter, then null will be returned + */ + public suspend fun findOneAndDelete( + clientSession: ClientSession, + filter: Bson, + options: FindOneAndDeleteOptions = FindOneAndDeleteOptions() + ): T? = wrapped.findOneAndDelete(clientSession.wrapped, filter, options).awaitFirstOrNull() + + /** + * Atomically find a document and update it. + * + * Use this method to only update the corresponding fields in the document according to the update operators used in + * the update document. To replace the entire document with a new document, use the corresponding + * [findOneAndReplace] method. + * + * Note: Supports retryable writes on MongoDB server versions 3.6 or higher when the retryWrites setting is enabled. + * + * @param filter a document describing the query filter, which may not be null. + * @param update a document describing the update, which may not be null. The update to apply must include at least + * one update operator. + * @param options the options to apply to the operation + * @return the document that was updated. Depending on the value of the `returnOriginal` property, this will either + * be the document as it was before the update or as it is after the update. If no documents matched the query + * filter, then null will be returned + * @see [Update Command Behaviors](https://www.mongodb.com/docs/manual/reference/command/update/) + * @see com.mongodb.client.MongoCollection.findOneAndReplace + */ + public suspend fun findOneAndUpdate( + filter: Bson, + update: Bson, + options: FindOneAndUpdateOptions = FindOneAndUpdateOptions() + ): T? = wrapped.findOneAndUpdate(filter, update, options).awaitFirstOrNull() + + /** + * Atomically find a document and update it. + * + * Use this method to only update the corresponding fields in the document according to the update operators used in + * the update document. To replace the entire document with a new document, use the corresponding + * [findOneAndReplace] method. + * + * Note: Supports retryable writes on MongoDB server versions 3.6 or higher when the retryWrites setting is enabled. + * + * @param clientSession the client session with which to associate this operation + * @param filter a document describing the query filter, which may not be null. + * @param update a document describing the update, which may not be null. The update to apply must include at least + * one update operator. + * @param options the options to apply to the operation + * @return the document that was updated. Depending on the value of the `returnOriginal` property, this will either + * be the document as it was before the update or as it is after the update. If no documents matched the query + * filter, then null will be returned + * @see [Update Command Behaviors](https://www.mongodb.com/docs/manual/reference/command/update/) + * @see com.mongodb.client.MongoCollection.findOneAndReplace + */ + public suspend fun findOneAndUpdate( + clientSession: ClientSession, + filter: Bson, + update: Bson, + options: FindOneAndUpdateOptions = FindOneAndUpdateOptions() + ): T? = wrapped.findOneAndUpdate(clientSession.wrapped, filter, update, options).awaitFirstOrNull() + + /** + * Atomically find a document and update it. + * + * Note: Supports retryable writes on MongoDB server versions 3.6 or higher when the retryWrites setting is enabled. + * + * @param filter a document describing the query filter, which may not be null. + * @param update a pipeline describing the update, which may not be null. + * @param options the options to apply to the operation + * @return the document that was updated. Depending on the value of the `returnOriginal` property, this will either + * be the document as it was before the update or as it is after the update. If no documents matched the query + * filter, then null will be returned + */ + public suspend fun findOneAndUpdate( + filter: Bson, + update: List, + options: FindOneAndUpdateOptions = FindOneAndUpdateOptions() + ): T? = wrapped.findOneAndUpdate(filter, update, options).awaitFirstOrNull() + + /** + * Atomically find a document and update it. + * + * Note: Supports retryable writes on MongoDB server versions 3.6 or higher when the retryWrites setting is enabled. + * + * @param clientSession the client session with which to associate this operation + * @param filter a document describing the query filter, which may not be null. + * @param update a pipeline describing the update, which may not be null. + * @param options the options to apply to the operation + * @return the document that was updated. Depending on the value of the `returnOriginal` property, this will either + * be the document as it was before the update or as it is after the update. If no documents matched the query + * filter, then null will be returned + */ + public suspend fun findOneAndUpdate( + clientSession: ClientSession, + filter: Bson, + update: List, + options: FindOneAndUpdateOptions = FindOneAndUpdateOptions() + ): T? = wrapped.findOneAndUpdate(clientSession.wrapped, filter, update, options).awaitFirstOrNull() + + /** + * Atomically find a document and replace it. + * + * Use this method to replace a document using the specified replacement argument. To update the document with + * update operators, use the corresponding [findOneAndUpdate] method. + * + * Note: Supports retryable writes on MongoDB server versions 3.6 or higher when the retryWrites setting is enabled. + * + * @param filter the query filter to apply the replace operation + * @param replacement the replacement document + * @param options the options to apply to the operation + * @return the document that was replaced. Depending on the value of the `returnOriginal` property, this will either + * be the document as it was before the update or as it is after the update. If no documents matched the query + * filter, then null will be returned + * @see [Update Command Behaviors](https://www.mongodb.com/docs/manual/reference/command/update/) + */ + public suspend fun findOneAndReplace( + filter: Bson, + replacement: T, + options: FindOneAndReplaceOptions = FindOneAndReplaceOptions() + ): T? = wrapped.findOneAndReplace(filter, replacement, options).awaitFirstOrNull() + + /** + * Atomically find a document and replace it. + * + * Use this method to replace a document using the specified replacement argument. To update the document with + * update operators, use the corresponding [findOneAndUpdate] method. + * + * Note: Supports retryable writes on MongoDB server versions 3.6 or higher when the retryWrites setting is enabled. + * + * @param clientSession the client session with which to associate this operation + * @param filter the query filter to apply the replace operation + * @param replacement the replacement document + * @param options the options to apply to the operation + * @return the document that was replaced. Depending on the value of the `returnOriginal` property, this will either + * be the document as it was before the update or as it is after the update. If no documents matched the query + * filter, then null will be returned + * @see [Update Command Behaviors](https://www.mongodb.com/docs/manual/reference/command/update/) + */ + public suspend fun findOneAndReplace( + clientSession: ClientSession, + filter: Bson, + replacement: T, + options: FindOneAndReplaceOptions = FindOneAndReplaceOptions() + ): T? = wrapped.findOneAndReplace(clientSession.wrapped, filter, replacement, options).awaitFirstOrNull() + + /** + * Drops this collection from the Database. + * + * @param options various options for dropping the collection + * @see [Drop Collection](https://www.mongodb.com/docs/manual/reference/command/drop/) + */ + public suspend fun drop(options: DropCollectionOptions = DropCollectionOptions()) { + wrapped.drop(options).awaitFirstOrNull() + } + /** + * Drops this collection from the Database. + * + * @param clientSession the client session with which to associate this operation + * @param options various options for dropping the collection + * @see [Drop Collection](https://www.mongodb.com/docs/manual/reference/command/drop/) + */ + public suspend fun drop(clientSession: ClientSession, options: DropCollectionOptions = DropCollectionOptions()) { + wrapped.drop(clientSession.wrapped, options).awaitFirstOrNull() + } + + /** + * Create an index with the given keys and options. + * + * @param keys an object describing the index key(s), which may not be null. + * @param options the options for the index + * @return the index name + * @see [Create indexes](https://www.mongodb.com/docs/manual/reference/command/createIndexes/) + */ + public suspend fun createIndex(keys: Bson, options: IndexOptions = IndexOptions()): String = + wrapped.createIndex(keys, options).awaitSingle() + + /** + * Create an index with the given keys and options. + * + * @param clientSession the client session with which to associate this operation + * @param keys an object describing the index key(s), which may not be null. + * @param options the options for the index + * @return the index name + * @see [Create indexes](https://www.mongodb.com/docs/manual/reference/command/createIndexes/) + */ + public suspend fun createIndex( + clientSession: ClientSession, + keys: Bson, + options: IndexOptions = IndexOptions() + ): String = wrapped.createIndex(clientSession.wrapped, keys, options).awaitSingle() + + /** + * Create multiple indexes. + * + * @param indexes the list of indexes + * @param options options to use when creating indexes + * @return the list of index names + * @see [Create indexes](https://www.mongodb.com/docs/manual/reference/command/createIndexes/) + */ + public fun createIndexes( + indexes: List, + options: CreateIndexOptions = CreateIndexOptions() + ): Flow = wrapped.createIndexes(indexes, options).asFlow() + + /** + * Create multiple indexes. + * + * @param clientSession the client session with which to associate this operation + * @param indexes the list of indexes + * @param options: options to use when creating indexes + * @return the list of index names + * @see [Create indexes](https://www.mongodb.com/docs/manual/reference/command/createIndexes/) + */ + public fun createIndexes( + clientSession: ClientSession, + indexes: List, + options: CreateIndexOptions = CreateIndexOptions() + ): Flow = wrapped.createIndexes(clientSession.wrapped, indexes, options).asFlow() + + /** + * Get all the indexes in this collection. + * + * @return the list indexes iterable interface + * @see [List indexes](https://www.mongodb.com/docs/manual/reference/command/listIndexes/) + */ + @JvmName("listIndexesAsDocument") public fun listIndexes(): ListIndexesFlow = listIndexes() + + /** + * Get all the indexes in this collection. + * + * @param clientSession the client session with which to associate this operation + * @return the list indexes iterable interface + * @see [List indexes](https://www.mongodb.com/docs/manual/reference/command/listIndexes/) + */ + @JvmName("listIndexesAsDocumentWithSession") + public fun listIndexes(clientSession: ClientSession): ListIndexesFlow = + listIndexes(clientSession) + + /** + * Get all the indexes in this collection. + * + * @param R the class to decode each document into + * @param resultClass the target document type of the iterable. + * @return the list indexes iterable interface + * @see [List indexes](https://www.mongodb.com/docs/manual/reference/command/listIndexes/) + */ + public fun listIndexes(resultClass: Class): ListIndexesFlow = + ListIndexesFlow(wrapped.listIndexes(resultClass)) + + /** + * Get all the indexes in this collection. + * + * @param R the class to decode each document into + * @param clientSession the client session with which to associate this operation + * @param resultClass the target document type of the iterable. + * @return the list indexes iterable interface + * @see [List indexes](https://www.mongodb.com/docs/manual/reference/command/listIndexes/) + */ + public fun listIndexes(clientSession: ClientSession, resultClass: Class): ListIndexesFlow = + ListIndexesFlow(wrapped.listIndexes(clientSession.wrapped, resultClass)) + + /** + * Get all the indexes in this collection. + * + * @param R the class to decode each document into + * @return the list indexes iterable interface + * @see [List indexes](https://www.mongodb.com/docs/manual/reference/command/listIndexes/) + */ + public inline fun listIndexes(): ListIndexesFlow = listIndexes(R::class.java) + + /** + * Get all the indexes in this collection. + * + * @param R the class to decode each document into + * @param clientSession the client session with which to associate this operation + * @return the list indexes iterable interface + * @see [List indexes](https://www.mongodb.com/docs/manual/reference/command/listIndexes/) + */ + public inline fun listIndexes(clientSession: ClientSession): ListIndexesFlow = + listIndexes(clientSession, R::class.java) + + /** + * Drops the index given its name. + * + * @param indexName the name of the index to remove + * @param options the options to use when dropping indexes + * @see [Drop indexes](https://www.mongodb.com/docs/manual/reference/command/dropIndexes/) + */ + public suspend fun dropIndex(indexName: String, options: DropIndexOptions = DropIndexOptions()) { + wrapped.dropIndex(indexName, options).awaitFirstOrNull() + } + + /** + * Drops the index given the keys used to create it. + * + * @param keys the keys of the index to remove + * @param options the options to use when dropping indexes + * @see [Drop indexes](https://www.mongodb.com/docs/manual/reference/command/dropIndexes/) + */ + public suspend fun dropIndex(keys: Bson, options: DropIndexOptions = DropIndexOptions()) { + wrapped.dropIndex(keys, options).awaitFirstOrNull() + } + + /** + * Drops the index given its name. + * + * @param clientSession the client session with which to associate this operation + * @param indexName the name of the index to remove + * @param options the options to use when dropping indexes + * @see [Drop indexes](https://www.mongodb.com/docs/manual/reference/command/dropIndexes/) + */ + public suspend fun dropIndex( + clientSession: ClientSession, + indexName: String, + options: DropIndexOptions = DropIndexOptions() + ) { + wrapped.dropIndex(clientSession.wrapped, indexName, options).awaitFirstOrNull() + } + + /** + * Drops the index given the keys used to create it. + * + * @param clientSession the client session with which to associate this operation + * @param keys the keys of the index to remove + * @param options the options to use when dropping indexes + * @see [Drop indexes](https://www.mongodb.com/docs/manual/reference/command/dropIndexes/) + */ + public suspend fun dropIndex( + clientSession: ClientSession, + keys: Bson, + options: DropIndexOptions = DropIndexOptions() + ) { + wrapped.dropIndex(clientSession.wrapped, keys, options).awaitFirstOrNull() + } + + /** + * Drop all the indexes on this collection, except for the default on `_id`. + * + * @param options the options to use when dropping indexes + * @see [Drop indexes](https://www.mongodb.com/docs/manual/reference/command/dropIndexes/) + */ + public suspend fun dropIndexes(options: DropIndexOptions = DropIndexOptions()) { + wrapped.dropIndexes(options).awaitFirstOrNull() + } + + /** + * Drop all the indexes on this collection, except for the default on `_id`. + * + * @param clientSession the client session with which to associate this operation + * @param options the options to use when dropping indexes + * @see [Drop indexes](https://www.mongodb.com/docs/manual/reference/command/dropIndexes/) + */ + public suspend fun dropIndexes(clientSession: ClientSession, options: DropIndexOptions = DropIndexOptions()) { + wrapped.dropIndexes(clientSession.wrapped, options).awaitFirstOrNull() + } + + /** + * Rename the collection with oldCollectionName to the newCollectionName. + * + * @param newCollectionNamespace the name the collection will be renamed to + * @param options the options for renaming a collection + * @throws com.mongodb.MongoServerException if you provide a newCollectionName that is the name of an existing + * collection and dropTarget is false, or if the oldCollectionName is the name of a collection that doesn't exist + * @see [Rename collection](https://www.mongodb.com/docs/manual/reference/command/renameCollection/) + */ + public suspend fun renameCollection( + newCollectionNamespace: MongoNamespace, + options: RenameCollectionOptions = RenameCollectionOptions() + ) { + wrapped.renameCollection(newCollectionNamespace, options).awaitFirstOrNull() + } + + /** + * Rename the collection with oldCollectionName to the newCollectionName. + * + * @param clientSession the client session with which to associate this operation + * @param newCollectionNamespace the name the collection will be renamed to + * @param options the options for renaming a collection + * @throws com.mongodb.MongoServerException if you provide a newCollectionName that is the name of an existing + * collection and dropTarget is false, or if the oldCollectionName is the name of a collection that doesn't exist + * @see [Rename collection](https://www.mongodb.com/docs/manual/reference/command/renameCollection/) + * @since 3.6 + */ + public suspend fun renameCollection( + clientSession: ClientSession, + newCollectionNamespace: MongoNamespace, + options: RenameCollectionOptions = RenameCollectionOptions() + ) { + wrapped.renameCollection(clientSession.wrapped, newCollectionNamespace, options).awaitFirstOrNull() + } +} + +/** + * maxTime extension function + * + * @param maxTime time in milliseconds + * @return the options + */ +public fun CreateIndexOptions.maxTime(maxTime: Long): CreateIndexOptions = + this.apply { maxTime(maxTime, TimeUnit.MILLISECONDS) } +/** + * maxTime extension function + * + * @param maxTime time in milliseconds + * @return the options + */ +public fun CountOptions.maxTime(maxTime: Long): CountOptions = this.apply { maxTime(maxTime, TimeUnit.MILLISECONDS) } +/** + * maxTime extension function + * + * @param maxTime time in milliseconds + * @return the options + */ +public fun DropIndexOptions.maxTime(maxTime: Long): DropIndexOptions = + this.apply { maxTime(maxTime, TimeUnit.MILLISECONDS) } +/** + * maxTime extension function + * + * @param maxTime time in milliseconds + * @return the options + */ +public fun EstimatedDocumentCountOptions.maxTime(maxTime: Long): EstimatedDocumentCountOptions = + this.apply { maxTime(maxTime, TimeUnit.MILLISECONDS) } +/** + * maxTime extension function + * + * @param maxTime time in milliseconds + * @return the options + */ +public fun FindOneAndDeleteOptions.maxTime(maxTime: Long): FindOneAndDeleteOptions = + this.apply { maxTime(maxTime, TimeUnit.MILLISECONDS) } +/** + * maxTime extension function + * + * @param maxTime time in milliseconds + * @return the options + */ +public fun FindOneAndReplaceOptions.maxTime(maxTime: Long): FindOneAndReplaceOptions = + this.apply { maxTime(maxTime, TimeUnit.MILLISECONDS) } +/** + * maxTime extension function + * + * @param maxTime time in milliseconds + * @return the options + */ +public fun FindOneAndUpdateOptions.maxTime(maxTime: Long): FindOneAndUpdateOptions = + this.apply { maxTime(maxTime, TimeUnit.MILLISECONDS) } +/** + * expireAfter extension function + * + * @param expireAfter time in seconds + * @return the options + */ +public fun IndexOptions.expireAfter(expireAfter: Long): IndexOptions = + this.apply { expireAfter(expireAfter, TimeUnit.SECONDS) } diff --git a/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/MongoDatabase.kt b/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/MongoDatabase.kt new file mode 100644 index 00000000000..974533be7f5 --- /dev/null +++ b/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/MongoDatabase.kt @@ -0,0 +1,548 @@ +/* + * 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.ReadConcern +import com.mongodb.ReadPreference +import com.mongodb.WriteConcern +import com.mongodb.client.model.CreateCollectionOptions +import com.mongodb.client.model.CreateViewOptions +import com.mongodb.reactivestreams.client.MongoDatabase as JMongoDatabase +import java.util.concurrent.TimeUnit +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.reactive.asFlow +import kotlinx.coroutines.reactive.awaitFirstOrNull +import kotlinx.coroutines.reactive.awaitSingle +import org.bson.Document +import org.bson.codecs.configuration.CodecRegistry +import org.bson.conversions.Bson + +/** The MongoDatabase representation. */ +public class MongoDatabase(private val wrapped: JMongoDatabase) { + + /** The name of the database. */ + public val name: String + get() = wrapped.name + + /** The codec registry for the database. */ + public val codecRegistry: CodecRegistry + get() = wrapped.codecRegistry + + /** The read preference for the database. */ + public val readPreference: ReadPreference + get() = wrapped.readPreference + + /** + * The read concern for the database. + * + * @see [Read Concern](https://www.mongodb.com/docs/manual/reference/readConcern/) + */ + public val readConcern: ReadConcern + get() = wrapped.readConcern + + /** The write concern for the database. */ + public val writeConcern: WriteConcern + get() = wrapped.writeConcern + + /** + * Create a new MongoDatabase instance with a different codec registry. + * + * The [CodecRegistry] configured by this method is effectively treated by the driver as an instance of + * [org.bson.codecs.configuration.CodecProvider], which [CodecRegistry] extends. So there is no benefit to defining + * a class that implements [CodecRegistry]. Rather, an application should always create [CodecRegistry] instances + * using the factory methods in [org.bson.codecs.configuration.CodecRegistries]. + * + * @param newCodecRegistry the new [org.bson.codecs.configuration.CodecRegistry] for the database + * @return a new MongoDatabase instance with the different codec registry + * @see org.bson.codecs.configuration.CodecRegistries + */ + public fun withCodecRegistry(newCodecRegistry: CodecRegistry): MongoDatabase = + MongoDatabase(wrapped.withCodecRegistry(newCodecRegistry)) + + /** + * Create a new MongoDatabase instance with a different read preference. + * + * @param newReadPreference the new [ReadPreference] for the database + * @return a new MongoDatabase instance with the different readPreference + */ + public fun withReadPreference(newReadPreference: ReadPreference): MongoDatabase = + MongoDatabase(wrapped.withReadPreference(newReadPreference)) + + /** + * Create a new MongoDatabase instance with a different read concern. + * + * @param newReadConcern the new [ReadConcern] for the database + * @return a new MongoDatabase instance with the different ReadConcern + * @see [Read Concern](https://www.mongodb.com/docs/manual/reference/readConcern/) + */ + public fun withReadConcern(newReadConcern: ReadConcern): MongoDatabase = + MongoDatabase(wrapped.withReadConcern(newReadConcern)) + + /** + * Create a new MongoDatabase instance with a different write concern. + * + * @param newWriteConcern the new [WriteConcern] for the database + * @return a new MongoDatabase instance with the different writeConcern + */ + public fun withWriteConcern(newWriteConcern: WriteConcern): MongoDatabase = + MongoDatabase(wrapped.withWriteConcern(newWriteConcern)) + + /** + * Gets a collection. + * + * @param T the default class to covert documents returned from the collection into. + * @param collectionName the name of the collection to return + * @param resultClass the target document type for the collection + * @return the collection + */ + public fun getCollection(collectionName: String, resultClass: Class): MongoCollection = + MongoCollection(wrapped.getCollection(collectionName, resultClass)) + + /** + * Gets a collection. + * + * @param T the default class to covert documents returned from the collection into. + * @param collectionName the name of the collection to return + * @return the collection + */ + public inline fun getCollection(collectionName: String): MongoCollection = + getCollection(collectionName, T::class.java) + + /** + * Executes the given command in the context of the current database with the given read preference. + * + * @param command the command to be run + * @param readPreference the [ReadPreference] to be used when executing the command, defaults to + * [MongoDatabase.readPreference] + * @return the command result + */ + @JvmName("runCommandDocument") + public suspend fun runCommand(command: Bson, readPreference: ReadPreference = this.readPreference): Document = + runCommand(command, readPreference) + + /** + * Executes the given command in the context of the current database with the given read preference. + * + * @param clientSession the client session with which to associate this operation + * @param command the command to be run + * @param readPreference the [ReadPreference] to be used when executing the command, defaults to + * [MongoDatabase.readPreference] + * @return the command result + */ + @JvmName("runCommandDocumentWithSession") + public suspend fun runCommand( + clientSession: ClientSession, + command: Bson, + readPreference: ReadPreference = this.readPreference + ): Document = runCommand(clientSession, command, readPreference) + + /** + * Executes the given command in the context of the current database with the given read preference. + * + * @param T the class to decode each document into + * @param command the command to be run + * @param readPreference the [ReadPreference] to be used when executing the command, defaults to + * [MongoDatabase.readPreference] + * @param resultClass the target document class + * @return the command result + */ + public suspend fun runCommand( + command: Bson, + readPreference: ReadPreference = this.readPreference, + resultClass: Class + ): T = wrapped.runCommand(command, readPreference, resultClass).awaitSingle() + + /** + * Executes the given command in the context of the current database with the given read preference. + * + * @param T the class to decode each document into + * @param clientSession the client session with which to associate this operation + * @param command the command to be run + * @param readPreference the [ReadPreference] to be used when executing the command, defaults to + * [MongoDatabase.readPreference] + * @param resultClass the target document class + * @return the command result + */ + public suspend fun runCommand( + clientSession: ClientSession, + command: Bson, + readPreference: ReadPreference = this.readPreference, + resultClass: Class + ): T = wrapped.runCommand(clientSession.wrapped, command, readPreference, resultClass).awaitSingle() + + /** + * Executes the given command in the context of the current database with the given read preference. + * + * @param T the class to decode each document into + * @param command the command to be run + * @param readPreference the [ReadPreference] to be used when executing the command, defaults to + * [MongoDatabase.readPreference] + * @return the command result + */ + public suspend inline fun runCommand( + command: Bson, + readPreference: ReadPreference = this.readPreference + ): T = runCommand(command, readPreference, T::class.java) + + /** + * Executes the given command in the context of the current database with the given read preference. + * + * @param T the class to decode each document into + * @param clientSession the client session with which to associate this operation + * @param command the command to be run + * @param readPreference the [ReadPreference] to be used when executing the command, defaults to + * [MongoDatabase.readPreference] + * @return the command result + */ + public suspend inline fun runCommand( + clientSession: ClientSession, + command: Bson, + readPreference: ReadPreference = this.readPreference + ): T = runCommand(clientSession, command, readPreference, T::class.java) + + /** + * Drops this database. + * + * @see [Drop database](https://www.mongodb.com/docs/manual/reference/command/dropDatabase/#dbcmd.dropDatabase) + */ + public suspend fun drop() { + wrapped.drop().awaitFirstOrNull() + } + + /** + * Drops this database. + * + * @param clientSession the client session with which to associate this operation + * @see [Drop database](https://www.mongodb.com/docs/manual/reference/command/dropDatabase/#dbcmd.dropDatabase) + */ + public suspend fun drop(clientSession: ClientSession) { + wrapped.drop(clientSession.wrapped).awaitFirstOrNull() + } + + /** + * Gets the names of all the collections in this database. + * + * @return an iterable containing all the names of all the collections in this database + */ + public fun listCollectionNames(): Flow = wrapped.listCollectionNames().asFlow() + + /** + * Gets the names of all the collections in this database. + * + * @param clientSession the client session with which to associate this operation + * @return an iterable containing all the names of all the collections in this database + */ + public fun listCollectionNames(clientSession: ClientSession): Flow = + wrapped.listCollectionNames(clientSession.wrapped).asFlow() + + /** + * Gets all the collections in this database. + * + * @return the list collections iterable interface + * @see [listCollections](https://www.mongodb.com/docs/manual/reference/command/listCollections) + */ + @JvmName("listCollectionsAsDocument") + public fun listCollections(): ListCollectionsFlow = listCollections() + + /** + * Gets all the collections in this database. + * + * @param clientSession the client session with which to associate this operation + * @return the list collections iterable interface + * @see [listCollections](https://www.mongodb.com/docs/manual/reference/command/listCollections) + */ + @JvmName("listCollectionsAsDocumentWithSession") + public fun listCollections(clientSession: ClientSession): ListCollectionsFlow = + listCollections(clientSession) + + /** + * Gets all the collections in this database. + * + * @param T the type of the class to use + * @param resultClass the target document type of the iterable. + * @return the list collections iterable interface + * @see [listCollections](https://www.mongodb.com/docs/manual/reference/command/listCollections) + */ + public fun listCollections(resultClass: Class): ListCollectionsFlow = + ListCollectionsFlow(wrapped.listCollections(resultClass)) + + /** + * Gets all the collections in this database. + * + * @param T the type of the class to use + * @param clientSession the client session with which to associate this operation + * @param resultClass the target document type of the iterable. + * @return the list collections iterable interface + * @see [listCollections](https://www.mongodb.com/docs/manual/reference/command/listCollections) + */ + public fun listCollections(clientSession: ClientSession, resultClass: Class): ListCollectionsFlow = + ListCollectionsFlow(wrapped.listCollections(clientSession.wrapped, resultClass)) + + /** + * Gets all the collections in this database. + * + * @param T the type of the class to use + * @return the list collections iterable interface + * @see [listCollections](https://www.mongodb.com/docs/manual/reference/command/listCollections) + */ + public inline fun listCollections(): ListCollectionsFlow = listCollections(T::class.java) + + /** + * Gets all the collections in this database. + * + * @param clientSession the client session with which to associate this operation + * @param T the type of the class to use + * @return the list collections iterable interface + * @see [listCollections](https://www.mongodb.com/docs/manual/reference/command/listCollections) + */ + public inline fun listCollections(clientSession: ClientSession): ListCollectionsFlow = + listCollections(clientSession, T::class.java) + + /** + * Create a new collection with the selected options + * + * @param collectionName the name for the new collection to create + * @param createCollectionOptions various options for creating the collection + * @see [Create Command](https://www.mongodb.com/docs/manual/reference/command/create) + */ + public suspend fun createCollection( + collectionName: String, + createCollectionOptions: CreateCollectionOptions = CreateCollectionOptions() + ) { + wrapped.createCollection(collectionName, createCollectionOptions).awaitFirstOrNull() + } + + /** + * Create a new collection with the selected options + * + * @param clientSession the client session with which to associate this operation + * @param collectionName the name for the new collection to create + * @param createCollectionOptions various options for creating the collection + * @see [Create Command](https://www.mongodb.com/docs/manual/reference/command/create) + */ + public suspend fun createCollection( + clientSession: ClientSession, + collectionName: String, + createCollectionOptions: CreateCollectionOptions = CreateCollectionOptions() + ) { + wrapped.createCollection(clientSession.wrapped, collectionName, createCollectionOptions).awaitFirstOrNull() + } + + /** + * Creates a view with the given name, backing collection/view name, aggregation pipeline, and options that defines + * the view. + * + * @param viewName the name of the view to create + * @param viewOn the backing collection/view for the view + * @param pipeline the pipeline that defines the view + * @param createViewOptions various options for creating the view + * @see [Create Command](https://www.mongodb.com/docs/manual/reference/command/create) + */ + public suspend fun createView( + viewName: String, + viewOn: String, + pipeline: List, + createViewOptions: CreateViewOptions = CreateViewOptions() + ) { + wrapped.createView(viewName, viewOn, pipeline, createViewOptions).awaitFirstOrNull() + } + + /** + * Creates a view with the given name, backing collection/view name, aggregation pipeline, and options that defines + * the view. + * + * @param clientSession the client session with which to associate this operation + * @param viewName the name of the view to create + * @param viewOn the backing collection/view for the view + * @param pipeline the pipeline that defines the view + * @param createViewOptions various options for creating the view + * @see [Create Command](https://www.mongodb.com/docs/manual/reference/command/create) + */ + public suspend fun createView( + clientSession: ClientSession, + viewName: String, + viewOn: String, + pipeline: List, + createViewOptions: CreateViewOptions = CreateViewOptions() + ) { + wrapped.createView(clientSession.wrapped, viewName, viewOn, pipeline, createViewOptions).awaitFirstOrNull() + } + + /** + * Runs an aggregation framework pipeline on the database for pipeline stages that do not require an underlying + * collection, such as `$currentOp` and `$listLocalSessions`. + * + * @param pipeline the aggregation pipeline + * @return an iterable containing the result of the aggregation operation + * @see [Aggregate Command](https://www.mongodb.com/docs/manual/reference/command/aggregate/#dbcmd.aggregate) + */ + @JvmName("aggregateAsDocument") + public fun aggregate(pipeline: List): AggregateFlow = aggregate(pipeline) + + /** + * Runs an aggregation framework pipeline on the database for pipeline stages that do not require an underlying + * collection, such as `$currentOp` and `$listLocalSessions`. + * + * @param clientSession the client session with which to associate this operation + * @param pipeline the aggregation pipeline + * @return an iterable containing the result of the aggregation operation + * @see [Aggregate Command](https://www.mongodb.com/docs/manual/reference/command/aggregate/#dbcmd.aggregate) + */ + @JvmName("aggregateAsDocumentWithSession") + public fun aggregate(clientSession: ClientSession, pipeline: List): AggregateFlow = + aggregate(clientSession, pipeline) + + /** + * Runs an aggregation framework pipeline on the database for pipeline stages that do not require an underlying + * collection, such as `$currentOp` and `$listLocalSessions`. + * + * @param T the class to decode each document into + * @param pipeline the aggregation pipeline + * @param resultClass the target document type of the iterable. + * @return an iterable containing the result of the aggregation operation + * @see [Aggregate Command](https://www.mongodb.com/docs/manual/reference/command/aggregate/#dbcmd.aggregate) + */ + public fun aggregate(pipeline: List, resultClass: Class): AggregateFlow = + AggregateFlow(wrapped.aggregate(pipeline, resultClass)) + + /** + * Runs an aggregation framework pipeline on the database for pipeline stages that do not require an underlying + * collection, such as `$currentOp` and `$listLocalSessions`. + * + * @param T the class to decode each document into + * @param clientSession the client session with which to associate this operation + * @param pipeline the aggregation pipeline + * @param resultClass the target document type of the iterable. + * @return an iterable containing the result of the aggregation operation + * @see [Aggregate Command](https://www.mongodb.com/docs/manual/reference/command/aggregate/#dbcmd.aggregate) + */ + public fun aggregate( + clientSession: ClientSession, + pipeline: List, + resultClass: Class + ): AggregateFlow = AggregateFlow(wrapped.aggregate(clientSession.wrapped, pipeline, resultClass)) + + /** + * Runs an aggregation framework pipeline on the database for pipeline stages that do not require an underlying + * collection, such as `$currentOp` and `$listLocalSessions`. + * + * @param T the class to decode each document into + * @param pipeline the aggregation pipeline + * @return an iterable containing the result of the aggregation operation + * @see [Aggregate Command](https://www.mongodb.com/docs/manual/reference/command/aggregate/#dbcmd.aggregate) + */ + public inline fun aggregate(pipeline: List): AggregateFlow = + aggregate(pipeline, T::class.java) + + /** + * Runs an aggregation framework pipeline on the database for pipeline stages that do not require an underlying + * collection, such as `$currentOp` and `$listLocalSessions`. + * + * @param T the class to decode each document into + * @param clientSession the client session with which to associate this operation + * @param pipeline the aggregation pipeline + * @return an iterable containing the result of the aggregation operation + * @see [Aggregate Command](https://www.mongodb.com/docs/manual/reference/command/aggregate/#dbcmd.aggregate) + */ + public inline fun aggregate( + clientSession: ClientSession, + pipeline: List + ): AggregateFlow = aggregate(clientSession, pipeline, T::class.java) + + /** + * Creates a change stream for this database. + * + * @param pipeline the aggregation pipeline to apply to the change stream, defaults to an empty pipeline. + * @return the change stream iterable + * @see [Change Streams](https://dochub.mongodb.org/changestreams] + */ + @JvmName("watchAsDocument") + public fun watch(pipeline: List = emptyList()): ChangeStreamFlow = watch(pipeline) + + /** + * Creates a change stream for this database. + * + * @param clientSession the client session with which to associate this operation + * @param pipeline the aggregation pipeline to apply to the change stream, defaults to an empty pipeline. + * @return the change stream iterable + * @see [Change Streams](https://dochub.mongodb.org/changestreams] + */ + @JvmName("watchAsDocumentWithSession") + public fun watch(clientSession: ClientSession, pipeline: List = emptyList()): ChangeStreamFlow = + watch(clientSession, pipeline) + + /** + * Creates a change stream for this database. + * + * @param T the target document type of the iterable. + * @param pipeline the aggregation pipeline to apply to the change stream, defaults to an empty pipeline. + * @param resultClass the target document type of the iterable. + * @return the change stream iterable + * @see [Change Streams](https://dochub.mongodb.org/changestreams] + */ + public fun watch(pipeline: List = emptyList(), resultClass: Class): ChangeStreamFlow = + ChangeStreamFlow(wrapped.watch(pipeline, resultClass)) + + /** + * Creates a change stream for this database. + * + * @param T the target document type of the iterable. + * @param clientSession the client session with which to associate this operation + * @param pipeline the aggregation pipeline to apply to the change stream, defaults to an empty pipeline. + * @param resultClass the target document type of the iterable. + * @return the change stream iterable + * @see [Change Streams](https://dochub.mongodb.org/changestreams] + */ + public fun watch( + clientSession: ClientSession, + pipeline: List = emptyList(), + resultClass: Class + ): ChangeStreamFlow = ChangeStreamFlow(wrapped.watch(clientSession.wrapped, pipeline, resultClass)) + + /** + * Creates a change stream for this database. + * + * @param T the target document type of the iterable. + * @param pipeline the aggregation pipeline to apply to the change stream, defaults to an empty pipeline. + * @return the change stream iterable + * @see [Change Streams](https://dochub.mongodb.org/changestreams] + */ + public inline fun watch(pipeline: List = emptyList()): ChangeStreamFlow = + watch(pipeline, T::class.java) + + /** + * Creates a change stream for this database. + * + * @param T the target document type of the iterable. + * @param clientSession the client session with which to associate this operation + * @param pipeline the aggregation pipeline to apply to the change stream, defaults to an empty pipeline. + * @return the change stream iterable + * @see [Change Streams](https://dochub.mongodb.org/changestreams] + */ + public inline fun watch( + clientSession: ClientSession, + pipeline: List = emptyList() + ): ChangeStreamFlow = watch(clientSession, pipeline, T::class.java) +} + +/** + * expireAfter extension function + * + * @param maxTime time in seconds + * @return the options + */ +public fun CreateCollectionOptions.expireAfter(maxTime: Long): CreateCollectionOptions = + this.apply { expireAfter(maxTime, TimeUnit.SECONDS) } diff --git a/driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/AggregateFlowTest.kt b/driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/AggregateFlowTest.kt new file mode 100644 index 00000000000..cf8ebaa02cf --- /dev/null +++ b/driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/AggregateFlowTest.kt @@ -0,0 +1,111 @@ +/* + * 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.ExplainVerbosity +import com.mongodb.client.model.Collation +import com.mongodb.reactivestreams.client.AggregatePublisher +import java.util.concurrent.TimeUnit +import kotlin.reflect.full.declaredFunctions +import kotlin.test.assertEquals +import kotlinx.coroutines.runBlocking +import org.bson.BsonDocument +import org.bson.BsonString +import org.bson.Document +import org.junit.jupiter.api.Test +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoMoreInteractions +import org.mockito.kotlin.whenever +import reactor.core.publisher.Mono + +class AggregateFlowTest { + + @Test + fun shouldHaveTheSameMethods() { + val jAggregatePublisherFunctions = AggregatePublisher::class.declaredFunctions.map { it.name }.toSet() - "first" + val kAggregateFlowFunctions = AggregateFlow::class.declaredFunctions.map { it.name }.toSet() - "collect" + + assertEquals(jAggregatePublisherFunctions, kAggregateFlowFunctions) + } + + @Test + fun shouldCallTheUnderlyingMethods() { + val wrapped: AggregatePublisher = mock() + val flow = AggregateFlow(wrapped) + + val batchSize = 10 + val bson = BsonDocument() + val bsonComment = BsonString("a comment") + val collation = Collation.builder().locale("en").build() + val comment = "comment" + val hint = Document("h", 1) + val hintString = "hintString" + val verbosity = ExplainVerbosity.QUERY_PLANNER + + flow.allowDiskUse(true) + flow.batchSize(batchSize) + flow.bypassDocumentValidation(true) + flow.collation(collation) + flow.comment(bsonComment) + flow.comment(comment) + flow.hint(hint) + flow.hintString(hintString) + flow.let(bson) + flow.maxAwaitTime(1) + flow.maxAwaitTime(1, TimeUnit.SECONDS) + flow.maxTime(1) + flow.maxTime(1, TimeUnit.SECONDS) + + verify(wrapped).allowDiskUse(true) + verify(wrapped).batchSize(batchSize) + verify(wrapped).bypassDocumentValidation(true) + verify(wrapped).collation(collation) + verify(wrapped).comment(bsonComment) + verify(wrapped).comment(comment) + verify(wrapped).hint(hint) + verify(wrapped).hintString(hintString) + verify(wrapped).maxAwaitTime(1, TimeUnit.MILLISECONDS) + verify(wrapped).maxAwaitTime(1, TimeUnit.SECONDS) + verify(wrapped).maxTime(1, TimeUnit.MILLISECONDS) + verify(wrapped).maxTime(1, TimeUnit.SECONDS) + verify(wrapped).let(bson) + + whenever(wrapped.explain(Document::class.java)).doReturn(Mono.fromCallable { Document() }) + whenever(wrapped.explain(Document::class.java, verbosity)).doReturn(Mono.fromCallable { Document() }) + whenever(wrapped.explain(BsonDocument::class.java, verbosity)).doReturn(Mono.fromCallable { BsonDocument() }) + whenever(wrapped.toCollection()).doReturn(Mono.empty()) + + runBlocking { + flow.explain() + flow.explain(verbosity) + flow.explain(Document::class.java) + flow.explain(BsonDocument::class.java, verbosity) + flow.explain() + flow.explain(verbosity) + flow.toCollection() + } + + verify(wrapped, times(3)).explain(Document::class.java) + verify(wrapped, times(1)).explain(Document::class.java, verbosity) + verify(wrapped, times(2)).explain(BsonDocument::class.java, verbosity) + verify(wrapped).toCollection() + + verifyNoMoreInteractions(wrapped) + } +} diff --git a/driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/ChangeStreamFlowTest.kt b/driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/ChangeStreamFlowTest.kt new file mode 100644 index 00000000000..47030468588 --- /dev/null +++ b/driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/ChangeStreamFlowTest.kt @@ -0,0 +1,92 @@ +/* + * 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.model.Collation +import com.mongodb.client.model.changestream.FullDocument +import com.mongodb.client.model.changestream.FullDocumentBeforeChange +import com.mongodb.reactivestreams.client.ChangeStreamPublisher +import java.util.concurrent.TimeUnit +import kotlin.reflect.full.declaredFunctions +import kotlin.test.assertEquals +import kotlinx.coroutines.runBlocking +import org.bson.BsonDocument +import org.bson.BsonString +import org.bson.BsonTimestamp +import org.bson.Document +import org.junit.jupiter.api.Test +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoMoreInteractions +import org.mockito.kotlin.whenever + +class ChangeStreamFlowTest { + + @Test + fun shouldHaveTheSameMethods() { + val jChangeStreamPublisherFunctions = + ChangeStreamPublisher::class.declaredFunctions.map { it.name }.toSet() - "first" + val kChangeStreamFlowFunctions = ChangeStreamFlow::class.declaredFunctions.map { it.name }.toSet() - "collect" + + assertEquals(jChangeStreamPublisherFunctions, kChangeStreamFlowFunctions) + } + + @Test + fun shouldCallTheUnderlyingMethods() { + val wrapped: ChangeStreamPublisher = mock() + val flow = ChangeStreamFlow(wrapped) + + val batchSize = 10 + val bsonComment = BsonString("a comment") + val collation = Collation.builder().locale("en").build() + val comment = "comment" + val operationTime = BsonTimestamp(1) + val resumeToken = BsonDocument() + + flow.batchSize(batchSize) + flow.collation(collation) + flow.comment(comment) + flow.comment(bsonComment) + flow.fullDocument(FullDocument.UPDATE_LOOKUP) + flow.fullDocumentBeforeChange(FullDocumentBeforeChange.REQUIRED) + flow.maxAwaitTime(1) + flow.maxAwaitTime(1, TimeUnit.SECONDS) + flow.resumeAfter(resumeToken) + flow.showExpandedEvents(true) + flow.startAfter(resumeToken) + flow.startAtOperationTime(operationTime) + + verify(wrapped).batchSize(batchSize) + verify(wrapped).collation(collation) + verify(wrapped).comment(comment) + verify(wrapped).comment(bsonComment) + verify(wrapped).fullDocument(FullDocument.UPDATE_LOOKUP) + verify(wrapped).fullDocumentBeforeChange(FullDocumentBeforeChange.REQUIRED) + verify(wrapped).maxAwaitTime(1, TimeUnit.MILLISECONDS) + verify(wrapped).maxAwaitTime(1, TimeUnit.SECONDS) + verify(wrapped).resumeAfter(resumeToken) + verify(wrapped).showExpandedEvents(true) + verify(wrapped).startAfter(resumeToken) + verify(wrapped).startAtOperationTime(operationTime) + + whenever(wrapped.withDocumentClass(BsonDocument::class.java)).doReturn(mock()) + runBlocking { flow.withDocumentClass() } + verify(wrapped).withDocumentClass(BsonDocument::class.java) + + verifyNoMoreInteractions(wrapped) + } +} diff --git a/driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/ClientSessionTest.kt b/driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/ClientSessionTest.kt new file mode 100644 index 00000000000..1c0ab88a744 --- /dev/null +++ b/driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/ClientSessionTest.kt @@ -0,0 +1,80 @@ +/* + * 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.ClientSessionOptions +import com.mongodb.TransactionOptions +import com.mongodb.reactivestreams.client.ClientSession as JClientSession +import kotlin.reflect.full.functions +import kotlin.test.assertEquals +import kotlinx.coroutines.runBlocking +import org.junit.jupiter.api.Test +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoMoreInteractions +import org.mockito.kotlin.whenever +import reactor.core.publisher.Mono + +class ClientSessionTest { + + @Test + fun shouldHaveTheSameMethods() { + val jClientSessionFunctions = JClientSession::class.functions.map { it.name }.toSet() + val kClientSessionFunctions = ClientSession::class.functions.map { it.name }.toSet() + + assertEquals(jClientSessionFunctions, kClientSessionFunctions) + } + + @Test + fun shouldCallTheUnderlyingMethods() { + val wrapped: JClientSession = mock() + val session = ClientSession(wrapped) + + val transactionOptions = TransactionOptions.builder().maxCommitTime(10).build() + + whenever(wrapped.options).doReturn(ClientSessionOptions.builder().build()) + whenever(wrapped.serverSession).doReturn(mock()) + whenever(wrapped.isCausallyConsistent).doReturn(true) + whenever(wrapped.transactionOptions).doReturn(transactionOptions) + + session.options + session.serverSession + session.isCausallyConsistent + session.startTransaction() + session.startTransaction(transactionOptions) + session.getTransactionOptions() + + verify(wrapped).options + verify(wrapped).serverSession + verify(wrapped).isCausallyConsistent + verify(wrapped).startTransaction() + verify(wrapped).startTransaction(transactionOptions) + verify(wrapped).transactionOptions + + whenever(wrapped.abortTransaction()).doReturn(Mono.empty()) + whenever(wrapped.commitTransaction()).doReturn(Mono.empty()) + + runBlocking { + session.abortTransaction() + session.commitTransaction() + } + + verify(wrapped).abortTransaction() + verify(wrapped).commitTransaction() + verifyNoMoreInteractions(wrapped) + } +} diff --git a/driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/DistinctFlowTest.kt b/driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/DistinctFlowTest.kt new file mode 100644 index 00000000000..fa3b25f92dd --- /dev/null +++ b/driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/DistinctFlowTest.kt @@ -0,0 +1,69 @@ +/* + * 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.model.Collation +import com.mongodb.reactivestreams.client.DistinctPublisher +import java.util.concurrent.TimeUnit +import kotlin.reflect.full.declaredFunctions +import kotlin.test.assertEquals +import org.bson.BsonDocument +import org.bson.BsonString +import org.bson.Document +import org.junit.jupiter.api.Test +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoMoreInteractions + +class DistinctFlowTest { + @Test + fun shouldHaveTheSameMethods() { + val jDistinctPublisherFunctions = DistinctPublisher::class.declaredFunctions.map { it.name }.toSet() - "first" + val kDistinctFlowFunctions = DistinctFlow::class.declaredFunctions.map { it.name }.toSet() - "collect" + + assertEquals(jDistinctPublisherFunctions, kDistinctFlowFunctions) + } + + @Test + fun shouldCallTheUnderlyingMethods() { + val wrapped: DistinctPublisher = mock() + val flow = DistinctFlow(wrapped) + + val batchSize = 10 + val bsonComment = BsonString("a comment") + val collation = Collation.builder().locale("en").build() + val comment = "comment" + val filter = BsonDocument() + + flow.batchSize(batchSize) + flow.collation(collation) + flow.comment(bsonComment) + flow.comment(comment) + flow.filter(filter) + flow.maxTime(1) + flow.maxTime(1, TimeUnit.SECONDS) + + verify(wrapped).batchSize(batchSize) + verify(wrapped).collation(collation) + verify(wrapped).comment(bsonComment) + verify(wrapped).comment(comment) + verify(wrapped).filter(filter) + verify(wrapped).maxTime(1, TimeUnit.MILLISECONDS) + verify(wrapped).maxTime(1, TimeUnit.SECONDS) + + verifyNoMoreInteractions(wrapped) + } +} diff --git a/driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/ExtensionMethodsTest.kt b/driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/ExtensionMethodsTest.kt new file mode 100644 index 00000000000..39780fb8d6c --- /dev/null +++ b/driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/ExtensionMethodsTest.kt @@ -0,0 +1,56 @@ +/* + * 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 io.github.classgraph.ClassGraph +import kotlin.test.assertEquals +import org.junit.jupiter.api.Test + +class ExtensionMethodsTest { + + @Test + fun shouldHaveTimeUnitExtensionsMethodsForOptionsClasses() { + + val extensionsAddedForClasses = + setOf( + "CountOptions", + "CreateCollectionOptions", + "CreateIndexOptions", + "DropIndexOptions", + "EstimatedDocumentCountOptions", + "FindOneAndDeleteOptions", + "FindOneAndReplaceOptions", + "FindOneAndUpdateOptions", + "IndexOptions", + "TransactionOptions") + + ClassGraph().enableClassInfo().enableMethodInfo().acceptPackages("com.mongodb").scan().use { scanResult -> + val optionsClassesWithTimeUnit = + scanResult.allClasses + .filter { !it.packageName.contains("internal") } + .filter { it.simpleName.endsWith("Options") } + .filter { + it.methodInfo.any { m -> + m.parameterInfo.any { p -> p.typeDescriptor.toStringWithSimpleNames().equals("TimeUnit") } + } + } + .map { c -> c.simpleName } + .toSet() + + assertEquals(extensionsAddedForClasses, optionsClassesWithTimeUnit) + } + } +} diff --git a/driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/FindFlowTest.kt b/driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/FindFlowTest.kt new file mode 100644 index 00000000000..d86b0daef99 --- /dev/null +++ b/driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/FindFlowTest.kt @@ -0,0 +1,134 @@ +/* + * 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.CursorType +import com.mongodb.ExplainVerbosity +import com.mongodb.client.model.Collation +import com.mongodb.reactivestreams.client.FindPublisher +import java.util.concurrent.TimeUnit +import kotlin.reflect.full.declaredFunctions +import kotlin.test.assertEquals +import kotlinx.coroutines.runBlocking +import org.bson.BsonDocument +import org.bson.BsonString +import org.bson.Document +import org.junit.jupiter.api.Test +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoMoreInteractions +import org.mockito.kotlin.whenever +import reactor.core.publisher.Mono + +class FindFlowTest { + @Test + fun shouldHaveTheSameMethods() { + val jFindPublisherFunctions = FindPublisher::class.declaredFunctions.map { it.name }.toSet() - "first" + val kFindFlowFunctions = FindFlow::class.declaredFunctions.map { it.name }.toSet() - "collect" + + assertEquals(jFindPublisherFunctions, kFindFlowFunctions) + } + + @Suppress("DEPRECATION") + @Test + fun shouldCallTheUnderlyingMethods() { + val wrapped: FindPublisher = mock() + val flow = FindFlow(wrapped) + + val batchSize = 10 + val bson = BsonDocument() + val bsonComment = BsonString("a comment") + val collation = Collation.builder().locale("en").build() + val comment = "comment" + val filter = BsonDocument() + val hint = Document("h", 1) + val hintString = "hintString" + val verbosity = ExplainVerbosity.QUERY_PLANNER + + flow.allowDiskUse(true) + flow.batchSize(batchSize) + flow.collation(collation) + flow.comment(bsonComment) + flow.comment(comment) + flow.cursorType(CursorType.NonTailable) + flow.filter(filter) + flow.hint(hint) + flow.hintString(hintString) + flow.let(bson) + flow.limit(1) + flow.max(bson) + flow.maxAwaitTime(1) + flow.maxAwaitTime(1, TimeUnit.SECONDS) + flow.maxTime(1) + flow.maxTime(1, TimeUnit.SECONDS) + flow.min(bson) + flow.oplogReplay(true) + flow.noCursorTimeout(true) + flow.partial(true) + flow.projection(bson) + flow.returnKey(true) + flow.showRecordId(true) + flow.skip(1) + flow.sort(bson) + + verify(wrapped).allowDiskUse(true) + verify(wrapped).batchSize(batchSize) + verify(wrapped).collation(collation) + verify(wrapped).comment(bsonComment) + verify(wrapped).comment(comment) + verify(wrapped).cursorType(CursorType.NonTailable) + verify(wrapped).filter(filter) + verify(wrapped).hint(hint) + verify(wrapped).hintString(hintString) + verify(wrapped).let(bson) + verify(wrapped).limit(1) + verify(wrapped).max(bson) + verify(wrapped).maxAwaitTime(1, TimeUnit.MILLISECONDS) + verify(wrapped).maxAwaitTime(1, TimeUnit.SECONDS) + verify(wrapped).maxTime(1, TimeUnit.MILLISECONDS) + verify(wrapped).maxTime(1, TimeUnit.SECONDS) + verify(wrapped).min(bson) + verify(wrapped).oplogReplay(true) + verify(wrapped).noCursorTimeout(true) + verify(wrapped).partial(true) + verify(wrapped).projection(bson) + verify(wrapped).returnKey(true) + verify(wrapped).showRecordId(true) + verify(wrapped).skip(1) + verify(wrapped).sort(bson) + + whenever(wrapped.explain(Document::class.java)).doReturn(Mono.fromCallable { Document() }) + whenever(wrapped.explain(Document::class.java, verbosity)).doReturn(Mono.fromCallable { Document() }) + whenever(wrapped.explain(BsonDocument::class.java, verbosity)).doReturn(Mono.fromCallable { BsonDocument() }) + + runBlocking { + flow.explain() + flow.explain(verbosity) + flow.explain(Document::class.java) + flow.explain(BsonDocument::class.java, verbosity) + flow.explain() + flow.explain(verbosity) + } + + verify(wrapped, times(3)).explain(Document::class.java) + verify(wrapped, times(1)).explain(Document::class.java, verbosity) + verify(wrapped, times(2)).explain(BsonDocument::class.java, verbosity) + + verifyNoMoreInteractions(wrapped) + } +} diff --git a/driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/ListCollectionsFlowTest.kt b/driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/ListCollectionsFlowTest.kt new file mode 100644 index 00000000000..98d16113ff9 --- /dev/null +++ b/driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/ListCollectionsFlowTest.kt @@ -0,0 +1,67 @@ +/* + * 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.reactivestreams.client.ListCollectionsPublisher +import java.util.concurrent.TimeUnit +import kotlin.reflect.full.declaredFunctions +import kotlin.test.assertEquals +import org.bson.BsonDocument +import org.bson.BsonString +import org.bson.Document +import org.junit.jupiter.api.Test +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoMoreInteractions + +class ListCollectionsFlowTest { + @Test + fun shouldHaveTheSameMethods() { + val jListCollectionsPublisherFunctions = + ListCollectionsPublisher::class.declaredFunctions.map { it.name }.toSet() - "first" + val kListCollectionsFlowFunctions = + ListCollectionsFlow::class.declaredFunctions.map { it.name }.toSet() - "collect" + + assertEquals(jListCollectionsPublisherFunctions, kListCollectionsFlowFunctions) + } + + @Test + fun shouldCallTheUnderlyingMethods() { + val wrapped: ListCollectionsPublisher = mock() + val flow = ListCollectionsFlow(wrapped) + + val batchSize = 10 + val bsonComment = BsonString("a comment") + val comment = "comment" + val filter = BsonDocument() + + flow.batchSize(batchSize) + flow.comment(bsonComment) + flow.comment(comment) + flow.filter(filter) + flow.maxTime(1) + flow.maxTime(1, TimeUnit.SECONDS) + + verify(wrapped).batchSize(batchSize) + verify(wrapped).comment(bsonComment) + verify(wrapped).comment(comment) + verify(wrapped).filter(filter) + verify(wrapped).maxTime(1, TimeUnit.MILLISECONDS) + verify(wrapped).maxTime(1, TimeUnit.SECONDS) + + verifyNoMoreInteractions(wrapped) + } +} diff --git a/driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/ListDatabasesFlowTest.kt b/driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/ListDatabasesFlowTest.kt new file mode 100644 index 00000000000..53e44f740f1 --- /dev/null +++ b/driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/ListDatabasesFlowTest.kt @@ -0,0 +1,70 @@ +/* + * 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.reactivestreams.client.ListDatabasesPublisher +import java.util.concurrent.TimeUnit +import kotlin.reflect.full.declaredFunctions +import kotlin.test.assertEquals +import org.bson.BsonDocument +import org.bson.BsonString +import org.bson.Document +import org.junit.jupiter.api.Test +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoMoreInteractions + +class ListDatabasesFlowTest { + @Test + fun shouldHaveTheSameMethods() { + val jListDatabasesPublisherFunctions = + ListDatabasesPublisher::class.declaredFunctions.map { it.name }.toSet() - "first" + val kListDatabasesFlowFunctions = ListDatabasesFlow::class.declaredFunctions.map { it.name }.toSet() - "collect" + + assertEquals(jListDatabasesPublisherFunctions, kListDatabasesFlowFunctions) + } + + @Test + fun shouldCallTheUnderlyingMethods() { + val wrapped: ListDatabasesPublisher = mock() + val flow = ListDatabasesFlow(wrapped) + + val batchSize = 10 + val bsonComment = BsonString("a comment") + val comment = "comment" + val filter = BsonDocument() + + flow.authorizedDatabasesOnly(true) + flow.batchSize(batchSize) + flow.comment(bsonComment) + flow.comment(comment) + flow.filter(filter) + flow.maxTime(1) + flow.maxTime(1, TimeUnit.SECONDS) + flow.nameOnly(true) + + verify(wrapped).authorizedDatabasesOnly(true) + verify(wrapped).batchSize(batchSize) + verify(wrapped).comment(bsonComment) + verify(wrapped).comment(comment) + verify(wrapped).filter(filter) + verify(wrapped).maxTime(1, TimeUnit.MILLISECONDS) + verify(wrapped).maxTime(1, TimeUnit.SECONDS) + verify(wrapped).nameOnly(true) + + verifyNoMoreInteractions(wrapped) + } +} diff --git a/driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/ListIndexesFlowTest.kt b/driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/ListIndexesFlowTest.kt new file mode 100644 index 00000000000..69287d1918d --- /dev/null +++ b/driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/ListIndexesFlowTest.kt @@ -0,0 +1,62 @@ +/* + * 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.reactivestreams.client.ListIndexesPublisher +import java.util.concurrent.TimeUnit +import kotlin.reflect.full.declaredFunctions +import kotlin.test.assertEquals +import org.bson.BsonString +import org.bson.Document +import org.junit.jupiter.api.Test +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoMoreInteractions + +class ListIndexesFlowTest { + @Test + fun shouldHaveTheSameMethods() { + val jListIndexesPublisherFunctions = + ListIndexesPublisher::class.declaredFunctions.map { it.name }.toSet() - "first" + val kListIndexesFlowFunctions = ListIndexesFlow::class.declaredFunctions.map { it.name }.toSet() - "collect" + + assertEquals(jListIndexesPublisherFunctions, kListIndexesFlowFunctions) + } + + @Test + fun shouldCallTheUnderlyingMethods() { + val wrapped: ListIndexesPublisher = mock() + val flow = ListIndexesFlow(wrapped) + + val batchSize = 10 + val bsonComment = BsonString("a comment") + val comment = "comment" + + flow.batchSize(batchSize) + flow.comment(bsonComment) + flow.comment(comment) + flow.maxTime(1) + flow.maxTime(1, TimeUnit.SECONDS) + + verify(wrapped).batchSize(batchSize) + verify(wrapped).comment(bsonComment) + verify(wrapped).comment(comment) + verify(wrapped).maxTime(1, TimeUnit.MILLISECONDS) + verify(wrapped).maxTime(1, TimeUnit.SECONDS) + + verifyNoMoreInteractions(wrapped) + } +} diff --git a/driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/MapReduceFlowTest.kt b/driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/MapReduceFlowTest.kt new file mode 100644 index 00000000000..132d26cf764 --- /dev/null +++ b/driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/MapReduceFlowTest.kt @@ -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. + */ +@file:Suppress("DEPRECATION") + +package com.mongodb.kotlin.client.coroutine + +import com.mongodb.client.model.Collation +import com.mongodb.client.model.MapReduceAction +import com.mongodb.reactivestreams.client.MapReducePublisher +import java.util.concurrent.TimeUnit +import kotlin.reflect.full.declaredFunctions +import kotlin.test.assertEquals +import kotlinx.coroutines.runBlocking +import org.bson.BsonDocument +import org.bson.Document +import org.junit.jupiter.api.Test +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoMoreInteractions +import org.mockito.kotlin.whenever +import reactor.core.publisher.Mono + +class MapReduceFlowTest { + @Test + fun shouldHaveTheSameMethods() { + val jMapReducePublisherFunctions = MapReducePublisher::class.declaredFunctions.map { it.name }.toSet() - "first" + val kMapReduceFlowFunctions = MapReduceFlow::class.declaredFunctions.map { it.name }.toSet() - "collect" + + assertEquals(jMapReducePublisherFunctions, kMapReduceFlowFunctions) + } + + @Test + fun shouldCallTheUnderlyingMethods() { + val wrapped: MapReducePublisher = mock() + val flow = MapReduceFlow(wrapped) + + val batchSize = 10 + val bson = BsonDocument() + val collation = Collation.builder().locale("en").build() + val collectionName = "coll" + val databaseName = "db" + val filter = BsonDocument() + val finalizeFunction = "finalize" + + flow.batchSize(batchSize) + flow.bypassDocumentValidation(true) + flow.collation(collation) + flow.collectionName(collectionName) + flow.databaseName(databaseName) + flow.filter(filter) + flow.finalizeFunction(finalizeFunction) + flow.jsMode(true) + flow.limit(1) + flow.maxTime(1) + flow.maxTime(1, TimeUnit.SECONDS) + flow.nonAtomic(true) + flow.scope(bson) + flow.sharded(true) + flow.sort(bson) + flow.verbose(true) + flow.action(MapReduceAction.MERGE) + + verify(wrapped).batchSize(batchSize) + verify(wrapped).bypassDocumentValidation(true) + verify(wrapped).collation(collation) + verify(wrapped).collectionName(collectionName) + verify(wrapped).databaseName(databaseName) + verify(wrapped).filter(filter) + verify(wrapped).finalizeFunction(finalizeFunction) + verify(wrapped).jsMode(true) + verify(wrapped).limit(1) + verify(wrapped).maxTime(1, TimeUnit.MILLISECONDS) + verify(wrapped).maxTime(1, TimeUnit.SECONDS) + verify(wrapped).nonAtomic(true) + verify(wrapped).scope(bson) + verify(wrapped).sharded(true) + verify(wrapped).sort(bson) + verify(wrapped).verbose(true) + verify(wrapped).action(MapReduceAction.MERGE) + + whenever(wrapped.toCollection()).doReturn(Mono.empty()) + runBlocking { flow.toCollection() } + verify(wrapped).toCollection() + + verifyNoMoreInteractions(wrapped) + } +} diff --git a/driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/MockitoHelper.kt b/driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/MockitoHelper.kt new file mode 100644 index 00000000000..cd5d0f4d68a --- /dev/null +++ b/driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/MockitoHelper.kt @@ -0,0 +1,53 @@ +/* + * 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 org.assertj.core.api.Assertions.assertThat +import org.mockito.ArgumentMatcher +import org.mockito.ArgumentMatchers.argThat + +/** Mockito test helper object */ +object MockitoHelper { + + /** + * Deep reflection comparison for complex nested objects + * + * The usecase is to reflect complex objects that don't have an equals method and contain nested complex properties + * that also do not contain equals values + * + * Example: + * ``` + * verify(wrapped).createCollection(eq(name), deepRefEq(defaultOptions)) + * ``` + * + * @param T the type of the value + * @param value the value + * @return the value + * @see [org.mockito.kotlin.refEq] + */ + fun deepRefEq(value: T): T = argThat(DeepReflectionEqMatcher(value)) + + private class DeepReflectionEqMatcher(private val expected: T) : ArgumentMatcher { + override fun matches(argument: T): Boolean { + return try { + assertThat(argument).usingRecursiveComparison().isEqualTo(expected) + true + } catch (e: Throwable) { + false + } + } + } +} diff --git a/driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/MongoClientTest.kt b/driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/MongoClientTest.kt new file mode 100644 index 00000000000..9ac4805f6fa --- /dev/null +++ b/driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/MongoClientTest.kt @@ -0,0 +1,169 @@ +/* + * 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.ClientSessionOptions +import com.mongodb.reactivestreams.client.MongoClient as JMongoClient +import kotlin.reflect.full.declaredFunctions +import kotlin.test.assertEquals +import kotlinx.coroutines.runBlocking +import org.bson.BsonDocument +import org.bson.Document +import org.junit.jupiter.api.Test +import org.mockito.Mock +import org.mockito.kotlin.any +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.refEq +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoMoreInteractions +import org.mockito.kotlin.whenever +import reactor.core.publisher.Mono + +class MongoClientTest { + + @Mock val wrapped: JMongoClient = mock() + @Mock val clientSession: ClientSession = ClientSession(mock()) + + @Test + fun shouldHaveTheSameMethods() { + val jMongoClientFunctions = JMongoClient::class.declaredFunctions.map { it.name }.toSet() + val kMongoClientFunctions = MongoClient::class.declaredFunctions.map { it.name }.toSet() + + assertEquals(jMongoClientFunctions, kMongoClientFunctions) + } + + @Test + fun shouldCallTheUnderlyingClose() { + val mongoClient = MongoClient(wrapped) + mongoClient.close() + + verify(wrapped).close() + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingClusterDescription() { + val mongoClient = MongoClient(wrapped) + whenever(wrapped.clusterDescription).doReturn(mock()) + + mongoClient.getClusterDescription() + + verify(wrapped).clusterDescription + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingGetDatabase() { + val mongoClient = MongoClient(wrapped) + whenever(wrapped.getDatabase(any())).doReturn(mock()) + + mongoClient.getDatabase("dbName") + verify(wrapped).getDatabase("dbName") + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shoulCallTheUnderlyingStartSession() { + val mongoClient = MongoClient(wrapped) + val defaultOptions = ClientSessionOptions.builder().build() + val options = ClientSessionOptions.builder().causallyConsistent(true).build() + + whenever(wrapped.startSession(refEq(defaultOptions))).doReturn(Mono.fromCallable { mock() }) + whenever(wrapped.startSession(options)).doReturn(Mono.fromCallable { mock() }) + + runBlocking { + mongoClient.startSession() + mongoClient.startSession(options) + } + + verify(wrapped).startSession(refEq(defaultOptions)) + verify(wrapped).startSession(options) + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingListDatabaseNames() { + val mongoClient = MongoClient(wrapped) + whenever(wrapped.listDatabaseNames()).doReturn(mock()) + whenever(wrapped.listDatabaseNames(any())).doReturn(mock()) + + mongoClient.listDatabaseNames() + mongoClient.listDatabaseNames(clientSession) + + verify(wrapped).listDatabaseNames() + verify(wrapped).listDatabaseNames(clientSession.wrapped) + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingListDatabases() { + val mongoClient = MongoClient(wrapped) + whenever(wrapped.listDatabases(Document::class.java)).doReturn(mock()) + whenever(wrapped.listDatabases(clientSession.wrapped, Document::class.java)).doReturn(mock()) + whenever(wrapped.listDatabases(clientSession.wrapped, BsonDocument::class.java)).doReturn(mock()) + + mongoClient.listDatabases() + mongoClient.listDatabases(clientSession) + mongoClient.listDatabases(Document::class.java) + mongoClient.listDatabases(clientSession, BsonDocument::class.java) + mongoClient.listDatabases() + mongoClient.listDatabases(clientSession) + + verify(wrapped, times(3)).listDatabases(Document::class.java) + verify(wrapped, times(1)).listDatabases(clientSession.wrapped, Document::class.java) + verify(wrapped, times(2)).listDatabases(clientSession.wrapped, BsonDocument::class.java) + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingWatch() { + val mongoClient = MongoClient(wrapped) + val pipeline = listOf(Document(mapOf("a" to 1))) + + whenever(wrapped.watch(emptyList(), Document::class.java)).doReturn(mock()) + whenever(wrapped.watch(pipeline, Document::class.java)).doReturn(mock()) + whenever(wrapped.watch(clientSession.wrapped, emptyList(), Document::class.java)).doReturn(mock()) + whenever(wrapped.watch(clientSession.wrapped, pipeline, Document::class.java)).doReturn(mock()) + whenever(wrapped.watch(pipeline, BsonDocument::class.java)).doReturn(mock()) + whenever(wrapped.watch(clientSession.wrapped, emptyList(), Document::class.java)).doReturn(mock()) + whenever(wrapped.watch(clientSession.wrapped, pipeline, BsonDocument::class.java)).doReturn(mock()) + + mongoClient.watch() + mongoClient.watch(pipeline) + mongoClient.watch(clientSession) + mongoClient.watch(clientSession, pipeline) + + mongoClient.watch(resultClass = Document::class.java) + mongoClient.watch(pipeline, BsonDocument::class.java) + mongoClient.watch(clientSession = clientSession, resultClass = Document::class.java) + mongoClient.watch(clientSession, pipeline, BsonDocument::class.java) + + mongoClient.watch() + mongoClient.watch(pipeline) + mongoClient.watch(clientSession) + mongoClient.watch(clientSession, pipeline) + + verify(wrapped, times(3)).watch(emptyList(), Document::class.java) + verify(wrapped, times(1)).watch(pipeline, Document::class.java) + verify(wrapped, times(3)).watch(clientSession.wrapped, emptyList(), Document::class.java) + verify(wrapped, times(1)).watch(clientSession.wrapped, pipeline, Document::class.java) + verify(wrapped, times(2)).watch(pipeline, BsonDocument::class.java) + verify(wrapped, times(2)).watch(clientSession.wrapped, pipeline, BsonDocument::class.java) + verifyNoMoreInteractions(wrapped) + } +} diff --git a/driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/MongoCollectionTest.kt b/driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/MongoCollectionTest.kt new file mode 100644 index 00000000000..e8e121f85dc --- /dev/null +++ b/driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/MongoCollectionTest.kt @@ -0,0 +1,1011 @@ +/* + * 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.CreateIndexCommitQuorum +import com.mongodb.MongoNamespace +import com.mongodb.ReadConcern +import com.mongodb.ReadPreference +import com.mongodb.WriteConcern +import com.mongodb.client.model.BulkWriteOptions +import com.mongodb.client.model.CountOptions +import com.mongodb.client.model.CreateIndexOptions +import com.mongodb.client.model.DeleteOptions +import com.mongodb.client.model.DropCollectionOptions +import com.mongodb.client.model.DropIndexOptions +import com.mongodb.client.model.EstimatedDocumentCountOptions +import com.mongodb.client.model.FindOneAndDeleteOptions +import com.mongodb.client.model.FindOneAndReplaceOptions +import com.mongodb.client.model.FindOneAndUpdateOptions +import com.mongodb.client.model.IndexModel +import com.mongodb.client.model.IndexOptions +import com.mongodb.client.model.InsertManyOptions +import com.mongodb.client.model.InsertOneModel +import com.mongodb.client.model.InsertOneOptions +import com.mongodb.client.model.RenameCollectionOptions +import com.mongodb.client.model.ReplaceOptions +import com.mongodb.client.model.UpdateOptions +import com.mongodb.reactivestreams.client.MongoCollection as JMongoCollection +import java.util.concurrent.TimeUnit +import kotlin.reflect.full.declaredFunctions +import kotlin.reflect.full.declaredMemberProperties +import kotlin.test.assertEquals +import kotlinx.coroutines.runBlocking +import org.bson.BsonDocument +import org.bson.Document +import org.bson.codecs.configuration.CodecRegistry +import org.junit.jupiter.api.Test +import org.mockito.Mock +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.refEq +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoMoreInteractions +import org.mockito.kotlin.whenever +import reactor.core.publisher.Mono + +class MongoCollectionTest { + + @Mock val wrapped: JMongoCollection = mock() + @Mock val clientSession: ClientSession = ClientSession(mock()) + + private val defaultFilter = BsonDocument() + private val filter = Document("a", 1) + private val pipeline = listOf(Document(mapOf("a" to 1))) + + @Test + fun shouldHaveTheSameMethods() { + val jMongoCollectionFunctions = JMongoCollection::class.declaredFunctions.map { it.name }.toSet() + val kMongoCollectionFunctions = + MongoCollection::class.declaredFunctions.map { it.name }.toSet() + + MongoCollection::class + .declaredMemberProperties + .filterNot { it.name == "wrapped" } + .map { "get${it.name.replaceFirstChar{c -> c.uppercaseChar() }}" } + + assertEquals(jMongoCollectionFunctions, kMongoCollectionFunctions) + } + + @Test + fun shouldCallTheUnderlyingGetDocumentClass() { + val mongoCollection = MongoCollection(wrapped) + whenever(wrapped.documentClass).doReturn(Document::class.java) + + mongoCollection.documentClass + verify(wrapped).documentClass + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingGetNamespace() { + val mongoCollection = MongoCollection(wrapped) + whenever(wrapped.namespace).doReturn(MongoNamespace("a.b")) + + mongoCollection.namespace + verify(wrapped).namespace + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingGetCodecRegistry() { + val mongoCollection = MongoCollection(wrapped) + whenever(wrapped.codecRegistry).doReturn(mock()) + + mongoCollection.codecRegistry + verify(wrapped).codecRegistry + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingGetReadPreference() { + val mongoCollection = MongoCollection(wrapped) + whenever(wrapped.readPreference).doReturn(mock()) + + mongoCollection.readPreference + verify(wrapped).readPreference + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingGetReadConcern() { + val mongoCollection = MongoCollection(wrapped) + whenever(wrapped.readConcern).doReturn(ReadConcern.DEFAULT) + + mongoCollection.readConcern + verify(wrapped).readConcern + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingGetWriteConcern() { + val mongoCollection = MongoCollection(wrapped) + whenever(wrapped.writeConcern).doReturn(mock()) + + mongoCollection.writeConcern + verify(wrapped).writeConcern + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingWithDocumentClass() { + val mongoCollection = MongoCollection(wrapped) + whenever(wrapped.withDocumentClass(BsonDocument::class.java)).doReturn(mock()) + + mongoCollection.withDocumentClass() + verify(wrapped).withDocumentClass(BsonDocument::class.java) + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingWithCodecRegistry() { + val mongoCollection = MongoCollection(wrapped) + val codecRegistry = mock() + whenever(wrapped.withCodecRegistry(codecRegistry)).doReturn(mock()) + + mongoCollection.withCodecRegistry(codecRegistry) + verify(wrapped).withCodecRegistry(codecRegistry) + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingWithReadPreference() { + val mongoCollection = MongoCollection(wrapped) + val readPreference = ReadPreference.primaryPreferred() + whenever(wrapped.withReadPreference(readPreference)).doReturn(mock()) + + mongoCollection.withReadPreference(readPreference) + verify(wrapped).withReadPreference(readPreference) + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingWithReadConcern() { + val mongoCollection = MongoCollection(wrapped) + val readConcern = ReadConcern.AVAILABLE + whenever(wrapped.withReadConcern(readConcern)).doReturn(mock()) + + mongoCollection.withReadConcern(readConcern) + verify(wrapped).withReadConcern(readConcern) + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingWithWriteConcern() { + val mongoCollection = MongoCollection(wrapped) + val writeConcern = WriteConcern.MAJORITY + whenever(wrapped.withWriteConcern(writeConcern)).doReturn(mock()) + + mongoCollection.withWriteConcern(writeConcern) + verify(wrapped).withWriteConcern(writeConcern) + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingCountDocuments() { + val mongoCollection = MongoCollection(wrapped) + + val defaultOptions = CountOptions() + + val options = CountOptions().comment("comment") + + whenever(wrapped.countDocuments(eq(defaultFilter), refEq(defaultOptions))).doReturn(Mono.fromCallable { 1 }) + whenever(wrapped.countDocuments(eq(filter), refEq(defaultOptions))).doReturn(Mono.fromCallable { 2 }) + whenever(wrapped.countDocuments(eq(filter), eq(options))).doReturn(Mono.fromCallable { 3 }) + whenever(wrapped.countDocuments(eq(clientSession.wrapped), eq(defaultFilter), refEq(defaultOptions))) + .doReturn(Mono.fromCallable { 4 }) + whenever(wrapped.countDocuments(eq(clientSession.wrapped), eq(filter), refEq(defaultOptions))) + .doReturn(Mono.fromCallable { 5 }) + whenever(wrapped.countDocuments(eq(clientSession.wrapped), eq(filter), eq(options))) + .doReturn(Mono.fromCallable { 6 }) + + runBlocking { + assertEquals(1, mongoCollection.countDocuments()) + assertEquals(2, mongoCollection.countDocuments(filter)) + assertEquals(3, mongoCollection.countDocuments(filter, options)) + assertEquals(4, mongoCollection.countDocuments(clientSession)) + assertEquals(5, mongoCollection.countDocuments(clientSession, filter)) + assertEquals(6, mongoCollection.countDocuments(clientSession, filter, options)) + } + + verify(wrapped).countDocuments(eq(defaultFilter), refEq(defaultOptions)) + verify(wrapped).countDocuments(eq(filter), refEq(defaultOptions)) + verify(wrapped).countDocuments(eq(filter), eq(options)) + verify(wrapped).countDocuments(eq(clientSession.wrapped), eq(defaultFilter), refEq(defaultOptions)) + verify(wrapped).countDocuments(eq(clientSession.wrapped), eq(filter), refEq(defaultOptions)) + verify(wrapped).countDocuments(eq(clientSession.wrapped), eq(filter), eq(options)) + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingEstimatedDocumentCount() { + val mongoCollection = MongoCollection(wrapped) + val defaultOptions = EstimatedDocumentCountOptions() + val options = EstimatedDocumentCountOptions().comment("comment") + + whenever(wrapped.estimatedDocumentCount(refEq(defaultOptions))).doReturn(Mono.fromCallable { 1 }) + whenever(wrapped.estimatedDocumentCount(options)).doReturn(Mono.fromCallable { 2 }) + + runBlocking { + assertEquals(1, mongoCollection.estimatedDocumentCount()) + assertEquals(2, mongoCollection.estimatedDocumentCount(options)) + } + + verify(wrapped).estimatedDocumentCount(refEq(defaultOptions)) + verify(wrapped).estimatedDocumentCount(options) + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingDistinct() { + val mongoCollection = MongoCollection(wrapped) + val fieldName = "fieldName" + + whenever(wrapped.distinct(fieldName, defaultFilter, Document::class.java)).doReturn(mock()) + whenever(wrapped.distinct(fieldName, filter, Document::class.java)).doReturn(mock()) + whenever(wrapped.distinct(clientSession.wrapped, fieldName, defaultFilter, Document::class.java)) + .doReturn(mock()) + whenever(wrapped.distinct(clientSession.wrapped, fieldName, filter, Document::class.java)).doReturn(mock()) + whenever(wrapped.distinct(fieldName, defaultFilter, BsonDocument::class.java)).doReturn(mock()) + whenever(wrapped.distinct(fieldName, filter, BsonDocument::class.java)).doReturn(mock()) + whenever(wrapped.distinct(clientSession.wrapped, fieldName, defaultFilter, BsonDocument::class.java)) + .doReturn(mock()) + whenever(wrapped.distinct(clientSession.wrapped, fieldName, filter, BsonDocument::class.java)).doReturn(mock()) + + mongoCollection.distinct("fieldName", resultClass = Document::class.java) + mongoCollection.distinct("fieldName", filter, Document::class.java) + mongoCollection.distinct(clientSession, "fieldName", resultClass = Document::class.java) + mongoCollection.distinct(clientSession, "fieldName", filter, Document::class.java) + + mongoCollection.distinct("fieldName") + mongoCollection.distinct("fieldName", filter) + mongoCollection.distinct(clientSession, "fieldName") + mongoCollection.distinct(clientSession, "fieldName", filter) + + verify(wrapped).distinct(fieldName, defaultFilter, Document::class.java) + verify(wrapped).distinct(fieldName, filter, Document::class.java) + verify(wrapped).distinct(clientSession.wrapped, fieldName, defaultFilter, Document::class.java) + verify(wrapped).distinct(clientSession.wrapped, fieldName, filter, Document::class.java) + + verify(wrapped).distinct(fieldName, defaultFilter, BsonDocument::class.java) + verify(wrapped).distinct(fieldName, filter, BsonDocument::class.java) + verify(wrapped).distinct(clientSession.wrapped, fieldName, defaultFilter, BsonDocument::class.java) + verify(wrapped).distinct(clientSession.wrapped, fieldName, filter, BsonDocument::class.java) + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingFind() { + val mongoCollection = MongoCollection(wrapped) + + whenever(wrapped.documentClass).doReturn(Document::class.java) + whenever(wrapped.find(defaultFilter, Document::class.java)).doReturn(mock()) + whenever(wrapped.find(filter, Document::class.java)).doReturn(mock()) + whenever(wrapped.find(clientSession.wrapped, defaultFilter, Document::class.java)).doReturn(mock()) + whenever(wrapped.find(clientSession.wrapped, filter, Document::class.java)).doReturn(mock()) + whenever(wrapped.find(defaultFilter, BsonDocument::class.java)).doReturn(mock()) + whenever(wrapped.find(filter, BsonDocument::class.java)).doReturn(mock()) + whenever(wrapped.find(clientSession.wrapped, defaultFilter, BsonDocument::class.java)).doReturn(mock()) + whenever(wrapped.find(clientSession.wrapped, filter, BsonDocument::class.java)).doReturn(mock()) + + mongoCollection.find() + mongoCollection.find(filter) + mongoCollection.find(clientSession) + mongoCollection.find(clientSession, filter) + + mongoCollection.find(resultClass = Document::class.java) + mongoCollection.find(filter, resultClass = Document::class.java) + mongoCollection.find(clientSession, resultClass = Document::class.java) + mongoCollection.find(clientSession, filter, Document::class.java) + + mongoCollection.find() + mongoCollection.find(filter) + mongoCollection.find(clientSession) + mongoCollection.find(clientSession, filter) + + verify(wrapped, times(4)).documentClass + verify(wrapped, times(2)).find(defaultFilter, Document::class.java) + verify(wrapped, times(2)).find(filter, Document::class.java) + verify(wrapped, times(2)).find(clientSession.wrapped, defaultFilter, Document::class.java) + verify(wrapped, times(2)).find(clientSession.wrapped, filter, Document::class.java) + verify(wrapped, times(1)).find(defaultFilter, BsonDocument::class.java) + verify(wrapped, times(1)).find(filter, BsonDocument::class.java) + verify(wrapped, times(1)).find(clientSession.wrapped, defaultFilter, BsonDocument::class.java) + verify(wrapped, times(1)).find(clientSession.wrapped, filter, BsonDocument::class.java) + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingAggregate() { + val mongoCollection = MongoCollection(wrapped) + + whenever(wrapped.documentClass).doReturn(Document::class.java) + whenever(wrapped.aggregate(pipeline, Document::class.java)).doReturn(mock()) + whenever(wrapped.aggregate(clientSession.wrapped, pipeline, Document::class.java)).doReturn(mock()) + whenever(wrapped.aggregate(pipeline, BsonDocument::class.java)).doReturn(mock()) + whenever(wrapped.aggregate(clientSession.wrapped, pipeline, BsonDocument::class.java)).doReturn(mock()) + + mongoCollection.aggregate(pipeline) + mongoCollection.aggregate(clientSession, pipeline) + + mongoCollection.aggregate(pipeline, resultClass = Document::class.java) + mongoCollection.aggregate(clientSession, pipeline, Document::class.java) + + mongoCollection.aggregate(pipeline) + mongoCollection.aggregate(clientSession, pipeline) + + verify(wrapped, times(2)).documentClass + verify(wrapped, times(2)).aggregate(pipeline, Document::class.java) + verify(wrapped, times(2)).aggregate(clientSession.wrapped, pipeline, Document::class.java) + verify(wrapped, times(1)).aggregate(pipeline, BsonDocument::class.java) + verify(wrapped, times(1)).aggregate(clientSession.wrapped, pipeline, BsonDocument::class.java) + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingWatch() { + val mongoCollection = MongoCollection(wrapped) + val pipeline = listOf(Document(mapOf("a" to 1))) + + whenever(wrapped.documentClass).doReturn(Document::class.java) + whenever(wrapped.watch(emptyList(), Document::class.java)).doReturn(mock()) + whenever(wrapped.watch(pipeline, Document::class.java)).doReturn(mock()) + whenever(wrapped.watch(clientSession.wrapped, emptyList(), Document::class.java)).doReturn(mock()) + whenever(wrapped.watch(clientSession.wrapped, pipeline, Document::class.java)).doReturn(mock()) + whenever(wrapped.watch(emptyList(), BsonDocument::class.java)).doReturn(mock()) + whenever(wrapped.watch(pipeline, BsonDocument::class.java)).doReturn(mock()) + whenever(wrapped.watch(clientSession.wrapped, emptyList(), BsonDocument::class.java)).doReturn(mock()) + whenever(wrapped.watch(clientSession.wrapped, pipeline, BsonDocument::class.java)).doReturn(mock()) + + mongoCollection.watch() + mongoCollection.watch(pipeline) + mongoCollection.watch(clientSession) + mongoCollection.watch(clientSession, pipeline) + + mongoCollection.watch(resultClass = Document::class.java) + mongoCollection.watch(pipeline, Document::class.java) + mongoCollection.watch(clientSession, resultClass = Document::class.java) + mongoCollection.watch(clientSession, pipeline, Document::class.java) + + mongoCollection.watch() + mongoCollection.watch(pipeline) + mongoCollection.watch(clientSession) + mongoCollection.watch(clientSession, pipeline) + + verify(wrapped, times(4)).documentClass + verify(wrapped, times(2)).watch(emptyList(), Document::class.java) + verify(wrapped, times(2)).watch(pipeline, Document::class.java) + verify(wrapped, times(2)).watch(clientSession.wrapped, emptyList(), Document::class.java) + verify(wrapped, times(2)).watch(clientSession.wrapped, pipeline, Document::class.java) + verify(wrapped, times(1)).watch(emptyList(), BsonDocument::class.java) + verify(wrapped, times(1)).watch(pipeline, BsonDocument::class.java) + verify(wrapped, times(1)).watch(clientSession.wrapped, emptyList(), BsonDocument::class.java) + verify(wrapped, times(1)).watch(clientSession.wrapped, pipeline, BsonDocument::class.java) + verifyNoMoreInteractions(wrapped) + } + + @Suppress("DEPRECATION") + @Test + fun shouldCallTheUnderlyingMapReduce() { + val mongoCollection = MongoCollection(wrapped) + val mapFunction = "mapper" + val reduceFunction = "mapper" + + whenever(wrapped.documentClass).doReturn(Document::class.java) + whenever(wrapped.mapReduce(mapFunction, reduceFunction, Document::class.java)).doReturn(mock()) + whenever(wrapped.mapReduce(clientSession.wrapped, mapFunction, reduceFunction, Document::class.java)) + .doReturn(mock()) + whenever(wrapped.mapReduce(mapFunction, reduceFunction, BsonDocument::class.java)).doReturn(mock()) + whenever(wrapped.mapReduce(clientSession.wrapped, mapFunction, reduceFunction, BsonDocument::class.java)) + .doReturn(mock()) + + mongoCollection.mapReduce(mapFunction, reduceFunction) + mongoCollection.mapReduce(clientSession, mapFunction, reduceFunction) + + mongoCollection.mapReduce(mapFunction, reduceFunction, Document::class.java) + mongoCollection.mapReduce(clientSession, mapFunction, reduceFunction, Document::class.java) + + mongoCollection.mapReduce(mapFunction, reduceFunction) + mongoCollection.mapReduce(clientSession, mapFunction, reduceFunction) + + verify(wrapped, times(2)).documentClass + verify(wrapped, times(2)).mapReduce(mapFunction, reduceFunction, Document::class.java) + verify(wrapped, times(2)).mapReduce(clientSession.wrapped, mapFunction, reduceFunction, Document::class.java) + verify(wrapped, times(1)).mapReduce(mapFunction, reduceFunction, BsonDocument::class.java) + verify(wrapped, times(1)) + .mapReduce(clientSession.wrapped, mapFunction, reduceFunction, BsonDocument::class.java) + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingInsertOne() { + val mongoCollection = MongoCollection(wrapped) + val value = Document("u", 1) + val defaultOptions = InsertOneOptions() + val options = InsertOneOptions().comment("comment") + + whenever(wrapped.insertOne(eq(value), refEq(defaultOptions))).doReturn(Mono.fromCallable { mock() }) + whenever(wrapped.insertOne(eq(value), eq(options))).doReturn(Mono.fromCallable { mock() }) + whenever(wrapped.insertOne(eq(clientSession.wrapped), eq(value), refEq(defaultOptions))) + .doReturn(Mono.fromCallable { mock() }) + whenever(wrapped.insertOne(eq(clientSession.wrapped), eq(value), eq(options))) + .doReturn(Mono.fromCallable { mock() }) + + runBlocking { + mongoCollection.insertOne(value) + mongoCollection.insertOne(value, options) + mongoCollection.insertOne(clientSession, value) + mongoCollection.insertOne(clientSession, value, options) + } + + verify(wrapped).insertOne(eq(value), refEq(defaultOptions)) + verify(wrapped).insertOne(eq(value), eq(options)) + verify(wrapped).insertOne(eq(clientSession.wrapped), eq(value), refEq(defaultOptions)) + verify(wrapped).insertOne(eq(clientSession.wrapped), eq(value), eq(options)) + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingInsertMany() { + val mongoCollection = MongoCollection(wrapped) + val value = listOf(Document("u", 1)) + val defaultOptions = InsertManyOptions() + val options = InsertManyOptions().comment("comment") + + whenever(wrapped.insertMany(eq(value), refEq(defaultOptions))).doReturn(Mono.fromCallable { mock() }) + whenever(wrapped.insertMany(eq(value), eq(options))).doReturn(Mono.fromCallable { mock() }) + whenever(wrapped.insertMany(eq(clientSession.wrapped), eq(value), refEq(defaultOptions))) + .doReturn(Mono.fromCallable { mock() }) + whenever(wrapped.insertMany(eq(clientSession.wrapped), eq(value), eq(options))) + .doReturn(Mono.fromCallable { mock() }) + + runBlocking { + mongoCollection.insertMany(value) + mongoCollection.insertMany(value, options) + mongoCollection.insertMany(clientSession, value) + mongoCollection.insertMany(clientSession, value, options) + } + + verify(wrapped).insertMany(eq(value), refEq(defaultOptions)) + verify(wrapped).insertMany(eq(value), eq(options)) + verify(wrapped).insertMany(eq(clientSession.wrapped), eq(value), refEq(defaultOptions)) + verify(wrapped).insertMany(eq(clientSession.wrapped), eq(value), eq(options)) + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingBulkWrite() { + val mongoCollection = MongoCollection(wrapped) + val value = listOf(InsertOneModel(Document("u", 1))) + val defaultOptions = BulkWriteOptions() + val options = BulkWriteOptions().comment("comment") + + whenever(wrapped.bulkWrite(eq(value), refEq(defaultOptions))).doReturn(Mono.fromCallable { mock() }) + whenever(wrapped.bulkWrite(eq(value), eq(options))).doReturn(Mono.fromCallable { mock() }) + whenever(wrapped.bulkWrite(eq(clientSession.wrapped), eq(value), refEq(defaultOptions))) + .doReturn(Mono.fromCallable { mock() }) + whenever(wrapped.bulkWrite(eq(clientSession.wrapped), eq(value), eq(options))) + .doReturn(Mono.fromCallable { mock() }) + + runBlocking { + mongoCollection.bulkWrite(value) + mongoCollection.bulkWrite(value, options) + mongoCollection.bulkWrite(clientSession, value) + mongoCollection.bulkWrite(clientSession, value, options) + } + + verify(wrapped).bulkWrite(eq(value), refEq(defaultOptions)) + verify(wrapped).bulkWrite(eq(value), eq(options)) + verify(wrapped).bulkWrite(eq(clientSession.wrapped), eq(value), refEq(defaultOptions)) + verify(wrapped).bulkWrite(eq(clientSession.wrapped), eq(value), eq(options)) + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingUpdateOne() { + val mongoCollection = MongoCollection(wrapped) + val update = Document("u", 1) + val updates = listOf(update) + val defaultOptions = UpdateOptions() + val options = UpdateOptions().comment("comment") + + whenever(wrapped.updateOne(eq(filter), eq(update), refEq(defaultOptions))) + .doReturn(Mono.fromCallable { mock() }) + whenever(wrapped.updateOne(eq(filter), eq(update), eq(options))).doReturn(Mono.fromCallable { mock() }) + whenever(wrapped.updateOne(eq(filter), eq(updates), refEq(defaultOptions))) + .doReturn(Mono.fromCallable { mock() }) + whenever(wrapped.updateOne(eq(filter), eq(updates), eq(options))).doReturn(Mono.fromCallable { mock() }) + whenever(wrapped.updateOne(eq(clientSession.wrapped), eq(filter), eq(update), refEq(defaultOptions))) + .doReturn(Mono.fromCallable { mock() }) + whenever(wrapped.updateOne(eq(clientSession.wrapped), eq(filter), eq(update), eq(options))) + .doReturn(Mono.fromCallable { mock() }) + whenever(wrapped.updateOne(eq(clientSession.wrapped), eq(filter), eq(updates), refEq(defaultOptions))) + .doReturn(Mono.fromCallable { mock() }) + whenever(wrapped.updateOne(eq(clientSession.wrapped), eq(filter), eq(updates), eq(options))) + .doReturn(Mono.fromCallable { mock() }) + + runBlocking { + mongoCollection.updateOne(filter, update) + mongoCollection.updateOne(filter, update, options) + mongoCollection.updateOne(filter, updates) + mongoCollection.updateOne(filter, updates, options) + mongoCollection.updateOne(clientSession, filter, update) + mongoCollection.updateOne(clientSession, filter, update, options) + mongoCollection.updateOne(clientSession, filter, updates) + mongoCollection.updateOne(clientSession, filter, updates, options) + } + + verify(wrapped).updateOne(eq(filter), eq(update), refEq(defaultOptions)) + verify(wrapped).updateOne(eq(filter), eq(update), eq(options)) + verify(wrapped).updateOne(eq(filter), eq(updates), refEq(defaultOptions)) + verify(wrapped).updateOne(eq(filter), eq(updates), eq(options)) + verify(wrapped).updateOne(eq(clientSession.wrapped), eq(filter), eq(update), refEq(defaultOptions)) + verify(wrapped).updateOne(eq(clientSession.wrapped), eq(filter), eq(update), eq(options)) + verify(wrapped).updateOne(eq(clientSession.wrapped), eq(filter), eq(updates), refEq(defaultOptions)) + verify(wrapped).updateOne(eq(clientSession.wrapped), eq(filter), eq(updates), eq(options)) + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingUpdateMany() { + val mongoCollection = MongoCollection(wrapped) + val update = Document("u", 1) + val updates = listOf(update) + val defaultOptions = UpdateOptions() + val options = UpdateOptions().comment("comment") + + whenever(wrapped.updateMany(eq(filter), eq(update), refEq(defaultOptions))) + .doReturn(Mono.fromCallable { mock() }) + whenever(wrapped.updateMany(eq(filter), eq(update), eq(options))).doReturn(Mono.fromCallable { mock() }) + whenever(wrapped.updateMany(eq(filter), eq(updates), refEq(defaultOptions))) + .doReturn(Mono.fromCallable { mock() }) + whenever(wrapped.updateMany(eq(filter), eq(updates), eq(options))).doReturn(Mono.fromCallable { mock() }) + whenever(wrapped.updateMany(eq(clientSession.wrapped), eq(filter), eq(update), refEq(defaultOptions))) + .doReturn(Mono.fromCallable { mock() }) + whenever(wrapped.updateMany(eq(clientSession.wrapped), eq(filter), eq(update), eq(options))) + .doReturn(Mono.fromCallable { mock() }) + whenever(wrapped.updateMany(eq(clientSession.wrapped), eq(filter), eq(updates), refEq(defaultOptions))) + .doReturn(Mono.fromCallable { mock() }) + whenever(wrapped.updateMany(eq(clientSession.wrapped), eq(filter), eq(updates), eq(options))) + .doReturn(Mono.fromCallable { mock() }) + + runBlocking { + mongoCollection.updateMany(filter, update) + mongoCollection.updateMany(filter, update, options) + mongoCollection.updateMany(filter, updates) + mongoCollection.updateMany(filter, updates, options) + mongoCollection.updateMany(clientSession, filter, update) + mongoCollection.updateMany(clientSession, filter, update, options) + mongoCollection.updateMany(clientSession, filter, updates) + mongoCollection.updateMany(clientSession, filter, updates, options) + } + + verify(wrapped).updateMany(eq(filter), eq(update), refEq(defaultOptions)) + verify(wrapped).updateMany(eq(filter), eq(update), eq(options)) + verify(wrapped).updateMany(eq(filter), eq(updates), refEq(defaultOptions)) + verify(wrapped).updateMany(eq(filter), eq(updates), eq(options)) + verify(wrapped).updateMany(eq(clientSession.wrapped), eq(filter), eq(update), refEq(defaultOptions)) + verify(wrapped).updateMany(eq(clientSession.wrapped), eq(filter), eq(update), eq(options)) + verify(wrapped).updateMany(eq(clientSession.wrapped), eq(filter), eq(updates), refEq(defaultOptions)) + verify(wrapped).updateMany(eq(clientSession.wrapped), eq(filter), eq(updates), eq(options)) + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingReplaceOne() { + val mongoCollection = MongoCollection(wrapped) + val replacement = Document("u", 1) + val defaultOptions = ReplaceOptions() + val options = ReplaceOptions().comment("comment") + + whenever(wrapped.replaceOne(eq(filter), eq(replacement), refEq(defaultOptions))) + .doReturn(Mono.fromCallable { mock() }) + whenever(wrapped.replaceOne(eq(filter), eq(replacement), eq(options))).doReturn(Mono.fromCallable { mock() }) + whenever(wrapped.replaceOne(eq(clientSession.wrapped), eq(filter), eq(replacement), refEq(defaultOptions))) + .doReturn(Mono.fromCallable { mock() }) + whenever(wrapped.replaceOne(eq(clientSession.wrapped), eq(filter), eq(replacement), eq(options))) + .doReturn(Mono.fromCallable { mock() }) + + runBlocking { + mongoCollection.replaceOne(filter, replacement) + mongoCollection.replaceOne(filter, replacement, options) + mongoCollection.replaceOne(clientSession, filter, replacement) + mongoCollection.replaceOne(clientSession, filter, replacement, options) + } + + verify(wrapped).replaceOne(eq(filter), eq(replacement), refEq(defaultOptions)) + verify(wrapped).replaceOne(eq(filter), eq(replacement), eq(options)) + verify(wrapped).replaceOne(eq(clientSession.wrapped), eq(filter), eq(replacement), refEq(defaultOptions)) + verify(wrapped).replaceOne(eq(clientSession.wrapped), eq(filter), eq(replacement), eq(options)) + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingDeleteOne() { + val mongoCollection = MongoCollection(wrapped) + + val defaultOptions = DeleteOptions() + val options = DeleteOptions().comment("comment") + + whenever(wrapped.deleteOne(eq(filter), refEq(defaultOptions))).doReturn(Mono.fromCallable { mock() }) + whenever(wrapped.deleteOne(eq(filter), eq(options))).doReturn(Mono.fromCallable { mock() }) + whenever(wrapped.deleteOne(eq(clientSession.wrapped), eq(filter), refEq(defaultOptions))) + .doReturn(Mono.fromCallable { mock() }) + whenever(wrapped.deleteOne(eq(clientSession.wrapped), eq(filter), eq(options))) + .doReturn(Mono.fromCallable { mock() }) + + runBlocking { + mongoCollection.deleteOne(filter) + mongoCollection.deleteOne(filter, options) + mongoCollection.deleteOne(clientSession, filter) + mongoCollection.deleteOne(clientSession, filter, options) + } + + verify(wrapped).deleteOne(eq(filter), refEq(defaultOptions)) + verify(wrapped).deleteOne(eq(filter), eq(options)) + verify(wrapped).deleteOne(eq(clientSession.wrapped), eq(filter), refEq(defaultOptions)) + verify(wrapped).deleteOne(eq(clientSession.wrapped), eq(filter), eq(options)) + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingDeleteMany() { + val mongoCollection = MongoCollection(wrapped) + + val defaultOptions = DeleteOptions() + val options = DeleteOptions().comment("comment") + + whenever(wrapped.deleteMany(eq(filter), refEq(defaultOptions))).doReturn(Mono.fromCallable { mock() }) + whenever(wrapped.deleteMany(eq(filter), eq(options))).doReturn(Mono.fromCallable { mock() }) + whenever(wrapped.deleteMany(eq(clientSession.wrapped), eq(filter), refEq(defaultOptions))) + .doReturn(Mono.fromCallable { mock() }) + whenever(wrapped.deleteMany(eq(clientSession.wrapped), eq(filter), eq(options))) + .doReturn(Mono.fromCallable { mock() }) + + runBlocking { + mongoCollection.deleteMany(filter) + mongoCollection.deleteMany(filter, options) + mongoCollection.deleteMany(clientSession, filter) + mongoCollection.deleteMany(clientSession, filter, options) + } + + verify(wrapped).deleteMany(eq(filter), refEq(defaultOptions)) + verify(wrapped).deleteMany(eq(filter), eq(options)) + verify(wrapped).deleteMany(eq(clientSession.wrapped), eq(filter), refEq(defaultOptions)) + verify(wrapped).deleteMany(eq(clientSession.wrapped), eq(filter), eq(options)) + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingFindOneAndDelete() { + val mongoCollection = MongoCollection(wrapped) + + val defaultOptions = FindOneAndDeleteOptions() + val options = FindOneAndDeleteOptions().comment("comment") + + whenever(wrapped.findOneAndDelete(eq(filter), refEq(defaultOptions))).doReturn(Mono.fromCallable { mock() }) + whenever(wrapped.findOneAndDelete(eq(filter), eq(options))).doReturn(Mono.fromCallable { mock() }) + whenever(wrapped.findOneAndDelete(eq(clientSession.wrapped), eq(filter), refEq(defaultOptions))) + .doReturn(Mono.fromCallable { mock() }) + whenever(wrapped.findOneAndDelete(eq(clientSession.wrapped), eq(filter), eq(options))) + .doReturn(Mono.fromCallable { mock() }) + + runBlocking { + mongoCollection.findOneAndDelete(filter) + mongoCollection.findOneAndDelete(filter, options) + mongoCollection.findOneAndDelete(clientSession, filter) + mongoCollection.findOneAndDelete(clientSession, filter, options) + } + + verify(wrapped).findOneAndDelete(eq(filter), refEq(defaultOptions)) + verify(wrapped).findOneAndDelete(eq(filter), eq(options)) + verify(wrapped).findOneAndDelete(eq(clientSession.wrapped), eq(filter), refEq(defaultOptions)) + verify(wrapped).findOneAndDelete(eq(clientSession.wrapped), eq(filter), eq(options)) + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingFindOneAndUpdate() { + val mongoCollection = MongoCollection(wrapped) + val update = Document("u", 1) + val updateList = listOf(update) + val defaultOptions = FindOneAndUpdateOptions() + val options = FindOneAndUpdateOptions().comment("comment") + + whenever(wrapped.findOneAndUpdate(eq(filter), eq(update), refEq(defaultOptions))) + .doReturn(Mono.fromCallable { mock() }) + whenever(wrapped.findOneAndUpdate(eq(filter), eq(update), eq(options))).doReturn(Mono.fromCallable { mock() }) + whenever(wrapped.findOneAndUpdate(eq(filter), eq(updateList), refEq(defaultOptions))) + .doReturn(Mono.fromCallable { mock() }) + whenever(wrapped.findOneAndUpdate(eq(filter), eq(updateList), eq(options))) + .doReturn(Mono.fromCallable { mock() }) + whenever(wrapped.findOneAndUpdate(eq(clientSession.wrapped), eq(filter), eq(update), refEq(defaultOptions))) + .doReturn(Mono.fromCallable { mock() }) + whenever(wrapped.findOneAndUpdate(eq(clientSession.wrapped), eq(filter), eq(update), eq(options))) + .doReturn(Mono.fromCallable { mock() }) + whenever(wrapped.findOneAndUpdate(eq(clientSession.wrapped), eq(filter), eq(updateList), refEq(defaultOptions))) + .doReturn(Mono.fromCallable { mock() }) + whenever(wrapped.findOneAndUpdate(eq(clientSession.wrapped), eq(filter), eq(updateList), eq(options))) + .doReturn(Mono.fromCallable { mock() }) + + runBlocking { + mongoCollection.findOneAndUpdate(filter, update) + mongoCollection.findOneAndUpdate(filter, update, options) + mongoCollection.findOneAndUpdate(filter, updateList) + mongoCollection.findOneAndUpdate(filter, updateList, options) + mongoCollection.findOneAndUpdate(clientSession, filter, update) + mongoCollection.findOneAndUpdate(clientSession, filter, update, options) + mongoCollection.findOneAndUpdate(clientSession, filter, updateList) + mongoCollection.findOneAndUpdate(clientSession, filter, updateList, options) + } + + verify(wrapped).findOneAndUpdate(eq(filter), eq(update), refEq(defaultOptions)) + verify(wrapped).findOneAndUpdate(eq(filter), eq(update), eq(options)) + verify(wrapped).findOneAndUpdate(eq(filter), eq(updateList), refEq(defaultOptions)) + verify(wrapped).findOneAndUpdate(eq(filter), eq(updateList), eq(options)) + verify(wrapped).findOneAndUpdate(eq(clientSession.wrapped), eq(filter), eq(update), refEq(defaultOptions)) + verify(wrapped).findOneAndUpdate(eq(clientSession.wrapped), eq(filter), eq(update), eq(options)) + verify(wrapped).findOneAndUpdate(eq(clientSession.wrapped), eq(filter), eq(updateList), refEq(defaultOptions)) + verify(wrapped).findOneAndUpdate(eq(clientSession.wrapped), eq(filter), eq(updateList), eq(options)) + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingFindOneAndReplace() { + val mongoCollection = MongoCollection(wrapped) + val replacement = Document("u", 1) + val defaultOptions = FindOneAndReplaceOptions() + val options = FindOneAndReplaceOptions().comment("comment") + + whenever(wrapped.findOneAndReplace(eq(filter), eq(replacement), refEq(defaultOptions))) + .doReturn(Mono.fromCallable { mock() }) + whenever(wrapped.findOneAndReplace(eq(filter), eq(replacement), eq(options))) + .doReturn(Mono.fromCallable { mock() }) + whenever( + wrapped.findOneAndReplace( + eq(clientSession.wrapped), eq(filter), eq(replacement), refEq(defaultOptions))) + .doReturn(Mono.fromCallable { mock() }) + whenever(wrapped.findOneAndReplace(eq(clientSession.wrapped), eq(filter), eq(replacement), eq(options))) + .doReturn(Mono.fromCallable { mock() }) + + runBlocking { + mongoCollection.findOneAndReplace(filter, replacement) + mongoCollection.findOneAndReplace(filter, replacement, options) + mongoCollection.findOneAndReplace(clientSession, filter, replacement) + mongoCollection.findOneAndReplace(clientSession, filter, replacement, options) + } + + verify(wrapped).findOneAndReplace(eq(filter), eq(replacement), refEq(defaultOptions)) + verify(wrapped).findOneAndReplace(eq(filter), eq(replacement), eq(options)) + verify(wrapped).findOneAndReplace(eq(clientSession.wrapped), eq(filter), eq(replacement), refEq(defaultOptions)) + verify(wrapped).findOneAndReplace(eq(clientSession.wrapped), eq(filter), eq(replacement), eq(options)) + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingDrop() { + val mongoCollection = MongoCollection(wrapped) + val defaultOptions = DropCollectionOptions() + val options = DropCollectionOptions().encryptedFields(Document()) + + whenever(wrapped.drop(refEq(defaultOptions))).doReturn(Mono.empty()) + whenever(wrapped.drop(options)).doReturn(Mono.empty()) + whenever(wrapped.drop(eq(clientSession.wrapped), refEq(defaultOptions))).doReturn(Mono.empty()) + whenever(wrapped.drop(clientSession.wrapped, options)).doReturn(Mono.empty()) + + runBlocking { + mongoCollection.drop() + mongoCollection.drop(options) + mongoCollection.drop(clientSession) + mongoCollection.drop(clientSession, options) + } + + verify(wrapped).drop(refEq(defaultOptions)) + verify(wrapped).drop(eq(options)) + verify(wrapped).drop(eq(clientSession.wrapped), refEq(defaultOptions)) + verify(wrapped).drop(eq(clientSession.wrapped), eq(options)) + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingCreateIndex() { + val mongoCollection = MongoCollection(wrapped) + val key = Document() + val defaultOptions = IndexOptions() + val options = IndexOptions().name("name") + + whenever(wrapped.createIndex(eq(key), refEq(defaultOptions))).doReturn(Mono.fromCallable { "1" }) + whenever(wrapped.createIndex(eq(key), eq(options))).doReturn(Mono.fromCallable { "2" }) + whenever(wrapped.createIndex(eq(clientSession.wrapped), eq(key), refEq(defaultOptions))) + .doReturn(Mono.fromCallable { "3" }) + whenever(wrapped.createIndex(eq(clientSession.wrapped), eq(key), eq(options))) + .doReturn(Mono.fromCallable { "4" }) + + runBlocking { + assertEquals("1", mongoCollection.createIndex(key)) + assertEquals("2", mongoCollection.createIndex(key, options)) + assertEquals("3", mongoCollection.createIndex(clientSession, key)) + assertEquals("4", mongoCollection.createIndex(clientSession, key, options)) + } + + verify(wrapped).createIndex(eq(key), refEq(defaultOptions)) + verify(wrapped).createIndex(eq(key), eq(options)) + verify(wrapped).createIndex(eq(clientSession.wrapped), eq(key), refEq(defaultOptions)) + verify(wrapped).createIndex(eq(clientSession.wrapped), eq(key), eq(options)) + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingCreateIndexes() { + val mongoCollection = MongoCollection(wrapped) + val indexes = listOf(IndexModel(Document())) + val defaultOptions = CreateIndexOptions() + val options = CreateIndexOptions().commitQuorum(CreateIndexCommitQuorum.MAJORITY) + + whenever(wrapped.createIndexes(eq(indexes), refEq(defaultOptions))).doReturn(Mono.fromCallable { mock() }) + whenever(wrapped.createIndexes(eq(indexes), eq(options))).doReturn(Mono.fromCallable { mock() }) + whenever(wrapped.createIndexes(eq(clientSession.wrapped), eq(indexes), refEq(defaultOptions))) + .doReturn(Mono.fromCallable { mock() }) + whenever(wrapped.createIndexes(eq(clientSession.wrapped), eq(indexes), eq(options))) + .doReturn(Mono.fromCallable { mock() }) + + runBlocking { + mongoCollection.createIndexes(indexes) + mongoCollection.createIndexes(indexes, options) + mongoCollection.createIndexes(clientSession, indexes) + mongoCollection.createIndexes(clientSession, indexes, options) + } + + verify(wrapped).createIndexes(eq(indexes), refEq(defaultOptions)) + verify(wrapped).createIndexes(eq(indexes), eq(options)) + verify(wrapped).createIndexes(eq(clientSession.wrapped), eq(indexes), refEq(defaultOptions)) + verify(wrapped).createIndexes(eq(clientSession.wrapped), eq(indexes), eq(options)) + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingListIndexes() { + val mongoCollection = MongoCollection(wrapped) + + whenever(wrapped.listIndexes(Document::class.java)).doReturn(mock()) + whenever(wrapped.listIndexes(clientSession.wrapped, Document::class.java)).doReturn(mock()) + whenever(wrapped.listIndexes(BsonDocument::class.java)).doReturn(mock()) + whenever(wrapped.listIndexes(clientSession.wrapped, BsonDocument::class.java)).doReturn(mock()) + + mongoCollection.listIndexes() + mongoCollection.listIndexes(clientSession) + + mongoCollection.listIndexes(resultClass = Document::class.java) + mongoCollection.listIndexes(clientSession, Document::class.java) + + mongoCollection.listIndexes() + mongoCollection.listIndexes(clientSession) + + verify(wrapped, times(2)).listIndexes(Document::class.java) + verify(wrapped, times(2)).listIndexes(clientSession.wrapped, Document::class.java) + verify(wrapped, times(1)).listIndexes(BsonDocument::class.java) + verify(wrapped, times(1)).listIndexes(clientSession.wrapped, BsonDocument::class.java) + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingDropIndex() { + val mongoCollection = MongoCollection(wrapped) + val indexName = "index" + val keys = Document() + val defaultOptions = DropIndexOptions() + val options = DropIndexOptions().maxTime(1, TimeUnit.MILLISECONDS) + + whenever(wrapped.dropIndex(eq(indexName), refEq(defaultOptions))).doReturn(Mono.empty()) + whenever(wrapped.dropIndex(eq(indexName), eq(options))).doReturn(Mono.empty()) + whenever(wrapped.dropIndex(eq(keys), refEq(defaultOptions))).doReturn(Mono.empty()) + whenever(wrapped.dropIndex(eq(keys), eq(options))).doReturn(Mono.empty()) + whenever(wrapped.dropIndex(eq(clientSession.wrapped), eq(indexName), refEq(defaultOptions))) + .doReturn(Mono.empty()) + whenever(wrapped.dropIndex(eq(clientSession.wrapped), eq(indexName), eq(options))).doReturn(Mono.empty()) + whenever(wrapped.dropIndex(eq(clientSession.wrapped), eq(keys), refEq(defaultOptions))).doReturn(Mono.empty()) + whenever(wrapped.dropIndex(eq(clientSession.wrapped), eq(keys), eq(options))).doReturn(Mono.empty()) + + runBlocking { + mongoCollection.dropIndex(indexName) + mongoCollection.dropIndex(indexName, options) + mongoCollection.dropIndex(keys) + mongoCollection.dropIndex(keys, options) + mongoCollection.dropIndex(clientSession, indexName) + mongoCollection.dropIndex(clientSession, indexName, options) + mongoCollection.dropIndex(clientSession, keys) + mongoCollection.dropIndex(clientSession, keys, options) + } + + verify(wrapped).dropIndex(eq(indexName), refEq(defaultOptions)) + verify(wrapped).dropIndex(eq(indexName), eq(options)) + verify(wrapped).dropIndex(eq(keys), refEq(defaultOptions)) + verify(wrapped).dropIndex(eq(keys), eq(options)) + verify(wrapped).dropIndex(eq(clientSession.wrapped), eq(indexName), refEq(defaultOptions)) + verify(wrapped).dropIndex(eq(clientSession.wrapped), eq(indexName), eq(options)) + verify(wrapped).dropIndex(eq(clientSession.wrapped), eq(keys), refEq(defaultOptions)) + verify(wrapped).dropIndex(eq(clientSession.wrapped), eq(keys), eq(options)) + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingDropIndexes() { + val mongoCollection = MongoCollection(wrapped) + val defaultOptions = DropIndexOptions() + val options = DropIndexOptions().maxTime(1, TimeUnit.MILLISECONDS) + + whenever(wrapped.dropIndexes(refEq(defaultOptions))).doReturn(Mono.empty()) + whenever(wrapped.dropIndexes(options)).doReturn(Mono.empty()) + whenever(wrapped.dropIndexes(eq(clientSession.wrapped), refEq(defaultOptions))).doReturn(Mono.empty()) + whenever(wrapped.dropIndexes(clientSession.wrapped, options)).doReturn(Mono.empty()) + + runBlocking { + mongoCollection.dropIndexes() + mongoCollection.dropIndexes(options) + mongoCollection.dropIndexes(clientSession) + mongoCollection.dropIndexes(clientSession, options) + } + + verify(wrapped).dropIndexes(refEq(defaultOptions)) + verify(wrapped).dropIndexes(eq(options)) + verify(wrapped).dropIndexes(eq(clientSession.wrapped), refEq(defaultOptions)) + verify(wrapped).dropIndexes(eq(clientSession.wrapped), eq(options)) + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingRenameCollection() { + val mongoCollection = MongoCollection(wrapped) + val mongoNamespace = MongoNamespace("db", "coll") + val defaultOptions = RenameCollectionOptions() + val options = RenameCollectionOptions().dropTarget(true) + + whenever(wrapped.renameCollection(eq(mongoNamespace), refEq(defaultOptions))).doReturn(Mono.empty()) + whenever(wrapped.renameCollection(eq(mongoNamespace), eq(options))).doReturn(Mono.empty()) + whenever(wrapped.renameCollection(eq(clientSession.wrapped), eq(mongoNamespace), refEq(defaultOptions))) + .doReturn(Mono.empty()) + whenever(wrapped.renameCollection(eq(clientSession.wrapped), eq(mongoNamespace), eq(options))) + .doReturn(Mono.empty()) + + runBlocking { + mongoCollection.renameCollection(mongoNamespace) + mongoCollection.renameCollection(mongoNamespace, options) + mongoCollection.renameCollection(clientSession, mongoNamespace) + mongoCollection.renameCollection(clientSession, mongoNamespace, options) + } + + verify(wrapped).renameCollection(eq(mongoNamespace), refEq(defaultOptions)) + verify(wrapped).renameCollection(eq(mongoNamespace), eq(options)) + verify(wrapped).renameCollection(eq(clientSession.wrapped), eq(mongoNamespace), refEq(defaultOptions)) + verify(wrapped).renameCollection(eq(clientSession.wrapped), eq(mongoNamespace), eq(options)) + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldProvideExtensionFunctionsForTimeBasedOptions() { + val oneThousand = 1000L + + assertEquals(1, CreateIndexOptions().maxTime(oneThousand).getMaxTime(TimeUnit.SECONDS)) + assertEquals(1, CountOptions().maxTime(oneThousand).getMaxTime(TimeUnit.SECONDS)) + assertEquals(1, DropIndexOptions().maxTime(oneThousand).getMaxTime(TimeUnit.SECONDS)) + assertEquals(1, EstimatedDocumentCountOptions().maxTime(oneThousand).getMaxTime(TimeUnit.SECONDS)) + assertEquals(1, FindOneAndDeleteOptions().maxTime(oneThousand).getMaxTime(TimeUnit.SECONDS)) + assertEquals(1, FindOneAndReplaceOptions().maxTime(oneThousand).getMaxTime(TimeUnit.SECONDS)) + assertEquals(1, FindOneAndUpdateOptions().maxTime(oneThousand).getMaxTime(TimeUnit.SECONDS)) + assertEquals(oneThousand, IndexOptions().expireAfter(oneThousand).getExpireAfter(TimeUnit.SECONDS)) + } +} diff --git a/driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/MongoDatabaseTest.kt b/driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/MongoDatabaseTest.kt new file mode 100644 index 00000000000..4ba7502bd24 --- /dev/null +++ b/driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/MongoDatabaseTest.kt @@ -0,0 +1,408 @@ +/* + * 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.ReadConcern +import com.mongodb.ReadPreference +import com.mongodb.WriteConcern +import com.mongodb.client.model.Collation +import com.mongodb.client.model.CreateCollectionOptions +import com.mongodb.client.model.CreateViewOptions +import com.mongodb.client.model.ValidationAction +import com.mongodb.client.model.ValidationOptions +import com.mongodb.kotlin.client.coroutine.MockitoHelper.deepRefEq +import com.mongodb.reactivestreams.client.MongoDatabase as JMongoDatabase +import java.util.concurrent.TimeUnit +import kotlin.reflect.full.declaredFunctions +import kotlin.reflect.full.declaredMemberProperties +import kotlin.test.assertEquals +import kotlinx.coroutines.runBlocking +import org.bson.BsonDocument +import org.bson.Document +import org.bson.codecs.configuration.CodecRegistry +import org.junit.jupiter.api.Test +import org.mockito.Mock +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.refEq +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoMoreInteractions +import org.mockito.kotlin.whenever +import reactor.core.publisher.Mono + +class MongoDatabaseTest { + + @Mock val wrapped: JMongoDatabase = mock() + @Mock val clientSession: ClientSession = ClientSession(mock()) + + @Test + fun shouldHaveTheSameMethods() { + val jMongoDatabaseFunctions = JMongoDatabase::class.declaredFunctions.map { it.name }.toSet() + val kMongoDatabaseFunctions = + MongoDatabase::class.declaredFunctions.map { it.name }.toSet() + + MongoDatabase::class + .declaredMemberProperties + .filterNot { it.name == "wrapped" } + .map { "get${it.name.replaceFirstChar{c -> c.uppercaseChar() }}" } + + assertEquals(jMongoDatabaseFunctions, kMongoDatabaseFunctions) + } + + @Test + fun shouldCallTheUnderlyingGetNamespace() { + val mongoDatabase = MongoDatabase(wrapped) + whenever(wrapped.name).doReturn("name") + + mongoDatabase.name + verify(wrapped).name + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingGetCodecRegistry() { + val mongoDatabase = MongoDatabase(wrapped) + whenever(wrapped.codecRegistry).doReturn(mock()) + + mongoDatabase.codecRegistry + verify(wrapped).codecRegistry + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingGetReadPreference() { + val mongoDatabase = MongoDatabase(wrapped) + whenever(wrapped.readPreference).doReturn(mock()) + + mongoDatabase.readPreference + verify(wrapped).readPreference + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingGetReadConcern() { + val mongoDatabase = MongoDatabase(wrapped) + whenever(wrapped.readConcern).doReturn(ReadConcern.DEFAULT) + + mongoDatabase.readConcern + verify(wrapped).readConcern + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingGetWriteConcern() { + val mongoDatabase = MongoDatabase(wrapped) + whenever(wrapped.writeConcern).doReturn(mock()) + + mongoDatabase.writeConcern + verify(wrapped).writeConcern + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingWithCodecRegistry() { + val mongoDatabase = MongoDatabase(wrapped) + val codecRegistry = mock() + whenever(wrapped.withCodecRegistry(codecRegistry)).doReturn(mock()) + + mongoDatabase.withCodecRegistry(codecRegistry) + verify(wrapped).withCodecRegistry(codecRegistry) + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingWithReadPreference() { + val mongoDatabase = MongoDatabase(wrapped) + val readPreference = ReadPreference.primaryPreferred() + whenever(wrapped.withReadPreference(readPreference)).doReturn(mock()) + + mongoDatabase.withReadPreference(readPreference) + verify(wrapped).withReadPreference(readPreference) + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingWithReadConcern() { + val mongoDatabase = MongoDatabase(wrapped) + val readConcern = ReadConcern.AVAILABLE + whenever(wrapped.withReadConcern(readConcern)).doReturn(mock()) + + mongoDatabase.withReadConcern(readConcern) + verify(wrapped).withReadConcern(readConcern) + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingWithWriteConcern() { + val mongoDatabase = MongoDatabase(wrapped) + val writeConcern = WriteConcern.MAJORITY + whenever(wrapped.withWriteConcern(writeConcern)).doReturn(mock()) + + mongoDatabase.withWriteConcern(writeConcern) + verify(wrapped).withWriteConcern(writeConcern) + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingGetCollection() { + val mongoDatabase = MongoDatabase(wrapped) + whenever(wrapped.getCollection("collectionName", Document::class.java)).doReturn(mock()) + + mongoDatabase.getCollection("collectionName") + verify(wrapped).getCollection("collectionName", Document::class.java) + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingRunCommand() { + val mongoDatabase = MongoDatabase(wrapped) + val command = Document(mapOf("a" to 1)) + val primary = ReadPreference.primary() + val primaryPreferred = ReadPreference.primaryPreferred() + + whenever(wrapped.readPreference).doReturn(primary) + whenever(wrapped.runCommand(command, primary, Document::class.java)).doReturn(Mono.fromCallable { Document() }) + whenever(wrapped.runCommand(clientSession.wrapped, command, primary, Document::class.java)) + .doReturn(Mono.fromCallable { Document() }) + whenever(wrapped.runCommand(command, primary, BsonDocument::class.java)) + .doReturn(Mono.fromCallable { BsonDocument() }) + whenever(wrapped.runCommand(clientSession.wrapped, command, primary, BsonDocument::class.java)) + .doReturn(Mono.fromCallable { BsonDocument() }) + whenever(wrapped.runCommand(command, primaryPreferred, BsonDocument::class.java)) + .doReturn(Mono.fromCallable { BsonDocument() }) + whenever(wrapped.runCommand(clientSession.wrapped, command, primaryPreferred, BsonDocument::class.java)) + .doReturn(Mono.fromCallable { BsonDocument() }) + + runBlocking { + mongoDatabase.runCommand(command) + mongoDatabase.runCommand(command, primary) + mongoDatabase.runCommand(command, resultClass = Document::class.java) + mongoDatabase.runCommand(command, primary, Document::class.java) + + mongoDatabase.runCommand(clientSession, command) + mongoDatabase.runCommand(clientSession, command, primary) + mongoDatabase.runCommand(clientSession, command, resultClass = Document::class.java) + mongoDatabase.runCommand(clientSession, command, primary, Document::class.java) + + mongoDatabase.runCommand(command) + mongoDatabase.runCommand(command, primaryPreferred) + mongoDatabase.runCommand(clientSession, command) + mongoDatabase.runCommand(clientSession, command, primaryPreferred) + } + + verify(wrapped, times(6)).readPreference + verify(wrapped, times(4)).runCommand(command, primary, Document::class.java) + verify(wrapped, times(4)).runCommand(clientSession.wrapped, command, primary, Document::class.java) + verify(wrapped, times(1)).runCommand(command, primary, BsonDocument::class.java) + verify(wrapped, times(1)).runCommand(clientSession.wrapped, command, primary, BsonDocument::class.java) + verify(wrapped, times(1)).runCommand(command, primaryPreferred, BsonDocument::class.java) + verify(wrapped, times(1)).runCommand(clientSession.wrapped, command, primaryPreferred, BsonDocument::class.java) + + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingDrop() { + val mongoDatabase = MongoDatabase(wrapped) + + whenever(wrapped.drop()).doReturn(Mono.empty()) + whenever(wrapped.drop(clientSession.wrapped)).doReturn(Mono.empty()) + + runBlocking { + mongoDatabase.drop() + mongoDatabase.drop(clientSession) + } + + verify(wrapped).drop() + verify(wrapped).drop(clientSession.wrapped) + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingListCollectionNames() { + val mongoDatabase = MongoDatabase(wrapped) + whenever(wrapped.listCollectionNames()).doReturn(mock()) + whenever(wrapped.listCollectionNames(clientSession.wrapped)).doReturn(mock()) + + mongoDatabase.listCollectionNames() + mongoDatabase.listCollectionNames(clientSession) + + verify(wrapped).listCollectionNames() + verify(wrapped).listCollectionNames(clientSession.wrapped) + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingListCollections() { + val mongoDatabase = MongoDatabase(wrapped) + whenever(wrapped.listCollections(Document::class.java)).doReturn(mock()) + whenever(wrapped.listCollections(BsonDocument::class.java)).doReturn(mock()) + whenever(wrapped.listCollections(clientSession.wrapped, Document::class.java)).doReturn(mock()) + whenever(wrapped.listCollections(clientSession.wrapped, BsonDocument::class.java)).doReturn(mock()) + + mongoDatabase.listCollections() + mongoDatabase.listCollections(clientSession) + + mongoDatabase.listCollections(resultClass = Document::class.java) + mongoDatabase.listCollections(clientSession, Document::class.java) + + mongoDatabase.listCollections() + mongoDatabase.listCollections(clientSession) + + verify(wrapped, times(2)).listCollections(Document::class.java) + verify(wrapped, times(2)).listCollections(clientSession.wrapped, Document::class.java) + verify(wrapped, times(1)).listCollections(BsonDocument::class.java) + verify(wrapped, times(1)).listCollections(clientSession.wrapped, BsonDocument::class.java) + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingCreateCollection() { + val mongoDatabase = MongoDatabase(wrapped) + val name = "coll" + val name2 = "coll2" + val defaultOptions = CreateCollectionOptions() + val options = + CreateCollectionOptions().validationOptions(ValidationOptions().validationAction(ValidationAction.WARN)) + + whenever(wrapped.createCollection(eq(name), deepRefEq(defaultOptions))).doReturn(Mono.empty()) + whenever(wrapped.createCollection(eq(name2), eq(options))).doReturn(Mono.empty()) + whenever(wrapped.createCollection(eq(clientSession.wrapped), eq(name), deepRefEq(defaultOptions))) + .doReturn(Mono.empty()) + whenever(wrapped.createCollection(eq(clientSession.wrapped), eq(name2), eq(options))).doReturn(Mono.empty()) + + runBlocking { + mongoDatabase.createCollection(name) + mongoDatabase.createCollection(name2, options) + mongoDatabase.createCollection(clientSession, name) + mongoDatabase.createCollection(clientSession, name2, options) + } + + verify(wrapped).createCollection(eq(name), deepRefEq(defaultOptions)) + verify(wrapped).createCollection(eq(name2), eq(options)) + verify(wrapped).createCollection(eq(clientSession.wrapped), eq(name), deepRefEq(defaultOptions)) + verify(wrapped).createCollection(eq(clientSession.wrapped), eq(name2), eq(options)) + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingCreateView() { + val mongoDatabase = MongoDatabase(wrapped) + val viewName = "view" + val viewOn = "coll" + val pipeline = listOf(Document(mapOf("a" to 1))) + val defaultOptions = CreateViewOptions() + val options = CreateViewOptions().collation(Collation.builder().backwards(true).build()) + + whenever(wrapped.createView(eq(viewName), eq(viewOn), eq(pipeline), refEq(defaultOptions))) + .doReturn(Mono.empty()) + whenever(wrapped.createView(eq(viewName), eq(viewOn), eq(pipeline), eq(options))).doReturn(Mono.empty()) + whenever( + wrapped.createView( + eq(clientSession.wrapped), eq(viewName), eq(viewOn), eq(pipeline), refEq(defaultOptions))) + .doReturn(Mono.empty()) + whenever(wrapped.createView(eq(clientSession.wrapped), eq(viewName), eq(viewOn), eq(pipeline), eq(options))) + .doReturn(Mono.empty()) + + runBlocking { + mongoDatabase.createView(viewName, viewOn, pipeline) + mongoDatabase.createView(viewName, viewOn, pipeline, options) + mongoDatabase.createView(clientSession, viewName, viewOn, pipeline) + mongoDatabase.createView(clientSession, viewName, viewOn, pipeline, options) + } + + verify(wrapped).createView(eq(viewName), eq(viewOn), eq(pipeline), refEq(defaultOptions)) + verify(wrapped).createView(eq(viewName), eq(viewOn), eq(pipeline), eq(options)) + verify(wrapped) + .createView(eq(clientSession.wrapped), eq(viewName), eq(viewOn), eq(pipeline), refEq(defaultOptions)) + verify(wrapped).createView(eq(clientSession.wrapped), eq(viewName), eq(viewOn), eq(pipeline), eq(options)) + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingAggregate() { + val mongoDatabase = MongoDatabase(wrapped) + val pipeline = listOf(Document(mapOf("a" to 1))) + + whenever(wrapped.aggregate(pipeline, Document::class.java)).doReturn(mock()) + whenever(wrapped.aggregate(clientSession.wrapped, pipeline, Document::class.java)).doReturn(mock()) + whenever(wrapped.aggregate(pipeline, BsonDocument::class.java)).doReturn(mock()) + whenever(wrapped.aggregate(clientSession.wrapped, pipeline, BsonDocument::class.java)).doReturn(mock()) + + mongoDatabase.aggregate(pipeline) + mongoDatabase.aggregate(clientSession, pipeline) + + mongoDatabase.aggregate(pipeline, resultClass = Document::class.java) + mongoDatabase.aggregate(clientSession, pipeline, Document::class.java) + + mongoDatabase.aggregate(pipeline) + mongoDatabase.aggregate(clientSession, pipeline) + + verify(wrapped, times(2)).aggregate(pipeline, Document::class.java) + verify(wrapped, times(2)).aggregate(clientSession.wrapped, pipeline, Document::class.java) + verify(wrapped, times(1)).aggregate(pipeline, BsonDocument::class.java) + verify(wrapped, times(1)).aggregate(clientSession.wrapped, pipeline, BsonDocument::class.java) + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldCallTheUnderlyingWatch() { + val mongoDatabase = MongoDatabase(wrapped) + val pipeline = listOf(Document(mapOf("a" to 1))) + + whenever(wrapped.watch(emptyList(), Document::class.java)).doReturn(mock()) + whenever(wrapped.watch(pipeline, Document::class.java)).doReturn(mock()) + whenever(wrapped.watch(clientSession.wrapped, emptyList(), Document::class.java)).doReturn(mock()) + whenever(wrapped.watch(clientSession.wrapped, pipeline, Document::class.java)).doReturn(mock()) + whenever(wrapped.watch(emptyList(), BsonDocument::class.java)).doReturn(mock()) + whenever(wrapped.watch(pipeline, BsonDocument::class.java)).doReturn(mock()) + whenever(wrapped.watch(clientSession.wrapped, emptyList(), BsonDocument::class.java)).doReturn(mock()) + whenever(wrapped.watch(clientSession.wrapped, pipeline, BsonDocument::class.java)).doReturn(mock()) + + mongoDatabase.watch() + mongoDatabase.watch(pipeline) + mongoDatabase.watch(clientSession) + mongoDatabase.watch(clientSession, pipeline) + + mongoDatabase.watch(resultClass = Document::class.java) + mongoDatabase.watch(pipeline, Document::class.java) + mongoDatabase.watch(clientSession, resultClass = Document::class.java) + mongoDatabase.watch(clientSession, pipeline, Document::class.java) + + mongoDatabase.watch() + mongoDatabase.watch(pipeline) + mongoDatabase.watch(clientSession) + mongoDatabase.watch(clientSession, pipeline) + + verify(wrapped, times(2)).watch(emptyList(), Document::class.java) + verify(wrapped, times(2)).watch(pipeline, Document::class.java) + verify(wrapped, times(2)).watch(clientSession.wrapped, emptyList(), Document::class.java) + verify(wrapped, times(2)).watch(clientSession.wrapped, pipeline, Document::class.java) + verify(wrapped, times(1)).watch(emptyList(), BsonDocument::class.java) + verify(wrapped, times(1)).watch(pipeline, BsonDocument::class.java) + verify(wrapped, times(1)).watch(clientSession.wrapped, emptyList(), BsonDocument::class.java) + verify(wrapped, times(1)).watch(clientSession.wrapped, pipeline, BsonDocument::class.java) + verifyNoMoreInteractions(wrapped) + } + + @Test + fun shouldProvideExtensionFunctionsForTimeBasedOptions() { + val oneThousand = 1000L + + assertEquals(oneThousand, CreateCollectionOptions().expireAfter(oneThousand).getExpireAfter(TimeUnit.SECONDS)) + } +} diff --git a/settings.gradle b/settings.gradle index 7ef5498b954..784bd0aa6ad 100644 --- a/settings.gradle +++ b/settings.gradle @@ -24,6 +24,7 @@ include ':driver-sync' include ':driver-reactive-streams' include ':bson-kotlin' include ':driver-kotlin-sync' +include ':driver-kotlin-coroutine' include ':bson-scala' include ':driver-scala' include 'util:spock'