From a19fad2b3d93ee818132f14377e42cfaa268e8ee Mon Sep 17 00:00:00 2001 From: Jeff Yemin Date: Wed, 22 Feb 2023 10:46:26 -0500 Subject: [PATCH 01/11] Make ClientEncryptionSettings#keyVaultMongoClientSettings non-nullable (#1087) * Update Javadoc * Add notNull assertion in ClientEncryptionSettings constructor * Remove unnecessary null checks and corresponding SpotBugs exclusions for them JAVA-4861 --- config/spotbugs/exclude.xml | 14 -------------- .../com/mongodb/ClientEncryptionSettings.java | 18 ++++++------------ .../internal/capi/MongoCryptHelperTest.java | 2 ++ .../internal/vault/ClientEncryptionImpl.java | 8 ++++---- .../client/internal/ClientEncryptionImpl.java | 8 ++++---- .../com/mongodb/client/unified/Entities.java | 2 ++ 6 files changed, 18 insertions(+), 34 deletions(-) diff --git a/config/spotbugs/exclude.xml b/config/spotbugs/exclude.xml index 8cfa95fb4ec..2a8b69a2e7b 100644 --- a/config/spotbugs/exclude.xml +++ b/config/spotbugs/exclude.xml @@ -240,18 +240,4 @@ - - - - - - - - - - - - diff --git a/driver-core/src/main/com/mongodb/ClientEncryptionSettings.java b/driver-core/src/main/com/mongodb/ClientEncryptionSettings.java index 1367547b6c3..2df4b3363d4 100644 --- a/driver-core/src/main/com/mongodb/ClientEncryptionSettings.java +++ b/driver-core/src/main/com/mongodb/ClientEncryptionSettings.java @@ -55,14 +55,14 @@ public static final class Builder { private Map kmsProviderSslContextMap = new HashMap<>(); /** - * Sets the key vault settings. + * Sets the {@link MongoClientSettings} that will be used to access the key vault. * - * @param keyVaultMongoClientSettings the key vault mongo client settings, which may be null. + * @param keyVaultMongoClientSettings the key vault mongo client settings, which may not be null. * @return this * @see #getKeyVaultMongoClientSettings() */ public Builder keyVaultMongoClientSettings(final MongoClientSettings keyVaultMongoClientSettings) { - this.keyVaultMongoClientSettings = keyVaultMongoClientSettings; + this.keyVaultMongoClientSettings = notNull("keyVaultMongoClientSettings", keyVaultMongoClientSettings); return this; } @@ -143,15 +143,9 @@ public static Builder builder() { } /** - * Gets the key vault settings. + * Gets the {@link MongoClientSettings} that will be used to access the key vault. * - *

- * The key vault collection is assumed to reside on the same MongoDB cluster as indicated by the connecting URI. But the optional - * keyVaultMongoClientSettings can be used to route data key queries to a separate MongoDB cluster, or the same cluster but with a - * different credential. - *

- * @return the key vault settings, which may be null to indicate that the same {@code MongoClient} should be used to access the key - * vault collection as is used for the rest of the application. + * @return the key vault settings, which may be not be null */ public MongoClientSettings getKeyVaultMongoClientSettings() { return keyVaultMongoClientSettings; @@ -260,7 +254,7 @@ public Map getKmsProviderSslContextMap() { } private ClientEncryptionSettings(final Builder builder) { - this.keyVaultMongoClientSettings = builder.keyVaultMongoClientSettings; + this.keyVaultMongoClientSettings = notNull("keyVaultMongoClientSettings", builder.keyVaultMongoClientSettings); this.keyVaultNamespace = notNull("keyVaultNamespace", builder.keyVaultNamespace); this.kmsProviders = notNull("kmsProviders", builder.kmsProviders); this.kmsProviderPropertySuppliers = notNull("kmsProviderPropertySuppliers", builder.kmsProviderPropertySuppliers); diff --git a/driver-core/src/test/functional/com/mongodb/internal/capi/MongoCryptHelperTest.java b/driver-core/src/test/functional/com/mongodb/internal/capi/MongoCryptHelperTest.java index 9feac3e57a8..c7d82748efb 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/capi/MongoCryptHelperTest.java +++ b/driver-core/src/test/functional/com/mongodb/internal/capi/MongoCryptHelperTest.java @@ -19,6 +19,7 @@ import com.mongodb.AutoEncryptionSettings; import com.mongodb.ClientEncryptionSettings; import com.mongodb.MongoClientException; +import com.mongodb.MongoClientSettings; import com.mongodb.client.model.vault.RewrapManyDataKeyOptions; import com.mongodb.crypt.capi.MongoCryptOptions; import org.bson.BsonDocument; @@ -52,6 +53,7 @@ public void createsExpectedMongoCryptOptionsUsingClientEncryptionSettings() { ClientEncryptionSettings settings = ClientEncryptionSettings .builder() .kmsProviders(kmsProvidersRaw) + .keyVaultMongoClientSettings(MongoClientSettings.builder().build()) .keyVaultNamespace("a.b") .build(); MongoCryptOptions mongoCryptOptions = MongoCryptHelper.createMongoCryptOptions(settings); diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/vault/ClientEncryptionImpl.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/vault/ClientEncryptionImpl.java index 5b4331fa982..b6c3cb73c61 100644 --- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/vault/ClientEncryptionImpl.java +++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/vault/ClientEncryptionImpl.java @@ -17,7 +17,6 @@ package com.mongodb.reactivestreams.client.internal.vault; import com.mongodb.ClientEncryptionSettings; -import com.mongodb.MongoClientSettings; import com.mongodb.MongoConfigurationException; import com.mongodb.MongoNamespace; import com.mongodb.MongoUpdatedEncryptedFieldsException; @@ -33,6 +32,7 @@ import com.mongodb.client.model.vault.RewrapManyDataKeyOptions; import com.mongodb.client.model.vault.RewrapManyDataKeyResult; import com.mongodb.client.result.DeleteResult; +import com.mongodb.internal.VisibleForTesting; import com.mongodb.reactivestreams.client.FindPublisher; import com.mongodb.reactivestreams.client.MongoClient; import com.mongodb.reactivestreams.client.MongoClients; @@ -59,6 +59,7 @@ import java.util.stream.Collectors; import static com.mongodb.assertions.Assertions.notNull; +import static com.mongodb.internal.VisibleForTesting.AccessModifier.PRIVATE; import static com.mongodb.internal.capi.MongoCryptHelper.validateRewrapManyDataKeyOptions; import static java.lang.String.format; import static java.util.Arrays.asList; @@ -78,6 +79,7 @@ public ClientEncryptionImpl(final ClientEncryptionSettings options) { this(MongoClients.create(options.getKeyVaultMongoClientSettings()), options); } + @VisibleForTesting(otherwise = PRIVATE) public ClientEncryptionImpl(final MongoClient keyVaultClient, final ClientEncryptionSettings options) { this.keyVaultClient = keyVaultClient; this.crypt = Crypts.create(keyVaultClient, options); @@ -208,9 +210,7 @@ public Publisher createEncryptedCollection(final MongoDatabase dat if (rawEncryptedFields == null) { throw new MongoConfigurationException(format("`encryptedFields` is not configured for the collection %s.", namespace)); } - CodecRegistry codecRegistry = options.getKeyVaultMongoClientSettings() == null - ? MongoClientSettings.getDefaultCodecRegistry() - : options.getKeyVaultMongoClientSettings().getCodecRegistry(); + CodecRegistry codecRegistry = options.getKeyVaultMongoClientSettings().getCodecRegistry(); BsonDocument encryptedFields = rawEncryptedFields.toBsonDocument(BsonDocument.class, codecRegistry); BsonValue fields = encryptedFields.get("fields"); if (fields != null && fields.isArray()) { diff --git a/driver-sync/src/main/com/mongodb/client/internal/ClientEncryptionImpl.java b/driver-sync/src/main/com/mongodb/client/internal/ClientEncryptionImpl.java index b8462f058c2..fad1c711d64 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/ClientEncryptionImpl.java +++ b/driver-sync/src/main/com/mongodb/client/internal/ClientEncryptionImpl.java @@ -17,7 +17,6 @@ package com.mongodb.client.internal; import com.mongodb.ClientEncryptionSettings; -import com.mongodb.MongoClientSettings; import com.mongodb.MongoConfigurationException; import com.mongodb.MongoNamespace; import com.mongodb.MongoUpdatedEncryptedFieldsException; @@ -39,6 +38,7 @@ import com.mongodb.client.model.vault.RewrapManyDataKeyResult; import com.mongodb.client.result.DeleteResult; import com.mongodb.client.vault.ClientEncryption; +import com.mongodb.internal.VisibleForTesting; import org.bson.BsonArray; import org.bson.BsonBinary; import org.bson.BsonDocument; @@ -54,6 +54,7 @@ import java.util.stream.Collectors; import static com.mongodb.assertions.Assertions.notNull; +import static com.mongodb.internal.VisibleForTesting.AccessModifier.PRIVATE; import static com.mongodb.internal.capi.MongoCryptHelper.validateRewrapManyDataKeyOptions; import static java.lang.String.format; import static java.util.Arrays.asList; @@ -73,6 +74,7 @@ public ClientEncryptionImpl(final ClientEncryptionSettings options) { this(MongoClients.create(options.getKeyVaultMongoClientSettings()), options); } + @VisibleForTesting(otherwise = PRIVATE) public ClientEncryptionImpl(final MongoClient keyVaultClient, final ClientEncryptionSettings options) { this.keyVaultClient = keyVaultClient; this.crypt = Crypts.create(keyVaultClient, options); @@ -195,9 +197,7 @@ public BsonDocument createEncryptedCollection(final MongoDatabase database, fina if (rawEncryptedFields == null) { throw new MongoConfigurationException(format("`encryptedFields` is not configured for the collection %s.", namespace)); } - CodecRegistry codecRegistry = options.getKeyVaultMongoClientSettings() == null - ? MongoClientSettings.getDefaultCodecRegistry() - : options.getKeyVaultMongoClientSettings().getCodecRegistry(); + CodecRegistry codecRegistry = options.getKeyVaultMongoClientSettings().getCodecRegistry(); BsonDocument encryptedFields = rawEncryptedFields.toBsonDocument(BsonDocument.class, codecRegistry); BsonValue fields = encryptedFields.get("fields"); if (fields != null && fields.isArray()) { diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/Entities.java b/driver-sync/src/test/functional/com/mongodb/client/unified/Entities.java index 0dd1041117b..b3838000006 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/unified/Entities.java +++ b/driver-sync/src/test/functional/com/mongodb/client/unified/Entities.java @@ -596,6 +596,8 @@ private void initClientEncryption(final BsonDocument entity, final String id, MongoClient mongoClient = null; ClientEncryptionSettings.Builder builder = ClientEncryptionSettings.builder(); + // this is ignored in preference to the keyVaultClient, but required to be non-null in the ClientEncryptionSettings constructor + builder.keyVaultMongoClientSettings(MongoClientSettings.builder().build()); for (Map.Entry entry : clientEncryptionOpts.entrySet()) { switch (entry.getKey()) { case "keyVaultClient": From e9a4bd8a498097553661201f5486f7f54a41920e Mon Sep 17 00:00:00 2001 From: Jeff Yemin Date: Fri, 3 Mar 2023 13:06:44 -0500 Subject: [PATCH 02/11] Sync specification tests JAVA-4898 --- .../legacy/fle2-CreateCollection.json | 252 +++++++++--------- ...EncryptedFields-vs-EncryptedFieldsMap.json | 6 +- 2 files changed, 129 insertions(+), 129 deletions(-) diff --git a/driver-core/src/test/resources/client-side-encryption/legacy/fle2-CreateCollection.json b/driver-core/src/test/resources/client-side-encryption/legacy/fle2-CreateCollection.json index 9f8db41f871..7f4f38161e1 100644 --- a/driver-core/src/test/resources/client-side-encryption/legacy/fle2-CreateCollection.json +++ b/driver-core/src/test/resources/client-side-encryption/legacy/fle2-CreateCollection.json @@ -21,9 +21,9 @@ }, "encryptedFieldsMap": { "default.encryptedCollection": { - "escCollection": "encryptedCollection.esc", - "eccCollection": "encryptedCollection.ecc", - "ecocCollection": "encryptedCollection.ecoc", + "escCollection": "enxcol_.encryptedCollection.esc", + "eccCollection": "enxcol_.encryptedCollection.ecc", + "ecocCollection": "enxcol_.encryptedCollection.ecoc", "fields": [ { "path": "firstName", @@ -60,7 +60,7 @@ "object": "testRunner", "arguments": { "database": "default", - "collection": "encryptedCollection.esc" + "collection": "enxcol_.encryptedCollection.esc" } }, { @@ -68,7 +68,7 @@ "object": "testRunner", "arguments": { "database": "default", - "collection": "encryptedCollection.ecc" + "collection": "enxcol_.encryptedCollection.ecc" } }, { @@ -76,7 +76,7 @@ "object": "testRunner", "arguments": { "database": "default", - "collection": "encryptedCollection.ecoc" + "collection": "enxcol_.encryptedCollection.ecoc" } }, { @@ -101,7 +101,7 @@ { "command_started_event": { "command": { - "drop": "encryptedCollection.esc" + "drop": "enxcol_.encryptedCollection.esc" }, "command_name": "drop", "database_name": "default" @@ -110,7 +110,7 @@ { "command_started_event": { "command": { - "drop": "encryptedCollection.ecc" + "drop": "enxcol_.encryptedCollection.ecc" }, "command_name": "drop", "database_name": "default" @@ -119,7 +119,7 @@ { "command_started_event": { "command": { - "drop": "encryptedCollection.ecoc" + "drop": "enxcol_.encryptedCollection.ecoc" }, "command_name": "drop", "database_name": "default" @@ -137,7 +137,7 @@ { "command_started_event": { "command": { - "create": "encryptedCollection.esc", + "create": "enxcol_.encryptedCollection.esc", "clusteredIndex": { "key": { "_id": 1 @@ -152,7 +152,7 @@ { "command_started_event": { "command": { - "create": "encryptedCollection.ecc", + "create": "enxcol_.encryptedCollection.ecc", "clusteredIndex": { "key": { "_id": 1 @@ -167,7 +167,7 @@ { "command_started_event": { "command": { - "create": "encryptedCollection.ecoc", + "create": "enxcol_.encryptedCollection.ecoc", "clusteredIndex": { "key": { "_id": 1 @@ -184,9 +184,9 @@ "command": { "create": "encryptedCollection", "encryptedFields": { - "escCollection": "encryptedCollection.esc", - "eccCollection": "encryptedCollection.ecc", - "ecocCollection": "encryptedCollection.ecoc", + "escCollection": "enxcol_.encryptedCollection.esc", + "eccCollection": "enxcol_.encryptedCollection.ecc", + "ecocCollection": "enxcol_.encryptedCollection.ecoc", "fields": [ { "path": "firstName", @@ -745,9 +745,9 @@ }, "encryptedFieldsMap": { "default.encryptedCollection": { - "escCollection": "encryptedCollection.esc", - "eccCollection": "encryptedCollection.ecc", - "ecocCollection": "encryptedCollection.ecoc", + "escCollection": "enxcol_.encryptedCollection.esc", + "eccCollection": "enxcol_.encryptedCollection.ecc", + "ecocCollection": "enxcol_.encryptedCollection.ecoc", "fields": [ { "path": "firstName", @@ -762,9 +762,9 @@ ] }, "default.encryptedCollection.esc": { - "escCollection": "encryptedCollection", - "eccCollection": "encryptedCollection.ecc", - "ecocCollection": "encryptedCollection.ecoc", + "escCollection": "enxcol_.encryptedCollection.esc", + "eccCollection": "enxcol_.encryptedCollection.ecc", + "ecocCollection": "enxcol_.encryptedCollection.ecoc", "fields": [ { "path": "firstName", @@ -801,7 +801,7 @@ "object": "testRunner", "arguments": { "database": "default", - "collection": "encryptedCollection.esc" + "collection": "enxcol_.encryptedCollection.esc" } }, { @@ -809,7 +809,7 @@ "object": "testRunner", "arguments": { "database": "default", - "collection": "encryptedCollection.ecc" + "collection": "enxcol_.encryptedCollection.ecc" } }, { @@ -817,7 +817,7 @@ "object": "testRunner", "arguments": { "database": "default", - "collection": "encryptedCollection.ecoc" + "collection": "enxcol_.encryptedCollection.ecoc" } }, { @@ -842,7 +842,7 @@ { "command_started_event": { "command": { - "drop": "encryptedCollection.esc" + "drop": "enxcol_.encryptedCollection.esc" }, "command_name": "drop", "database_name": "default" @@ -851,7 +851,7 @@ { "command_started_event": { "command": { - "drop": "encryptedCollection.ecc" + "drop": "enxcol_.encryptedCollection.ecc" }, "command_name": "drop", "database_name": "default" @@ -860,7 +860,7 @@ { "command_started_event": { "command": { - "drop": "encryptedCollection.ecoc" + "drop": "enxcol_.encryptedCollection.ecoc" }, "command_name": "drop", "database_name": "default" @@ -878,7 +878,7 @@ { "command_started_event": { "command": { - "create": "encryptedCollection.esc", + "create": "enxcol_.encryptedCollection.esc", "clusteredIndex": { "key": { "_id": 1 @@ -893,7 +893,7 @@ { "command_started_event": { "command": { - "create": "encryptedCollection.ecc", + "create": "enxcol_.encryptedCollection.ecc", "clusteredIndex": { "key": { "_id": 1 @@ -908,7 +908,7 @@ { "command_started_event": { "command": { - "create": "encryptedCollection.ecoc", + "create": "enxcol_.encryptedCollection.ecoc", "clusteredIndex": { "key": { "_id": 1 @@ -925,9 +925,9 @@ "command": { "create": "encryptedCollection", "encryptedFields": { - "escCollection": "encryptedCollection.esc", - "eccCollection": "encryptedCollection.ecc", - "ecocCollection": "encryptedCollection.ecoc", + "escCollection": "enxcol_.encryptedCollection.esc", + "eccCollection": "enxcol_.encryptedCollection.ecc", + "ecocCollection": "enxcol_.encryptedCollection.ecoc", "fields": [ { "path": "firstName", @@ -974,9 +974,9 @@ }, "encryptedFieldsMap": { "default.encryptedCollection": { - "escCollection": "encryptedCollection.esc", - "eccCollection": "encryptedCollection.ecc", - "ecocCollection": "encryptedCollection.ecoc", + "escCollection": "enxcol_.encryptedCollection.esc", + "eccCollection": "enxcol_.encryptedCollection.ecc", + "ecocCollection": "enxcol_.encryptedCollection.ecoc", "fields": [ { "path": "firstName", @@ -1059,9 +1059,9 @@ }, "encryptedFieldsMap": { "default.encryptedCollection": { - "escCollection": "encryptedCollection.esc", - "eccCollection": "encryptedCollection.ecc", - "ecocCollection": "encryptedCollection.ecoc", + "escCollection": "enxcol_.encryptedCollection.esc", + "eccCollection": "enxcol_.encryptedCollection.ecc", + "ecocCollection": "enxcol_.encryptedCollection.ecoc", "fields": [ { "path": "firstName", @@ -1098,7 +1098,7 @@ "object": "testRunner", "arguments": { "database": "default", - "collection": "encryptedCollection.esc" + "collection": "enxcol_.encryptedCollection.esc" } }, { @@ -1106,7 +1106,7 @@ "object": "testRunner", "arguments": { "database": "default", - "collection": "encryptedCollection.ecc" + "collection": "enxcol_.encryptedCollection.ecc" } }, { @@ -1114,7 +1114,7 @@ "object": "testRunner", "arguments": { "database": "default", - "collection": "encryptedCollection.ecoc" + "collection": "enxcol_.encryptedCollection.ecoc" } }, { @@ -1139,7 +1139,7 @@ { "command_started_event": { "command": { - "drop": "encryptedCollection.esc" + "drop": "enxcol_.encryptedCollection.esc" }, "command_name": "drop", "database_name": "default" @@ -1148,7 +1148,7 @@ { "command_started_event": { "command": { - "drop": "encryptedCollection.ecc" + "drop": "enxcol_.encryptedCollection.ecc" }, "command_name": "drop", "database_name": "default" @@ -1157,7 +1157,7 @@ { "command_started_event": { "command": { - "drop": "encryptedCollection.ecoc" + "drop": "enxcol_.encryptedCollection.ecoc" }, "command_name": "drop", "database_name": "default" @@ -1175,7 +1175,7 @@ { "command_started_event": { "command": { - "create": "encryptedCollection.esc", + "create": "enxcol_.encryptedCollection.esc", "clusteredIndex": { "key": { "_id": 1 @@ -1190,7 +1190,7 @@ { "command_started_event": { "command": { - "create": "encryptedCollection.ecc", + "create": "enxcol_.encryptedCollection.ecc", "clusteredIndex": { "key": { "_id": 1 @@ -1205,7 +1205,7 @@ { "command_started_event": { "command": { - "create": "encryptedCollection.ecoc", + "create": "enxcol_.encryptedCollection.ecoc", "clusteredIndex": { "key": { "_id": 1 @@ -1222,9 +1222,9 @@ "command": { "create": "encryptedCollection", "encryptedFields": { - "escCollection": "encryptedCollection.esc", - "eccCollection": "encryptedCollection.ecc", - "ecocCollection": "encryptedCollection.ecoc", + "escCollection": "enxcol_.encryptedCollection.esc", + "eccCollection": "enxcol_.encryptedCollection.ecc", + "ecocCollection": "enxcol_.encryptedCollection.ecoc", "fields": [ { "path": "firstName", @@ -1278,9 +1278,9 @@ "arguments": { "collection": "encryptedCollection", "encryptedFields": { - "escCollection": "encryptedCollection.esc", - "eccCollection": "encryptedCollection.ecc", - "ecocCollection": "encryptedCollection.ecoc", + "escCollection": "enxcol_.encryptedCollection.esc", + "eccCollection": "enxcol_.encryptedCollection.ecc", + "ecocCollection": "enxcol_.encryptedCollection.ecoc", "fields": [ { "path": "firstName", @@ -1302,9 +1302,9 @@ "arguments": { "collection": "encryptedCollection", "encryptedFields": { - "escCollection": "encryptedCollection.esc", - "eccCollection": "encryptedCollection.ecc", - "ecocCollection": "encryptedCollection.ecoc", + "escCollection": "enxcol_.encryptedCollection.esc", + "eccCollection": "enxcol_.encryptedCollection.ecc", + "ecocCollection": "enxcol_.encryptedCollection.ecoc", "fields": [ { "path": "firstName", @@ -1325,7 +1325,7 @@ "object": "testRunner", "arguments": { "database": "default", - "collection": "encryptedCollection.esc" + "collection": "enxcol_.encryptedCollection.esc" } }, { @@ -1333,7 +1333,7 @@ "object": "testRunner", "arguments": { "database": "default", - "collection": "encryptedCollection.ecc" + "collection": "enxcol_.encryptedCollection.ecc" } }, { @@ -1341,7 +1341,7 @@ "object": "testRunner", "arguments": { "database": "default", - "collection": "encryptedCollection.ecoc" + "collection": "enxcol_.encryptedCollection.ecoc" } }, { @@ -1366,7 +1366,7 @@ { "command_started_event": { "command": { - "drop": "encryptedCollection.esc" + "drop": "enxcol_.encryptedCollection.esc" }, "command_name": "drop", "database_name": "default" @@ -1375,7 +1375,7 @@ { "command_started_event": { "command": { - "drop": "encryptedCollection.ecc" + "drop": "enxcol_.encryptedCollection.ecc" }, "command_name": "drop", "database_name": "default" @@ -1384,7 +1384,7 @@ { "command_started_event": { "command": { - "drop": "encryptedCollection.ecoc" + "drop": "enxcol_.encryptedCollection.ecoc" }, "command_name": "drop", "database_name": "default" @@ -1402,7 +1402,7 @@ { "command_started_event": { "command": { - "create": "encryptedCollection.esc", + "create": "enxcol_.encryptedCollection.esc", "clusteredIndex": { "key": { "_id": 1 @@ -1417,7 +1417,7 @@ { "command_started_event": { "command": { - "create": "encryptedCollection.ecc", + "create": "enxcol_.encryptedCollection.ecc", "clusteredIndex": { "key": { "_id": 1 @@ -1432,7 +1432,7 @@ { "command_started_event": { "command": { - "create": "encryptedCollection.ecoc", + "create": "enxcol_.encryptedCollection.ecoc", "clusteredIndex": { "key": { "_id": 1 @@ -1449,9 +1449,9 @@ "command": { "create": "encryptedCollection", "encryptedFields": { - "escCollection": "encryptedCollection.esc", - "eccCollection": "encryptedCollection.ecc", - "ecocCollection": "encryptedCollection.ecoc", + "escCollection": "enxcol_.encryptedCollection.esc", + "eccCollection": "enxcol_.encryptedCollection.ecc", + "ecocCollection": "enxcol_.encryptedCollection.ecoc", "fields": [ { "path": "firstName", @@ -1510,9 +1510,9 @@ }, "encryptedFieldsMap": { "default.encryptedCollection": { - "escCollection": "encryptedCollection.esc", - "eccCollection": "encryptedCollection.ecc", - "ecocCollection": "encryptedCollection.ecoc", + "escCollection": "enxcol_.encryptedCollection.esc", + "eccCollection": "enxcol_.encryptedCollection.ecc", + "ecocCollection": "enxcol_.encryptedCollection.ecoc", "fields": [ { "path": "firstName", @@ -1542,7 +1542,7 @@ { "command_started_event": { "command": { - "drop": "encryptedCollection.esc" + "drop": "enxcol_.encryptedCollection.esc" }, "command_name": "drop", "database_name": "default" @@ -1551,7 +1551,7 @@ { "command_started_event": { "command": { - "drop": "encryptedCollection.ecc" + "drop": "enxcol_.encryptedCollection.ecc" }, "command_name": "drop", "database_name": "default" @@ -1560,7 +1560,7 @@ { "command_started_event": { "command": { - "drop": "encryptedCollection.ecoc" + "drop": "enxcol_.encryptedCollection.ecoc" }, "command_name": "drop", "database_name": "default" @@ -1594,9 +1594,9 @@ "arguments": { "collection": "encryptedCollection", "encryptedFields": { - "escCollection": "encryptedCollection.esc", - "eccCollection": "encryptedCollection.ecc", - "ecocCollection": "encryptedCollection.ecoc", + "escCollection": "enxcol_.encryptedCollection.esc", + "eccCollection": "enxcol_.encryptedCollection.ecc", + "ecocCollection": "enxcol_.encryptedCollection.ecoc", "fields": [ { "path": "firstName", @@ -1618,9 +1618,9 @@ "arguments": { "collection": "encryptedCollection", "encryptedFields": { - "escCollection": "encryptedCollection.esc", - "eccCollection": "encryptedCollection.ecc", - "ecocCollection": "encryptedCollection.ecoc", + "escCollection": "enxcol_.encryptedCollection.esc", + "eccCollection": "enxcol_.encryptedCollection.ecc", + "ecocCollection": "enxcol_.encryptedCollection.ecoc", "fields": [ { "path": "firstName", @@ -1641,7 +1641,7 @@ "object": "testRunner", "arguments": { "database": "default", - "collection": "encryptedCollection.esc" + "collection": "enxcol_.encryptedCollection.esc" } }, { @@ -1649,7 +1649,7 @@ "object": "testRunner", "arguments": { "database": "default", - "collection": "encryptedCollection.ecc" + "collection": "enxcol_.encryptedCollection.ecc" } }, { @@ -1657,7 +1657,7 @@ "object": "testRunner", "arguments": { "database": "default", - "collection": "encryptedCollection.ecoc" + "collection": "enxcol_.encryptedCollection.ecoc" } }, { @@ -1683,9 +1683,9 @@ "arguments": { "collection": "encryptedCollection", "encryptedFields": { - "escCollection": "encryptedCollection.esc", - "eccCollection": "encryptedCollection.ecc", - "ecocCollection": "encryptedCollection.ecoc", + "escCollection": "enxcol_.encryptedCollection.esc", + "eccCollection": "enxcol_.encryptedCollection.ecc", + "ecocCollection": "enxcol_.encryptedCollection.ecoc", "fields": [ { "path": "firstName", @@ -1706,7 +1706,7 @@ "object": "testRunner", "arguments": { "database": "default", - "collection": "encryptedCollection.esc" + "collection": "enxcol_.encryptedCollection.esc" } }, { @@ -1714,7 +1714,7 @@ "object": "testRunner", "arguments": { "database": "default", - "collection": "encryptedCollection.ecc" + "collection": "enxcol_.encryptedCollection.ecc" } }, { @@ -1722,7 +1722,7 @@ "object": "testRunner", "arguments": { "database": "default", - "collection": "encryptedCollection.ecoc" + "collection": "enxcol_.encryptedCollection.ecoc" } }, { @@ -1738,7 +1738,7 @@ { "command_started_event": { "command": { - "drop": "encryptedCollection.esc" + "drop": "enxcol_.encryptedCollection.esc" }, "command_name": "drop", "database_name": "default" @@ -1747,7 +1747,7 @@ { "command_started_event": { "command": { - "drop": "encryptedCollection.ecc" + "drop": "enxcol_.encryptedCollection.ecc" }, "command_name": "drop", "database_name": "default" @@ -1756,7 +1756,7 @@ { "command_started_event": { "command": { - "drop": "encryptedCollection.ecoc" + "drop": "enxcol_.encryptedCollection.ecoc" }, "command_name": "drop", "database_name": "default" @@ -1774,7 +1774,7 @@ { "command_started_event": { "command": { - "create": "encryptedCollection.esc", + "create": "enxcol_.encryptedCollection.esc", "clusteredIndex": { "key": { "_id": 1 @@ -1789,7 +1789,7 @@ { "command_started_event": { "command": { - "create": "encryptedCollection.ecc", + "create": "enxcol_.encryptedCollection.ecc", "clusteredIndex": { "key": { "_id": 1 @@ -1804,7 +1804,7 @@ { "command_started_event": { "command": { - "create": "encryptedCollection.ecoc", + "create": "enxcol_.encryptedCollection.ecoc", "clusteredIndex": { "key": { "_id": 1 @@ -1821,9 +1821,9 @@ "command": { "create": "encryptedCollection", "encryptedFields": { - "escCollection": "encryptedCollection.esc", - "eccCollection": "encryptedCollection.ecc", - "ecocCollection": "encryptedCollection.ecoc", + "escCollection": "enxcol_.encryptedCollection.esc", + "eccCollection": "enxcol_.encryptedCollection.ecc", + "ecocCollection": "enxcol_.encryptedCollection.ecoc", "fields": [ { "path": "firstName", @@ -1874,7 +1874,7 @@ { "command_started_event": { "command": { - "drop": "encryptedCollection.esc" + "drop": "enxcol_.encryptedCollection.esc" }, "command_name": "drop", "database_name": "default" @@ -1883,7 +1883,7 @@ { "command_started_event": { "command": { - "drop": "encryptedCollection.ecc" + "drop": "enxcol_.encryptedCollection.ecc" }, "command_name": "drop", "database_name": "default" @@ -1892,7 +1892,7 @@ { "command_started_event": { "command": { - "drop": "encryptedCollection.ecoc" + "drop": "enxcol_.encryptedCollection.ecoc" }, "command_name": "drop", "database_name": "default" @@ -1926,9 +1926,9 @@ "arguments": { "collection": "encryptedCollection", "encryptedFields": { - "escCollection": "encryptedCollection.esc", - "eccCollection": "encryptedCollection.ecc", - "ecocCollection": "encryptedCollection.ecoc", + "escCollection": "enxcol_.encryptedCollection.esc", + "eccCollection": "enxcol_.encryptedCollection.ecc", + "ecocCollection": "enxcol_.encryptedCollection.ecoc", "fields": [ { "path": "firstName", @@ -1950,9 +1950,9 @@ "arguments": { "collection": "encryptedCollection", "encryptedFields": { - "escCollection": "encryptedCollection.esc", - "eccCollection": "encryptedCollection.ecc", - "ecocCollection": "encryptedCollection.ecoc", + "escCollection": "enxcol_.encryptedCollection.esc", + "eccCollection": "enxcol_.encryptedCollection.ecc", + "ecocCollection": "enxcol_.encryptedCollection.ecoc", "fields": [ { "path": "firstName", @@ -1973,7 +1973,7 @@ "object": "testRunner", "arguments": { "database": "default", - "collection": "encryptedCollection.esc" + "collection": "enxcol_.encryptedCollection.esc" } }, { @@ -1981,7 +1981,7 @@ "object": "testRunner", "arguments": { "database": "default", - "collection": "encryptedCollection.ecc" + "collection": "enxcol_.encryptedCollection.ecc" } }, { @@ -1989,7 +1989,7 @@ "object": "testRunner", "arguments": { "database": "default", - "collection": "encryptedCollection.ecoc" + "collection": "enxcol_.encryptedCollection.ecoc" } }, { @@ -2021,7 +2021,7 @@ "object": "testRunner", "arguments": { "database": "default", - "collection": "encryptedCollection.esc" + "collection": "enxcol_.encryptedCollection.esc" } }, { @@ -2029,7 +2029,7 @@ "object": "testRunner", "arguments": { "database": "default", - "collection": "encryptedCollection.ecc" + "collection": "enxcol_.encryptedCollection.ecc" } }, { @@ -2037,7 +2037,7 @@ "object": "testRunner", "arguments": { "database": "default", - "collection": "encryptedCollection.ecoc" + "collection": "enxcol_.encryptedCollection.ecoc" } }, { @@ -2053,7 +2053,7 @@ { "command_started_event": { "command": { - "drop": "encryptedCollection.esc" + "drop": "enxcol_.encryptedCollection.esc" }, "command_name": "drop", "database_name": "default" @@ -2062,7 +2062,7 @@ { "command_started_event": { "command": { - "drop": "encryptedCollection.ecc" + "drop": "enxcol_.encryptedCollection.ecc" }, "command_name": "drop", "database_name": "default" @@ -2071,7 +2071,7 @@ { "command_started_event": { "command": { - "drop": "encryptedCollection.ecoc" + "drop": "enxcol_.encryptedCollection.ecoc" }, "command_name": "drop", "database_name": "default" @@ -2089,7 +2089,7 @@ { "command_started_event": { "command": { - "create": "encryptedCollection.esc", + "create": "enxcol_.encryptedCollection.esc", "clusteredIndex": { "key": { "_id": 1 @@ -2104,7 +2104,7 @@ { "command_started_event": { "command": { - "create": "encryptedCollection.ecc", + "create": "enxcol_.encryptedCollection.ecc", "clusteredIndex": { "key": { "_id": 1 @@ -2119,7 +2119,7 @@ { "command_started_event": { "command": { - "create": "encryptedCollection.ecoc", + "create": "enxcol_.encryptedCollection.ecoc", "clusteredIndex": { "key": { "_id": 1 @@ -2136,9 +2136,9 @@ "command": { "create": "encryptedCollection", "encryptedFields": { - "escCollection": "encryptedCollection.esc", - "eccCollection": "encryptedCollection.ecc", - "ecocCollection": "encryptedCollection.ecoc", + "escCollection": "enxcol_.encryptedCollection.esc", + "eccCollection": "enxcol_.encryptedCollection.ecc", + "ecocCollection": "enxcol_.encryptedCollection.ecoc", "fields": [ { "path": "firstName", @@ -2201,7 +2201,7 @@ { "command_started_event": { "command": { - "drop": "encryptedCollection.esc" + "drop": "enxcol_.encryptedCollection.esc" }, "command_name": "drop", "database_name": "default" @@ -2210,7 +2210,7 @@ { "command_started_event": { "command": { - "drop": "encryptedCollection.ecc" + "drop": "enxcol_.encryptedCollection.ecc" }, "command_name": "drop", "database_name": "default" @@ -2219,7 +2219,7 @@ { "command_started_event": { "command": { - "drop": "encryptedCollection.ecoc" + "drop": "enxcol_.encryptedCollection.ecoc" }, "command_name": "drop", "database_name": "default" diff --git a/driver-core/src/test/resources/client-side-encryption/legacy/fle2-EncryptedFields-vs-EncryptedFieldsMap.json b/driver-core/src/test/resources/client-side-encryption/legacy/fle2-EncryptedFields-vs-EncryptedFieldsMap.json index 911b428633a..42cd4bbc9c8 100644 --- a/driver-core/src/test/resources/client-side-encryption/legacy/fle2-EncryptedFields-vs-EncryptedFieldsMap.json +++ b/driver-core/src/test/resources/client-side-encryption/legacy/fle2-EncryptedFields-vs-EncryptedFieldsMap.json @@ -94,9 +94,9 @@ }, "encryptedFieldsMap": { "default.default": { - "escCollection": "esc", - "eccCollection": "ecc", - "ecocCollection": "ecoc", + "escCollection": "enxcol_.default.esc", + "eccCollection": "enxcol_.default.ecc", + "ecocCollection": "enxcol_.default.ecoc", "fields": [] } } From dfd6e7cb27388d9b75915fa990dfadb3d5a4fbab Mon Sep 17 00:00:00 2001 From: Jeff Yemin Date: Thu, 9 Mar 2023 08:30:14 -0500 Subject: [PATCH 03/11] Do not perform server selection to determine sessions support (#1092) Instead, check for session support during operation execution after the connection is checked out. --- .../connection/ConnectionDescription.java | 53 +++++++- .../internal/connection/CommandMessage.java | 10 +- .../connection/DescriptionHelper.java | 2 +- .../internal/connection/MessageSettings.java | 13 ++ .../internal/connection/ProtocolHelper.java | 1 + .../operation/BaseFindAndModifyOperation.java | 2 +- .../internal/operation/BulkWriteBatch.java | 6 +- .../operation/CommandOperationHelper.java | 7 +- .../operation/MixedBulkWriteOperation.java | 11 +- .../internal/operation/OperationHelper.java | 23 +--- ...FindAndDeleteOperationSpecification.groovy | 20 +-- ...indAndReplaceOperationSpecification.groovy | 27 ++-- ...FindAndUpdateOperationSpecification.groovy | 25 ++-- .../CommandMessageSpecification.groovy | 1 + .../BulkWriteBatchSpecification.groovy | 18 +-- .../OperationHelperSpecification.groovy | 44 +++--- .../client/internal/ClientSessionHelper.java | 40 +----- .../client/internal/MongoClientImpl.java | 6 +- .../client/internal/MongoClientImplTest.java | 8 -- .../client/internal/MongoClientDelegate.java | 87 ++++-------- .../client/AbstractSessionsProseTest.java | 126 ++++++++++++++++-- 21 files changed, 281 insertions(+), 249 deletions(-) diff --git a/driver-core/src/main/com/mongodb/connection/ConnectionDescription.java b/driver-core/src/main/com/mongodb/connection/ConnectionDescription.java index b512e5384ca..8b68cd6f9c2 100644 --- a/driver-core/src/main/com/mongodb/connection/ConnectionDescription.java +++ b/driver-core/src/main/com/mongodb/connection/ConnectionDescription.java @@ -46,6 +46,7 @@ public class ConnectionDescription { private final int maxMessageSize; private final List compressors; private final BsonArray saslSupportedMechanisms; + private final Integer logicalSessionTimeoutMinutes; private static final int DEFAULT_MAX_MESSAGE_SIZE = 0x2000000; // 32MB private static final int DEFAULT_MAX_WRITE_BATCH_SIZE = 512; @@ -99,6 +100,29 @@ public ConnectionDescription(final ConnectionId connectionId, final int maxWireV saslSupportedMechanisms); } + /** + * Construct an instance. + * + * @param connectionId the connection id + * @param maxWireVersion the max wire version + * @param serverType the server type + * @param maxBatchCount the max batch count + * @param maxDocumentSize the max document size in bytes + * @param maxMessageSize the max message size in bytes + * @param compressors the available compressors on the connection + * @param saslSupportedMechanisms the supported SASL mechanisms + * @param logicalSessionTimeoutMinutes the logical session timeout, in minutes + * @since 4.10 + */ + public ConnectionDescription(final ConnectionId connectionId, final int maxWireVersion, + final ServerType serverType, final int maxBatchCount, final int maxDocumentSize, + final int maxMessageSize, final List compressors, + @Nullable final BsonArray saslSupportedMechanisms, + @Nullable final Integer logicalSessionTimeoutMinutes) { + this(null, connectionId, maxWireVersion, serverType, maxBatchCount, maxDocumentSize, maxMessageSize, compressors, + saslSupportedMechanisms, logicalSessionTimeoutMinutes); + } + /** * Construct an instance. * @@ -117,6 +141,14 @@ public ConnectionDescription(@Nullable final ObjectId serviceId, final Connectio final ServerType serverType, final int maxBatchCount, final int maxDocumentSize, final int maxMessageSize, final List compressors, @Nullable final BsonArray saslSupportedMechanisms) { + this(serviceId, connectionId, maxWireVersion, serverType, maxBatchCount, maxDocumentSize, maxMessageSize, compressors, + saslSupportedMechanisms, null); + } + + private ConnectionDescription(@Nullable final ObjectId serviceId, final ConnectionId connectionId, final int maxWireVersion, + final ServerType serverType, final int maxBatchCount, final int maxDocumentSize, + final int maxMessageSize, final List compressors, + @Nullable final BsonArray saslSupportedMechanisms, @Nullable final Integer logicalSessionTimeoutMinutes) { this.serviceId = serviceId; this.connectionId = connectionId; this.serverType = serverType; @@ -126,6 +158,7 @@ public ConnectionDescription(@Nullable final ObjectId serviceId, final Connectio this.maxWireVersion = maxWireVersion; this.compressors = notNull("compressors", Collections.unmodifiableList(new ArrayList<>(compressors))); this.saslSupportedMechanisms = saslSupportedMechanisms; + this.logicalSessionTimeoutMinutes = logicalSessionTimeoutMinutes; } /** * Creates a new connection description with the set connection id @@ -137,7 +170,7 @@ public ConnectionDescription(@Nullable final ObjectId serviceId, final Connectio public ConnectionDescription withConnectionId(final ConnectionId connectionId) { notNull("connectionId", connectionId); return new ConnectionDescription(serviceId, connectionId, maxWireVersion, serverType, maxBatchCount, maxDocumentSize, - maxMessageSize, compressors, saslSupportedMechanisms); + maxMessageSize, compressors, saslSupportedMechanisms, logicalSessionTimeoutMinutes); } /** @@ -150,7 +183,7 @@ public ConnectionDescription withConnectionId(final ConnectionId connectionId) { public ConnectionDescription withServiceId(final ObjectId serviceId) { notNull("serviceId", serviceId); return new ConnectionDescription(serviceId, connectionId, maxWireVersion, serverType, maxBatchCount, maxDocumentSize, - maxMessageSize, compressors, saslSupportedMechanisms); + maxMessageSize, compressors, saslSupportedMechanisms, logicalSessionTimeoutMinutes); } /** @@ -248,6 +281,17 @@ public BsonArray getSaslSupportedMechanisms() { return saslSupportedMechanisms; } + /** + * Gets the session timeout in minutes. + * + * @return the session timeout in minutes, or null if sessions are not supported by this connection + * @mongodb.server.release 3.6 + * @since 4.10 + */ + @Nullable + public Integer getLogicalSessionTimeoutMinutes() { + return logicalSessionTimeoutMinutes; + } /** * Get the default maximum message size. * @@ -302,6 +346,9 @@ public boolean equals(final Object o) { if (!compressors.equals(that.compressors)) { return false; } + if (!Objects.equals(logicalSessionTimeoutMinutes, that.logicalSessionTimeoutMinutes)) { + return false; + } return Objects.equals(saslSupportedMechanisms, that.saslSupportedMechanisms); } @@ -316,6 +363,7 @@ public int hashCode() { result = 31 * result + compressors.hashCode(); result = 31 * result + (serviceId != null ? serviceId.hashCode() : 0); result = 31 * result + (saslSupportedMechanisms != null ? saslSupportedMechanisms.hashCode() : 0); + result = 31 * result + (logicalSessionTimeoutMinutes != null ? logicalSessionTimeoutMinutes.hashCode() : 0); return result; } @@ -329,6 +377,7 @@ public String toString() { + ", maxDocumentSize=" + maxDocumentSize + ", maxMessageSize=" + maxMessageSize + ", compressors=" + compressors + + ", logicialSessionTimeoutMinutes=" + logicalSessionTimeoutMinutes + ", serviceId=" + serviceId + '}'; } diff --git a/driver-core/src/main/com/mongodb/internal/connection/CommandMessage.java b/driver-core/src/main/com/mongodb/internal/connection/CommandMessage.java index 51df527077f..f9ca361778f 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/CommandMessage.java +++ b/driver-core/src/main/com/mongodb/internal/connection/CommandMessage.java @@ -221,8 +221,14 @@ private List getExtraElements(final SessionContext sessionContext) if (sessionContext.getClusterTime() != null) { extraElements.add(new BsonElement("$clusterTime", sessionContext.getClusterTime())); } - if (sessionContext.hasSession() && responseExpected) { - extraElements.add(new BsonElement("lsid", sessionContext.getSessionId())); + if (sessionContext.hasSession()) { + if (!sessionContext.isImplicitSession() && !getSettings().isSessionSupported()) { + throw new MongoClientException("Attempting to use a ClientSession while connected to a server that doesn't support " + + "sessions"); + } + if (getSettings().isSessionSupported() && responseExpected) { + extraElements.add(new BsonElement("lsid", sessionContext.getSessionId())); + } } boolean firstMessageInTransaction = sessionContext.notifyMessageSent(); diff --git a/driver-core/src/main/com/mongodb/internal/connection/DescriptionHelper.java b/driver-core/src/main/com/mongodb/internal/connection/DescriptionHelper.java index ae86d365bab..6609e00693c 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/DescriptionHelper.java +++ b/driver-core/src/main/com/mongodb/internal/connection/DescriptionHelper.java @@ -68,7 +68,7 @@ static ConnectionDescription createConnectionDescription(final ClusterConnection ConnectionDescription connectionDescription = new ConnectionDescription(connectionId, getMaxWireVersion(helloResult), getServerType(helloResult), getMaxWriteBatchSize(helloResult), getMaxBsonObjectSize(helloResult), getMaxMessageSizeBytes(helloResult), getCompressors(helloResult), - helloResult.getArray("saslSupportedMechs", null)); + helloResult.getArray("saslSupportedMechs", null), getLogicalSessionTimeoutMinutes(helloResult)); if (helloResult.containsKey("connectionId")) { ConnectionId newConnectionId = connectionDescription.getConnectionId().withServerValue(helloResult.getNumber("connectionId").intValue()); diff --git a/driver-core/src/main/com/mongodb/internal/connection/MessageSettings.java b/driver-core/src/main/com/mongodb/internal/connection/MessageSettings.java index a25cd954ad8..7fc48bb01a5 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/MessageSettings.java +++ b/driver-core/src/main/com/mongodb/internal/connection/MessageSettings.java @@ -36,6 +36,7 @@ public final class MessageSettings { private final int maxBatchCount; private final int maxWireVersion; private final ServerType serverType; + private final boolean sessionSupported; /** * Gets the builder @@ -56,6 +57,7 @@ public static final class Builder { private int maxBatchCount = DEFAULT_MAX_BATCH_COUNT; private int maxWireVersion; private ServerType serverType; + private boolean sessionSupported; /** * Build it. @@ -108,6 +110,11 @@ public Builder serverType(final ServerType serverType) { this.serverType = serverType; return this; } + + public Builder sessionSupported(final boolean sessionSupported) { + this.sessionSupported = sessionSupported; + return this; + } } /** @@ -145,11 +152,17 @@ public ServerType getServerType() { return serverType; } + public boolean isSessionSupported() { + return sessionSupported; + } + + private MessageSettings(final Builder builder) { this.maxDocumentSize = builder.maxDocumentSize; this.maxMessageSize = builder.maxMessageSize; this.maxBatchCount = builder.maxBatchCount; this.maxWireVersion = builder.maxWireVersion; this.serverType = builder.serverType; + this.sessionSupported = builder.sessionSupported; } } diff --git a/driver-core/src/main/com/mongodb/internal/connection/ProtocolHelper.java b/driver-core/src/main/com/mongodb/internal/connection/ProtocolHelper.java index d0d64999799..3432354ee38 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/ProtocolHelper.java +++ b/driver-core/src/main/com/mongodb/internal/connection/ProtocolHelper.java @@ -228,6 +228,7 @@ static MessageSettings getMessageSettings(final ConnectionDescription connection .maxBatchCount(connectionDescription.getMaxBatchCount()) .maxWireVersion(connectionDescription.getMaxWireVersion()) .serverType(connectionDescription.getServerType()) + .sessionSupported(connectionDescription.getLogicalSessionTimeoutMinutes() != null) .build(); } diff --git a/driver-core/src/main/com/mongodb/internal/operation/BaseFindAndModifyOperation.java b/driver-core/src/main/com/mongodb/internal/operation/BaseFindAndModifyOperation.java index 04c058d39c2..820da388d22 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/BaseFindAndModifyOperation.java +++ b/driver-core/src/main/com/mongodb/internal/operation/BaseFindAndModifyOperation.java @@ -224,7 +224,7 @@ private CommandCreator getCommandCreator(final SessionContext sessionContext) { putIfNotNull(commandDocument, "comment", getComment()); putIfNotNull(commandDocument, "let", getLet()); - if (isRetryableWrite(isRetryWrites(), getWriteConcern(), serverDescription, connectionDescription, sessionContext)) { + if (isRetryableWrite(isRetryWrites(), getWriteConcern(), connectionDescription, sessionContext)) { commandDocument.put("txnNumber", new BsonInt64(sessionContext.advanceTransactionNumber())); } return commandDocument; diff --git a/driver-core/src/main/com/mongodb/internal/operation/BulkWriteBatch.java b/driver-core/src/main/com/mongodb/internal/operation/BulkWriteBatch.java index 9c0000d04b2..6d6a76885be 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/BulkWriteBatch.java +++ b/driver-core/src/main/com/mongodb/internal/operation/BulkWriteBatch.java @@ -27,7 +27,6 @@ import com.mongodb.bulk.BulkWriteUpsert; import com.mongodb.bulk.WriteConcernError; import com.mongodb.connection.ConnectionDescription; -import com.mongodb.connection.ServerDescription; import com.mongodb.internal.bulk.DeleteRequest; import com.mongodb.internal.bulk.UpdateRequest; import com.mongodb.internal.bulk.WriteRequest; @@ -96,8 +95,7 @@ public final class BulkWriteBatch { private final BsonDocument variables; static BulkWriteBatch createBulkWriteBatch(final MongoNamespace namespace, - final ServerDescription serverDescription, - final ConnectionDescription connectionDescription, + final ConnectionDescription connectionDescription, final boolean ordered, final WriteConcern writeConcern, final Boolean bypassDocumentValidation, final boolean retryWrites, final List writeRequests, @@ -107,7 +105,7 @@ static BulkWriteBatch createBulkWriteBatch(final MongoNamespace namespace, && !writeConcern.isAcknowledged()) { throw new MongoClientException("Unacknowledged writes are not supported when using an explicit session"); } - boolean canRetryWrites = isRetryableWrite(retryWrites, writeConcern, serverDescription, connectionDescription, sessionContext); + boolean canRetryWrites = isRetryableWrite(retryWrites, writeConcern, connectionDescription, sessionContext); List writeRequestsWithIndex = new ArrayList<>(); boolean writeRequestsAreRetryable = true; for (int i = 0; i < writeRequests.size(); i++) { diff --git a/driver-core/src/main/com/mongodb/internal/operation/CommandOperationHelper.java b/driver-core/src/main/com/mongodb/internal/operation/CommandOperationHelper.java index 72924a5b213..06ce958971c 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/CommandOperationHelper.java +++ b/driver-core/src/main/com/mongodb/internal/operation/CommandOperationHelper.java @@ -407,8 +407,7 @@ static R executeRetryableWrite( return withSourceAndConnection(binding::getWriteConnectionSource, true, (source, connection) -> { int maxWireVersion = connection.getDescription().getMaxWireVersion(); try { - retryState.breakAndThrowIfRetryAnd(() -> !canRetryWrite(source.getServerDescription(), connection.getDescription(), - binding.getSessionContext())); + retryState.breakAndThrowIfRetryAnd(() -> !canRetryWrite(connection.getDescription(), binding.getSessionContext())); BsonDocument command = retryState.attachment(AttachmentKeys.command()) .map(previousAttemptCommand -> { assertFalse(firstAttempt); @@ -462,8 +461,8 @@ static void executeRetryableWriteAsync( SingleResultCallback addingRetryableLabelCallback = firstAttempt ? releasingCallback : addingRetryableLabelCallback(releasingCallback, maxWireVersion); - if (retryState.breakAndCompleteIfRetryAnd(() -> !canRetryWrite(source.getServerDescription(), connection.getDescription(), - binding.getSessionContext()), addingRetryableLabelCallback)) { + if (retryState.breakAndCompleteIfRetryAnd(() -> !canRetryWrite(connection.getDescription(), binding.getSessionContext()), + addingRetryableLabelCallback)) { return; } BsonDocument command; diff --git a/driver-core/src/main/com/mongodb/internal/operation/MixedBulkWriteOperation.java b/driver-core/src/main/com/mongodb/internal/operation/MixedBulkWriteOperation.java index f6d75ba48c3..6690fc7f218 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/MixedBulkWriteOperation.java +++ b/driver-core/src/main/com/mongodb/internal/operation/MixedBulkWriteOperation.java @@ -184,14 +184,13 @@ public BulkWriteResult execute(final WriteBinding binding) { retryState.attach(AttachmentKeys.maxWireVersion(), connectionDescription.getMaxWireVersion(), true); SessionContext sessionContext = binding.getSessionContext(); WriteConcern writeConcern = getAppliedWriteConcern(sessionContext); - if (!isRetryableWrite(retryWrites, getAppliedWriteConcern(sessionContext), - source.getServerDescription(), connectionDescription, sessionContext)) { + if (!isRetryableWrite(retryWrites, getAppliedWriteConcern(sessionContext), connectionDescription, sessionContext)) { handleMongoWriteConcernWithResponseException(retryState, true); } validateWriteRequests(connectionDescription, bypassDocumentValidation, writeRequests, writeConcern); if (!retryState.attachment(AttachmentKeys.bulkWriteTracker()).orElseThrow(Assertions::fail).batch().isPresent()) { BulkWriteTracker.attachNew(retryState, BulkWriteBatch.createBulkWriteBatch(namespace, - source.getServerDescription(), connectionDescription, ordered, writeConcern, + connectionDescription, ordered, writeConcern, bypassDocumentValidation, retryWrites, writeRequests, sessionContext, comment, variables)); } logRetryExecute(retryState); @@ -220,9 +219,7 @@ public void executeAsync(final AsyncWriteBinding binding, final SingleResultCall retryState.attach(AttachmentKeys.maxWireVersion(), connectionDescription.getMaxWireVersion(), true); SessionContext sessionContext = binding.getSessionContext(); WriteConcern writeConcern = getAppliedWriteConcern(sessionContext); - if (!isRetryableWrite(retryWrites, getAppliedWriteConcern(sessionContext), - source.getServerDescription(), - connectionDescription, sessionContext) + if (!isRetryableWrite(retryWrites, getAppliedWriteConcern(sessionContext), connectionDescription, sessionContext) && handleMongoWriteConcernWithResponseExceptionAsync(retryState, releasingCallback)) { return; } @@ -233,7 +230,7 @@ && handleMongoWriteConcernWithResponseExceptionAsync(retryState, releasingCallba try { if (!retryState.attachment(AttachmentKeys.bulkWriteTracker()).orElseThrow(Assertions::fail).batch().isPresent()) { BulkWriteTracker.attachNew(retryState, BulkWriteBatch.createBulkWriteBatch(namespace, - source.getServerDescription(), connectionDescription, ordered, writeConcern, + connectionDescription, ordered, writeConcern, bypassDocumentValidation, retryWrites, writeRequests, sessionContext, comment, variables)); } } catch (Throwable t) { diff --git a/driver-core/src/main/com/mongodb/internal/operation/OperationHelper.java b/driver-core/src/main/com/mongodb/internal/operation/OperationHelper.java index 0cbbdf63006..9ba182ebb58 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/OperationHelper.java +++ b/driver-core/src/main/com/mongodb/internal/operation/OperationHelper.java @@ -202,8 +202,7 @@ static void checkBypassDocumentValidationIsSupported(@Nullable final Boolean byp } static boolean isRetryableWrite(final boolean retryWrites, final WriteConcern writeConcern, - final ServerDescription serverDescription, final ConnectionDescription connectionDescription, - final SessionContext sessionContext) { + final ConnectionDescription connectionDescription, final SessionContext sessionContext) { if (!retryWrites) { return false; } else if (!writeConcern.isAcknowledged()) { @@ -213,22 +212,17 @@ static boolean isRetryableWrite(final boolean retryWrites, final WriteConcern wr LOGGER.debug("retryWrites set to true but in an active transaction."); return false; } else { - return canRetryWrite(serverDescription, connectionDescription, sessionContext); + return canRetryWrite(connectionDescription, sessionContext); } } - static boolean canRetryWrite(final ServerDescription serverDescription, final ConnectionDescription connectionDescription, - final SessionContext sessionContext) { - if (serverDescription.getLogicalSessionTimeoutMinutes() == null && serverDescription.getType() != ServerType.LOAD_BALANCER) { - LOGGER.debug("retryWrites set to true but the server does not have 3.6 feature compatibility enabled."); + static boolean canRetryWrite(final ConnectionDescription connectionDescription, final SessionContext sessionContext) { + if (connectionDescription.getLogicalSessionTimeoutMinutes() == null) { + LOGGER.debug("retryWrites set to true but the server does not support sessions."); return false; } else if (connectionDescription.getServerType().equals(ServerType.STANDALONE)) { LOGGER.debug("retryWrites set to true but the server is a standalone server."); return false; - } else if (!sessionContext.hasSession()) { - LOGGER.debug("retryWrites set to true but there is no implicit session, likely because the MongoClient was created with " - + "multiple MongoCredential instances and sessions can only be used with a single MongoCredential"); - return false; } return true; } @@ -237,13 +231,6 @@ static boolean canRetryRead(final ServerDescription serverDescription, final Ses if (sessionContext.hasActiveTransaction()) { LOGGER.debug("retryReads set to true but in an active transaction."); return false; - } else if (serverDescription.getLogicalSessionTimeoutMinutes() == null && serverDescription.getType() != ServerType.LOAD_BALANCER) { - LOGGER.debug("retryReads set to true but the server does not have 3.6 feature compatibility enabled."); - return false; - } else if (serverDescription.getType() != ServerType.STANDALONE && !sessionContext.hasSession()) { - LOGGER.debug("retryReads set to true but there is no implicit session, likely because the MongoClient was created with " - + "multiple MongoCredential instances and sessions can only be used with a single MongoCredential"); - return false; } return true; } diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/FindAndDeleteOperationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/operation/FindAndDeleteOperationSpecification.groovy index e7aea93327c..aad74b1881f 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/FindAndDeleteOperationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/FindAndDeleteOperationSpecification.groovy @@ -193,11 +193,8 @@ class FindAndDeleteOperationSpecification extends OperationFunctionalSpecificati def 'should create the expected command'() { when: - def includeCollation = serverVersionIsGreaterThan(serverVersion, [3, 4, 0]) - def includeTxnNumber = (serverVersionIsGreaterThan(serverVersion, [3, 6, 0]) && retryWrites - && writeConcern.isAcknowledged() && serverType != STANDALONE) - def includeWriteConcern = (writeConcern.isAcknowledged() && !writeConcern.isServerDefault() - && serverVersionIsGreaterThan(serverVersion, [3, 4, 0])) + def includeTxnNumber = retryWrites && writeConcern.isAcknowledged() && serverType != STANDALONE + def includeWriteConcern = writeConcern.isAcknowledged() && !writeConcern.isServerDefault() def cannedResult = new BsonDocument('value', new BsonDocumentWrapper(BsonDocument.parse('{}'), new BsonDocumentCodec())) def operation = new FindAndDeleteOperation(getNamespace(), writeConcern as WriteConcern, retryWrites as boolean, documentCodec) @@ -212,7 +209,7 @@ class FindAndDeleteOperationSpecification extends OperationFunctionalSpecificati } then: - testOperation([operation: operation, serverVersion: serverVersion, expectedCommand: expectedCommand, async: async, + testOperation([operation: operation, serverVersion: [3, 6, 0], expectedCommand: expectedCommand, async: async, result: cannedResult, serverType: serverType]) when: @@ -230,18 +227,15 @@ class FindAndDeleteOperationSpecification extends OperationFunctionalSpecificati .append('fields', projection) .append('maxTimeMS', new BsonInt64(10)) - if (includeCollation) { - operation.collation(defaultCollation) - expectedCommand.append('collation', defaultCollation.asDocument()) - } + operation.collation(defaultCollation) + expectedCommand.append('collation', defaultCollation.asDocument()) then: - testOperation([operation: operation, serverVersion: serverVersion, expectedCommand: expectedCommand, async: async, + testOperation([operation: operation, serverVersion: [3, 6, 0], expectedCommand: expectedCommand, async: async, result: cannedResult, serverType: serverType]) where: - [serverVersion, serverType, writeConcern, async, retryWrites] << [ - [[3, 6, 0], [3, 4, 0]], + [serverType, writeConcern, async, retryWrites] << [ [REPLICA_SET_PRIMARY, STANDALONE], [ACKNOWLEDGED, W1, UNACKNOWLEDGED], [true, false], diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/FindAndReplaceOperationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/operation/FindAndReplaceOperationSpecification.groovy index 282a82bab56..a4a0a48bd60 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/FindAndReplaceOperationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/FindAndReplaceOperationSpecification.groovy @@ -313,14 +313,8 @@ class FindAndReplaceOperationSpecification extends OperationFunctionalSpecificat def 'should create the expected command'() { when: - def includeBypassValidation = serverVersionIsGreaterThan(serverVersion, [3, 4, 0]) - def includeCollation = serverVersionIsGreaterThan(serverVersion, [3, 4, 0]) - def includeTxnNumber = (serverVersionIsGreaterThan(serverVersion, [3, 6, 0]) && retryWrites - && writeConcern.isAcknowledged() && serverType != STANDALONE) - def includeWriteConcern = (writeConcern.isAcknowledged() && !writeConcern.isServerDefault() - && serverVersionIsGreaterThan(serverVersion, [3, 4, 0])) - - + def includeTxnNumber = retryWrites && writeConcern.isAcknowledged() && serverType != STANDALONE + def includeWriteConcern = writeConcern.isAcknowledged() && !writeConcern.isServerDefault() def cannedResult = new BsonDocument('value', new BsonDocumentWrapper(BsonDocument.parse('{}'), new BsonDocumentCodec())) def replacement = BsonDocument.parse('{ replacement: 1}') def operation = new FindAndReplaceOperation(getNamespace(), writeConcern, retryWrites, documentCodec, replacement) @@ -335,7 +329,7 @@ class FindAndReplaceOperationSpecification extends OperationFunctionalSpecificat expectedCommand.put('new', BsonBoolean.FALSE) then: - testOperation([operation: operation, serverVersion: serverVersion, expectedCommand: expectedCommand, async: async, + testOperation([operation: operation, serverVersion: [3, 6, 0], expectedCommand: expectedCommand, async: async, result : cannedResult, serverType: serverType]) when: @@ -354,21 +348,16 @@ class FindAndReplaceOperationSpecification extends OperationFunctionalSpecificat .append('fields', projection) .append('maxTimeMS', new BsonInt64(10)) - if (includeCollation) { - operation.collation(defaultCollation) - expectedCommand.append('collation', defaultCollation.asDocument()) - } - if (includeBypassValidation) { - expectedCommand.append('bypassDocumentValidation', BsonBoolean.TRUE) - } + operation.collation(defaultCollation) + expectedCommand.append('collation', defaultCollation.asDocument()) + expectedCommand.append('bypassDocumentValidation', BsonBoolean.TRUE) then: - testOperation([operation: operation, serverVersion: serverVersion, expectedCommand: expectedCommand, async: async, + testOperation([operation: operation, serverVersion: [3, 6, 0], expectedCommand: expectedCommand, async: async, result : cannedResult, serverType: serverType]) where: - [serverVersion, serverType, writeConcern, async, retryWrites] << [ - [[3, 6, 0], [3, 4, 0]], + [serverType, writeConcern, async, retryWrites] << [ [REPLICA_SET_PRIMARY, STANDALONE], [ACKNOWLEDGED, W1, UNACKNOWLEDGED], [true, false], diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/FindAndUpdateOperationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/operation/FindAndUpdateOperationSpecification.groovy index 54d3bcb9dc0..d6625cd4d88 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/FindAndUpdateOperationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/FindAndUpdateOperationSpecification.groovy @@ -433,12 +433,8 @@ class FindAndUpdateOperationSpecification extends OperationFunctionalSpecificati def 'should create the expected command'() { when: - def includeBypassValidation = serverVersionIsGreaterThan(serverVersion, [3, 4, 0]) - def includeCollation = serverVersionIsGreaterThan(serverVersion, [3, 4, 0]) - def includeTxnNumber = (serverVersionIsGreaterThan(serverVersion, [3, 6, 0]) && retryWrites - && writeConcern.isAcknowledged() && serverType != STANDALONE) - def includeWriteConcern = (writeConcern.isAcknowledged() && !writeConcern.isServerDefault() - && serverVersionIsGreaterThan(serverVersion, [3, 4, 0])) + def includeTxnNumber = retryWrites && writeConcern.isAcknowledged() && serverType != STANDALONE + def includeWriteConcern = writeConcern.isAcknowledged() && !writeConcern.isServerDefault() def cannedResult = new BsonDocument('value', new BsonDocumentWrapper(BsonDocument.parse('{}'), new BsonDocumentCodec())) def update = BsonDocument.parse('{ update: 1}') def operation = new FindAndUpdateOperation(getNamespace(), writeConcern, retryWrites, documentCodec, update) @@ -453,7 +449,7 @@ class FindAndUpdateOperationSpecification extends OperationFunctionalSpecificati expectedCommand.put('new', BsonBoolean.FALSE) then: - testOperation([operation: operation, serverVersion: serverVersion, expectedCommand: expectedCommand, async: async, + testOperation([operation: operation, serverVersion: [3, 6, 0], expectedCommand: expectedCommand, async: async, result: cannedResult, serverType: serverType]) when: @@ -472,21 +468,16 @@ class FindAndUpdateOperationSpecification extends OperationFunctionalSpecificati .append('fields', projection) .append('maxTimeMS', new BsonInt64(10)) - if (includeCollation) { - operation.collation(defaultCollation) - expectedCommand.append('collation', defaultCollation.asDocument()) - } - if (includeBypassValidation) { - expectedCommand.append('bypassDocumentValidation', BsonBoolean.TRUE) - } + operation.collation(defaultCollation) + expectedCommand.append('collation', defaultCollation.asDocument()) + expectedCommand.append('bypassDocumentValidation', BsonBoolean.TRUE) then: - testOperation([operation: operation, serverVersion: serverVersion, expectedCommand: expectedCommand, async: async, + testOperation([operation: operation, serverVersion: [3, 6, 0], expectedCommand: expectedCommand, async: async, result: cannedResult, serverType: serverType]) where: - [serverVersion, serverType, writeConcern, async, retryWrites] << [ - [[3, 6, 0], [3, 4, 0]], + [serverType, writeConcern, async, retryWrites] << [ [REPLICA_SET_PRIMARY, STANDALONE], [ACKNOWLEDGED, W1, UNACKNOWLEDGED], [true, false], diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/CommandMessageSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/connection/CommandMessageSpecification.groovy index 7a2f088ca76..6449d202f1b 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/CommandMessageSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/CommandMessageSpecification.groovy @@ -61,6 +61,7 @@ class CommandMessageSpecification extends Specification { MessageSettings.builder() .maxWireVersion(THREE_DOT_SIX_WIRE_VERSION) .serverType(serverType as ServerType) + .sessionSupported(true) .build(), responseExpected, exhaustAllowed, null, null, clusterConnectionMode, null) def output = new BasicOutputBuffer() diff --git a/driver-core/src/test/unit/com/mongodb/internal/operation/BulkWriteBatchSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/operation/BulkWriteBatchSpecification.groovy index 88256610c00..c7e1a0d4363 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/operation/BulkWriteBatchSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/operation/BulkWriteBatchSpecification.groovy @@ -56,7 +56,7 @@ class BulkWriteBatchSpecification extends Specification { def 'should split payloads by type when ordered'() { when: - def bulkWriteBatch = BulkWriteBatch.createBulkWriteBatch(namespace, serverDescription, connectionDescription, true, + def bulkWriteBatch = BulkWriteBatch.createBulkWriteBatch(namespace, connectionDescription, true, WriteConcern.ACKNOWLEDGED, null, false, getWriteRequests(), sessionContext, null, null) def payload = bulkWriteBatch.getPayload() payload.setPosition(payload.size()) @@ -136,7 +136,7 @@ class BulkWriteBatchSpecification extends Specification { def 'should group payloads by type when unordered'() { when: - def bulkWriteBatch = BulkWriteBatch.createBulkWriteBatch(namespace, serverDescription, connectionDescription, false, + def bulkWriteBatch = BulkWriteBatch.createBulkWriteBatch(namespace, connectionDescription, false, WriteConcern.MAJORITY, true, false, getWriteRequests(), sessionContext, null, null) def payload = bulkWriteBatch.getPayload() payload.setPosition(payload.size()) @@ -188,7 +188,7 @@ class BulkWriteBatchSpecification extends Specification { def 'should split payloads if only payload partially processed'() { when: - def bulkWriteBatch = BulkWriteBatch.createBulkWriteBatch(namespace, serverDescription, connectionDescription, false, + def bulkWriteBatch = BulkWriteBatch.createBulkWriteBatch(namespace, connectionDescription, false, WriteConcern.ACKNOWLEDGED, null, false, getWriteRequests()[0..3], sessionContext, null, null) def payload = bulkWriteBatch.getPayload() payload.setPosition(1) @@ -231,7 +231,7 @@ class BulkWriteBatchSpecification extends Specification { def 'should map all inserted ids'() { when: - def bulkWriteBatch = BulkWriteBatch.createBulkWriteBatch(namespace, serverDescription, connectionDescription, false, + def bulkWriteBatch = BulkWriteBatch.createBulkWriteBatch(namespace, connectionDescription, false, WriteConcern.ACKNOWLEDGED, null, false, [new InsertRequest(toBsonDocument('{_id: 0}')), new InsertRequest(toBsonDocument('{_id: 1}')), @@ -272,7 +272,7 @@ class BulkWriteBatchSpecification extends Specification { def 'should not map inserted id with a write error'() { given: - def bulkWriteBatch = BulkWriteBatch.createBulkWriteBatch(namespace, serverDescription, connectionDescription, false, + def bulkWriteBatch = BulkWriteBatch.createBulkWriteBatch(namespace, connectionDescription, false, WriteConcern.ACKNOWLEDGED, null, false, [new InsertRequest(toBsonDocument('{_id: 0}')), new InsertRequest(toBsonDocument('{_id: 1}')), @@ -298,7 +298,7 @@ class BulkWriteBatchSpecification extends Specification { def 'should not retry when at least one write is not retryable'() { when: - def bulkWriteBatch = BulkWriteBatch.createBulkWriteBatch(namespace, serverDescription, connectionDescription, false, + def bulkWriteBatch = BulkWriteBatch.createBulkWriteBatch(namespace, connectionDescription, false, WriteConcern.ACKNOWLEDGED, null, true, [new DeleteRequest(new BsonDocument()).multi(true), new InsertRequest(new BsonDocument())], sessionContext, null, null) @@ -308,7 +308,7 @@ class BulkWriteBatchSpecification extends Specification { def 'should handle operation responses'() { given: - def bulkWriteBatch = BulkWriteBatch.createBulkWriteBatch(namespace, serverDescription, connectionDescription, true, + def bulkWriteBatch = BulkWriteBatch.createBulkWriteBatch(namespace, connectionDescription, true, WriteConcern.ACKNOWLEDGED, null, false, getWriteRequests()[1..1], sessionContext, null, null) def writeConcernError = toBsonDocument('{ok: 1, n: 1, upserted: [{_id: 2, index: 0}]}') @@ -323,7 +323,7 @@ class BulkWriteBatchSpecification extends Specification { def 'should handle writeConcernError error responses'() { given: - def bulkWriteBatch = BulkWriteBatch.createBulkWriteBatch(namespace, serverDescription, connectionDescription, true, + def bulkWriteBatch = BulkWriteBatch.createBulkWriteBatch(namespace, connectionDescription, true, WriteConcern.ACKNOWLEDGED, null, false, getWriteRequests()[0..0], sessionContext, null, null) def writeConcernError = toBsonDocument('{n: 1, writeConcernError: {code: 75, errmsg: "wtimeout", errInfo: {wtimeout: "0"}}}') @@ -339,7 +339,7 @@ class BulkWriteBatchSpecification extends Specification { def 'should handle writeErrors error responses'() { given: - def bulkWriteBatch = BulkWriteBatch.createBulkWriteBatch(namespace, serverDescription, connectionDescription, true, + def bulkWriteBatch = BulkWriteBatch.createBulkWriteBatch(namespace, connectionDescription, true, WriteConcern.ACKNOWLEDGED, null, false, getWriteRequests()[0..0], sessionContext, null, null) def writeError = toBsonDocument('''{"ok": 0, "n": 1, "code": 65, "errmsg": "bulk op errors", "writeErrors": [{ "index" : 0, "code" : 100, "errmsg": "some error"}] }''') diff --git a/driver-core/src/test/unit/com/mongodb/internal/operation/OperationHelperSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/operation/OperationHelperSpecification.groovy index c759a35fb70..ff664a594ea 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/operation/OperationHelperSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/operation/OperationHelperSpecification.groovy @@ -28,6 +28,7 @@ import com.mongodb.internal.bulk.UpdateRequest import com.mongodb.internal.bulk.WriteRequest import com.mongodb.internal.connection.AsyncConnection import com.mongodb.internal.session.SessionContext +import org.bson.BsonArray import org.bson.BsonDocument import spock.lang.Specification @@ -79,24 +80,19 @@ class OperationHelperSpecification extends Specification { hasSession() >> true hasActiveTransaction() >> false } - def noOpSessionContext = Stub(SessionContext) { - hasSession() >> false - hasActiveTransaction() >> false - } expect: - isRetryableWrite(retryWrites, writeConcern, serverDescription, connectionDescription, noTransactionSessionContext) == expected - !isRetryableWrite(retryWrites, writeConcern, serverDescription, connectionDescription, activeTransactionSessionContext) - !isRetryableWrite(retryWrites, writeConcern, serverDescription, connectionDescription, noOpSessionContext) + isRetryableWrite(retryWrites, writeConcern, connectionDescription, noTransactionSessionContext) == expected + !isRetryableWrite(retryWrites, writeConcern, connectionDescription, activeTransactionSessionContext) where: - retryWrites | writeConcern | serverDescription | connectionDescription | expected - false | ACKNOWLEDGED | retryableServerDescription | threeSixConnectionDescription | false - true | UNACKNOWLEDGED | retryableServerDescription | threeSixConnectionDescription | false - true | ACKNOWLEDGED | nonRetryableServerDescription | threeSixConnectionDescription | false - true | ACKNOWLEDGED | retryableServerDescription | threeFourConnectionDescription | false - true | ACKNOWLEDGED | retryableServerDescription | threeSixConnectionDescription | false - true | ACKNOWLEDGED | retryableServerDescription | threeSixPrimaryConnectionDescription | true + retryWrites | writeConcern | connectionDescription | expected + false | ACKNOWLEDGED | threeSixConnectionDescription | false + true | UNACKNOWLEDGED | threeSixConnectionDescription | false + true | ACKNOWLEDGED | threeSixConnectionDescription | false + true | ACKNOWLEDGED | threeFourConnectionDescription | false + true | ACKNOWLEDGED | threeSixConnectionDescription | false + true | ACKNOWLEDGED | threeSixPrimaryConnectionDescription | true } def 'should check if a valid retryable read'() { @@ -109,30 +105,20 @@ class OperationHelperSpecification extends Specification { hasSession() >> true hasActiveTransaction() >> false } - def noOpSessionContext = Stub(SessionContext) { - hasSession() >> false - hasActiveTransaction() >> false - } expect: - canRetryRead(serverDescription, noTransactionSessionContext) == expected - !canRetryRead(serverDescription, activeTransactionSessionContext) - !canRetryRead(serverDescription, noOpSessionContext) - - where: - serverDescription | expected - retryableServerDescription | true - nonRetryableServerDescription | false + canRetryRead(retryableServerDescription, noTransactionSessionContext) + !canRetryRead(retryableServerDescription, activeTransactionSessionContext) } static ConnectionId connectionId = new ConnectionId(new ServerId(new ClusterId(), new ServerAddress())) static ConnectionDescription threeSixConnectionDescription = new ConnectionDescription(connectionId, 6, - STANDALONE, 1000, 100000, 100000, []) + STANDALONE, 1000, 100000, 100000, [], new BsonArray(), 30) static ConnectionDescription threeSixPrimaryConnectionDescription = new ConnectionDescription(connectionId, 6, - REPLICA_SET_PRIMARY, 1000, 100000, 100000, []) + REPLICA_SET_PRIMARY, 1000, 100000, 100000, [], new BsonArray(), 30) static ConnectionDescription threeFourConnectionDescription = new ConnectionDescription(connectionId, 5, - STANDALONE, 1000, 100000, 100000, []) + STANDALONE, 1000, 100000, 100000, [], new BsonArray(), null) static ServerDescription retryableServerDescription = ServerDescription.builder().address(new ServerAddress()).state(CONNECTED) .logicalSessionTimeoutMinutes(1).build() diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/ClientSessionHelper.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/ClientSessionHelper.java index 3226933ab55..30714a6a576 100644 --- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/ClientSessionHelper.java +++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/ClientSessionHelper.java @@ -18,20 +18,12 @@ import com.mongodb.ClientSessionOptions; import com.mongodb.TransactionOptions; -import com.mongodb.connection.ClusterConnectionMode; -import com.mongodb.connection.ClusterDescription; -import com.mongodb.connection.ServerDescription; -import com.mongodb.connection.ServerType; import com.mongodb.internal.session.ServerSessionPool; import com.mongodb.lang.Nullable; import com.mongodb.reactivestreams.client.ClientSession; import reactor.core.publisher.Mono; -import java.util.List; - import static com.mongodb.assertions.Assertions.isTrue; -import static com.mongodb.internal.connection.ClusterDescriptionHelper.getAny; -import static com.mongodb.internal.connection.ClusterDescriptionHelper.getAnyPrimaryOrSecondary; /** *

This class is not part of the public API and may be removed or changed at any time

@@ -56,30 +48,10 @@ Mono withClientSession(@Nullable final ClientSession clientSessio } Mono createClientSessionMono(final ClientSessionOptions options, final OperationExecutor executor) { - ClusterDescription clusterDescription = mongoClient.getCluster().getCurrentDescription(); - if (!getServerDescriptionListToConsiderForSessionSupport(clusterDescription).isEmpty() - && (clusterDescription.getLogicalSessionTimeoutMinutes() != null - || clusterDescription.getConnectionMode() == ClusterConnectionMode.LOAD_BALANCED)) { - return Mono.fromCallable(() -> createClientSession(options, executor)); - } else { - return Mono.create(sink -> - mongoClient.getCluster() - .selectServerAsync(this::getServerDescriptionListToConsiderForSessionSupport, - (serverTuple, t) -> { - if (t != null) { - sink.success(); - } else if (serverTuple.getServerDescription().getLogicalSessionTimeoutMinutes() == null - && serverTuple.getServerDescription().getType() != ServerType.LOAD_BALANCER) { - sink.success(); - } else { - sink.success(createClientSession(options, executor)); - } - }) - ); - } + return Mono.fromCallable(() -> createClientSession(options, executor)); } - private ClientSession createClientSession(final ClientSessionOptions options, final OperationExecutor executor) { + ClientSession createClientSession(final ClientSessionOptions options, final OperationExecutor executor) { ClientSessionOptions mergedOptions = ClientSessionOptions.builder(options) .defaultTransactionOptions( TransactionOptions.merge( @@ -92,12 +64,4 @@ private ClientSession createClientSession(final ClientSessionOptions options, fi .build(); return new ClientSessionPublisherImpl(serverSessionPool, mongoClient, mergedOptions, executor); } - - private List getServerDescriptionListToConsiderForSessionSupport(final ClusterDescription clusterDescription) { - if (clusterDescription.getConnectionMode() == ClusterConnectionMode.SINGLE) { - return getAny(clusterDescription); - } else { - return getAnyPrimaryOrSecondary(clusterDescription); - } - } } diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoClientImpl.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoClientImpl.java index 612804db112..e1164f1451a 100644 --- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoClientImpl.java +++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoClientImpl.java @@ -18,7 +18,6 @@ import com.mongodb.AutoEncryptionSettings; import com.mongodb.ClientSessionOptions; -import com.mongodb.MongoClientException; import com.mongodb.MongoClientSettings; import com.mongodb.MongoDriverInformation; import com.mongodb.connection.ClusterDescription; @@ -238,9 +237,8 @@ public Publisher startSession() { @Override public Publisher startSession(final ClientSessionOptions options) { - return clientSessionHelper.createClientSessionMono(notNull("options", options), executor) - .switchIfEmpty(Mono.create(sink -> sink.error( - new MongoClientException("Sessions are not supported by the MongoDB cluster to which this client is connected")))); + notNull("options", options); + return Mono.fromCallable(() -> clientSessionHelper.createClientSession(options, executor)); } @Override diff --git a/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/MongoClientImplTest.java b/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/MongoClientImplTest.java index bd7a9c39324..1eb42c647d7 100644 --- a/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/MongoClientImplTest.java +++ b/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/MongoClientImplTest.java @@ -22,9 +22,6 @@ import com.mongodb.ReadConcern; import com.mongodb.ServerAddress; import com.mongodb.TransactionOptions; -import com.mongodb.connection.ClusterConnectionMode; -import com.mongodb.connection.ClusterDescription; -import com.mongodb.connection.ClusterType; import com.mongodb.connection.ServerConnectionState; import com.mongodb.connection.ServerDescription; import com.mongodb.internal.client.model.changestream.ChangeStreamLevel; @@ -47,7 +44,6 @@ import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; public class MongoClientImplTest extends TestHelper { @@ -189,10 +185,6 @@ void testStartSession() { .build(); MongoClientImpl mongoClient = createMongoClient(); - Cluster cluster = mongoClient.getCluster(); - when(cluster.getCurrentDescription()) - .thenReturn(new ClusterDescription(ClusterConnectionMode.SINGLE, ClusterType.STANDALONE, singletonList(serverDescription))); - ServerSessionPool serverSessionPool = mock(ServerSessionPool.class); ClientSessionHelper clientSessionHelper = new ClientSessionHelper(mongoClient, serverSessionPool); diff --git a/driver-sync/src/main/com/mongodb/client/internal/MongoClientDelegate.java b/driver-sync/src/main/com/mongodb/client/internal/MongoClientDelegate.java index 39def6af111..8703fc8ce2d 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/MongoClientDelegate.java +++ b/driver-sync/src/main/com/mongodb/client/internal/MongoClientDelegate.java @@ -31,9 +31,6 @@ import com.mongodb.WriteConcern; import com.mongodb.client.ClientSession; import com.mongodb.client.SynchronousContextProvider; -import com.mongodb.connection.ClusterConnectionMode; -import com.mongodb.connection.ClusterDescription; -import com.mongodb.connection.ServerDescription; import com.mongodb.internal.IgnorableRequestContext; import com.mongodb.internal.binding.ClusterAwareReadWriteBinding; import com.mongodb.internal.binding.ClusterBinding; @@ -47,7 +44,6 @@ import com.mongodb.lang.Nullable; import org.bson.codecs.configuration.CodecRegistry; -import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import static com.mongodb.MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL; @@ -55,8 +51,6 @@ import static com.mongodb.ReadPreference.primary; import static com.mongodb.assertions.Assertions.isTrue; import static com.mongodb.assertions.Assertions.notNull; -import static com.mongodb.internal.connection.ClusterDescriptionHelper.getAny; -import static com.mongodb.internal.connection.ClusterDescriptionHelper.getAnyPrimaryOrSecondary; final class MongoClientDelegate { private final Cluster cluster; @@ -90,31 +84,23 @@ public OperationExecutor getOperationExecutor() { return operationExecutor; } - @Nullable public ClientSession createClientSession(final ClientSessionOptions options, final ReadConcern readConcern, final WriteConcern writeConcern, final ReadPreference readPreference) { notNull("readConcern", readConcern); notNull("writeConcern", writeConcern); notNull("readPreference", readPreference); - ClusterDescription connectedClusterDescription = getConnectedClusterDescription(); - - if (connectedClusterDescription.getLogicalSessionTimeoutMinutes() == null - && connectedClusterDescription.getConnectionMode() != ClusterConnectionMode.LOAD_BALANCED) { - return null; - } else { - ClientSessionOptions mergedOptions = ClientSessionOptions.builder(options) - .defaultTransactionOptions( - TransactionOptions.merge( - options.getDefaultTransactionOptions(), - TransactionOptions.builder() - .readConcern(readConcern) - .writeConcern(writeConcern) - .readPreference(readPreference) - .build())) - .build(); - return new ClientSessionImpl(serverSessionPool, originator, mergedOptions, this); - } + ClientSessionOptions mergedOptions = ClientSessionOptions.builder(options) + .defaultTransactionOptions( + TransactionOptions.merge( + options.getDefaultTransactionOptions(), + TransactionOptions.builder() + .readConcern(readConcern) + .writeConcern(writeConcern) + .readPreference(readPreference) + .build())) + .build(); + return new ClientSessionImpl(serverSessionPool, originator, mergedOptions, this); } public void close() { @@ -139,23 +125,6 @@ public ServerSessionPool getServerSessionPool() { return serverSessionPool; } - private ClusterDescription getConnectedClusterDescription() { - ClusterDescription clusterDescription = cluster.getDescription(); - if (getServerDescriptionListToConsiderForSessionSupport(clusterDescription).isEmpty()) { - cluster.selectServer(clusterDescription1 -> getServerDescriptionListToConsiderForSessionSupport(clusterDescription1)); - clusterDescription = cluster.getDescription(); - } - return clusterDescription; - } - - private List getServerDescriptionListToConsiderForSessionSupport(final ClusterDescription clusterDescription) { - if (clusterDescription.getConnectionMode() == ClusterConnectionMode.SINGLE) { - return getAny(clusterDescription); - } else { - return getAnyPrimaryOrSecondary(clusterDescription); - } - } - private class DelegateOperationExecutor implements OperationExecutor { @Override public T execute(final ReadOperation operation, final ReadPreference readPreference, final ReadConcern readConcern) { @@ -175,16 +144,15 @@ public T execute(final ReadOperation operation, final ReadPreference read } ClientSession actualClientSession = getClientSession(session); - ReadBinding binding = getReadBinding(readPreference, readConcern, actualClientSession, - session == null && actualClientSession != null); + ReadBinding binding = getReadBinding(readPreference, readConcern, actualClientSession, session == null); try { - if (session != null && session.hasActiveTransaction() && !binding.getReadPreference().equals(primary())) { + if (actualClientSession.hasActiveTransaction() && !binding.getReadPreference().equals(primary())) { throw new MongoClientException("Read preference in a transaction must be primary"); } return operation.execute(binding); } catch (MongoException e) { - labelException(session, e); + labelException(actualClientSession, e); clearTransactionContextOnTransientTransactionError(session, e); throw e; } finally { @@ -199,13 +167,12 @@ public T execute(final WriteOperation operation, final ReadConcern readCo } ClientSession actualClientSession = getClientSession(session); - WriteBinding binding = getWriteBinding(readConcern, actualClientSession, - session == null && actualClientSession != null); + WriteBinding binding = getWriteBinding(readConcern, actualClientSession, session == null); try { return operation.execute(binding); } catch (MongoException e) { - labelException(session, e); + labelException(actualClientSession, e); clearTransactionContextOnTransientTransactionError(session, e); throw e; } finally { @@ -213,17 +180,17 @@ public T execute(final WriteOperation operation, final ReadConcern readCo } } - WriteBinding getWriteBinding(final ReadConcern readConcern, @Nullable final ClientSession session, final boolean ownsSession) { + WriteBinding getWriteBinding(final ReadConcern readConcern, final ClientSession session, final boolean ownsSession) { return getReadWriteBinding(primary(), readConcern, session, ownsSession); } ReadBinding getReadBinding(final ReadPreference readPreference, final ReadConcern readConcern, - @Nullable final ClientSession session, final boolean ownsSession) { + final ClientSession session, final boolean ownsSession) { return getReadWriteBinding(readPreference, readConcern, session, ownsSession); } ReadWriteBinding getReadWriteBinding(final ReadPreference readPreference, final ReadConcern readConcern, - @Nullable final ClientSession session, final boolean ownsSession) { + final ClientSession session, final boolean ownsSession) { ClusterAwareReadWriteBinding readWriteBinding = new ClusterBinding(cluster, getReadPreferenceForBinding(readPreference, session), readConcern, serverApi, getContext()); @@ -231,14 +198,10 @@ ReadWriteBinding getReadWriteBinding(final ReadPreference readPreference, final readWriteBinding = new CryptBinding(readWriteBinding, crypt); } - if (session != null) { - return new ClientSessionBinding(session, ownsSession, readWriteBinding); - } else { - return readWriteBinding; - } + return new ClientSessionBinding(session, ownsSession, readWriteBinding); } - private RequestContext getContext() { + private RequestContext getContext() { RequestContext context = null; if (contextProvider != null) { context = contextProvider.getContext(); @@ -246,10 +209,9 @@ private RequestContext getContext() { return context == null ? IgnorableRequestContext.INSTANCE : context; } - private void labelException(@Nullable final ClientSession session, final MongoException e) { - if (session != null && session.hasActiveTransaction() - && (e instanceof MongoSocketException || e instanceof MongoTimeoutException - || (e instanceof MongoQueryException && e.getCode() == 91)) + private void labelException(final ClientSession session, final MongoException e) { + if (session.hasActiveTransaction() && (e instanceof MongoSocketException || e instanceof MongoTimeoutException + || e instanceof MongoQueryException && e.getCode() == 91) && !e.hasErrorLabel(UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL)) { e.addLabel(TRANSIENT_TRANSACTION_ERROR_LABEL); } @@ -275,7 +237,6 @@ private ReadPreference getReadPreferenceForBinding(final ReadPreference readPref return readPreference; } - @Nullable ClientSession getClientSession(@Nullable final ClientSession clientSessionFromOperation) { ClientSession session; if (clientSessionFromOperation != null) { diff --git a/driver-sync/src/test/functional/com/mongodb/client/AbstractSessionsProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/AbstractSessionsProseTest.java index dbd2afef464..60d74c4d52e 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/AbstractSessionsProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/AbstractSessionsProseTest.java @@ -16,7 +16,10 @@ package com.mongodb.client; +import com.mongodb.MongoClientException; import com.mongodb.MongoClientSettings; +import com.mongodb.MongoCommandException; +import com.mongodb.ServerAddress; import com.mongodb.client.model.Filters; import com.mongodb.client.model.UpdateOneModel; import com.mongodb.client.model.Updates; @@ -26,28 +29,36 @@ import org.bson.Document; import org.junit.jupiter.api.Test; +import java.io.File; +import java.io.IOException; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import static com.mongodb.ClusterFixture.getDefaultDatabaseName; import static com.mongodb.ClusterFixture.serverVersionAtLeast; import static com.mongodb.client.Fixture.getMongoClientSettingsBuilder; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; +import static org.bson.assertions.Assertions.fail; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assumptions.assumeTrue; -// Prose tests from https://github.com/mongodb/specifications/tree/master/source/sessions +// Prose tests for Sessions specification: https://github.com/mongodb/specifications/tree/master/source/sessions +// Prose test README: https://github.com/mongodb/specifications/tree/master/source/sessions/tests/README.rst public abstract class AbstractSessionsProseTest { + private static final int MONGOCRYPTD_PORT = 47017; + protected abstract MongoClient getMongoClient(MongoClientSettings settings); - // Test 13 from https://github.com/mongodb/specifications/blob/master/source/sessions/driver-sessions.rst#test-plan" + // Test 13 from #13-existing-sessions-are-not-checked-into-a-cleared-pool-after-forking @Test public void shouldCreateServerSessionOnlyAfterConnectionCheckout() throws InterruptedException { assumeTrue(serverVersionAtLeast(3, 6)); @@ -56,14 +67,14 @@ public void shouldCreateServerSessionOnlyAfterConnectionCheckout() throws Interr MongoCollection collection; try (MongoClient client = getMongoClient( getMongoClientSettingsBuilder() - .applyToConnectionPoolSettings(builder -> builder.maxSize(1)) - .addCommandListener(new CommandListener() { - @Override - public void commandStarted(final CommandStartedEvent event) { - lsidSet.add(event.getCommand().getDocument("lsid")); - } - }) - .build())) { + .applyToConnectionPoolSettings(builder -> builder.maxSize(1)) + .addCommandListener(new CommandListener() { + @Override + public void commandStarted(final CommandStartedEvent event) { + lsidSet.add(event.getCommand().getDocument("lsid")); + } + }) + .build())) { collection = client.getDatabase(getDefaultDatabaseName()).getCollection(getClass().getName()); List operations = asList( @@ -103,4 +114,99 @@ public void commandStarted(final CommandStartedEvent event) { assertEquals(1, minLsidSetSize); } } + + // Test 18 from #18-implicit-session-is-ignored-if-connection-does-not-support-sessions + @Test + public void shouldIgnoreImplicitSessionIfConnectionDoesNotSupportSessions() throws IOException { + assumeTrue(serverVersionAtLeast(4, 2)); + Process mongocryptdProcess = startMongocryptdProcess("1"); + try { + // initialize to true in case the command listener is never actually called, in which case the assertFalse will fire + AtomicBoolean containsLsid = new AtomicBoolean(true); + try (MongoClient client = getMongoClient( + getMongocryptdMongoClientSettingsBuilder() + .addCommandListener(new CommandListener() { + @Override + public void commandStarted(final CommandStartedEvent event) { + containsLsid.set(event.getCommand().containsKey("lsid")); + } + }) + .build())) { + + Document helloResponse = client.getDatabase("admin").runCommand(new Document("hello", 1)); + assertFalse((helloResponse.containsKey("logicalSessionTimeoutMinutes"))); + + MongoCollection collection = client.getDatabase(getDefaultDatabaseName()).getCollection(getClass().getName()); + try { + collection.find().first(); + } catch (MongoCommandException e) { + // ignore command errors from mongocryptd + } + assertFalse(containsLsid.get()); + + // reset + containsLsid.set(true); + + try { + collection.insertOne(new Document()); + } catch (MongoCommandException e) { + // ignore command errors from mongocryptd + } + assertFalse(containsLsid.get()); + } + } finally { + mongocryptdProcess.destroy(); + } + } + + // Test 19 from #19-explicit-session-raises-an-error-if-connection-does-not-support-sessions + @Test + public void shouldThrowOnExplicitSessionIfConnectionDoesNotSupportSessions() throws IOException { + assumeTrue(serverVersionAtLeast(4, 2)); + Process mongocryptdProcess = startMongocryptdProcess("2"); + try { + try (MongoClient client = getMongoClient(getMongocryptdMongoClientSettingsBuilder().build())) { + MongoCollection collection = client.getDatabase(getDefaultDatabaseName()).getCollection(getClass().getName()); + + Document helloResponse = client.getDatabase("admin").runCommand(new Document("hello", 1)); + assertFalse((helloResponse.containsKey("logicalSessionTimeoutMinutes"))); + + try (ClientSession session = client.startSession()) { + String expectedClientExceptionMessage = + "Attempting to use a ClientSession while connected to a server that doesn't support sessions"; + try { + collection.find(session).first(); + fail("Expected MongoClientException"); + } catch (MongoClientException e) { + assertEquals(expectedClientExceptionMessage, e.getMessage()); + } + + try { + collection.insertOne(session, new Document()); + fail("Expected MongoClientException"); + } catch (MongoClientException e) { + assertEquals(expectedClientExceptionMessage, e.getMessage()); + } + } + } + } finally { + mongocryptdProcess.destroy(); + } + } + + private static MongoClientSettings.Builder getMongocryptdMongoClientSettingsBuilder() { + return MongoClientSettings.builder() + .applyToClusterSettings(builder -> + builder.hosts(singletonList(new ServerAddress("localhost", MONGOCRYPTD_PORT)))); + } + + private static Process startMongocryptdProcess(final String pidSuffix) throws IOException { + ProcessBuilder processBuilder = new ProcessBuilder(asList("mongocryptd", + "--port", Integer.toString(MONGOCRYPTD_PORT), + "--pidfilepath", "mongocryptd-" + pidSuffix + ".pid")); + processBuilder.redirectErrorStream(true); + processBuilder.redirectOutput(new File("/tmp/mongocryptd.log")); + return processBuilder.start(); + } } + From 582356c3d8c1d700f5a89f6226821ec7f47983c8 Mon Sep 17 00:00:00 2001 From: Jeff Yemin Date: Thu, 9 Mar 2023 18:47:13 -0500 Subject: [PATCH 04/11] Work around create failure in unified tests with retry (#1094) On 4.0 sharded clusters, the create collection command often fails with error code 11601 (Interrupted). Work around it by retrying the command. JAVA-4883 --- .../com/mongodb/client/AbstractUnifiedTest.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/driver-sync/src/test/functional/com/mongodb/client/AbstractUnifiedTest.java b/driver-sync/src/test/functional/com/mongodb/client/AbstractUnifiedTest.java index 76e855146eb..7b3d01e7666 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/AbstractUnifiedTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/AbstractUnifiedTest.java @@ -71,6 +71,7 @@ import static com.mongodb.ClusterFixture.isDataLakeTest; import static com.mongodb.ClusterFixture.isSharded; import static com.mongodb.ClusterFixture.serverVersionAtLeast; +import static com.mongodb.ClusterFixture.serverVersionLessThan; import static com.mongodb.ClusterFixture.setDirectConnection; import static com.mongodb.client.CommandMonitoringTestHelper.assertEventsEquality; import static com.mongodb.client.CommandMonitoringTestHelper.getExpectedEvents; @@ -168,7 +169,16 @@ public void setUp() { collectionHelper.killAllSessions(); if (!isDataLakeTest()) { - collectionHelper.create(collectionName, new CreateCollectionOptions(), WriteConcern.MAJORITY); + try { + collectionHelper.create(collectionName, new CreateCollectionOptions(), WriteConcern.MAJORITY); + } catch (MongoCommandException e) { + // Older sharded clusters sometimes reply with this error. Work around it by retrying once. + if (e.getErrorCode() == 11601 && isSharded() && serverVersionLessThan(4, 2)) { + collectionHelper.create(collectionName, new CreateCollectionOptions(), WriteConcern.MAJORITY); + } else { + throw e; + } + } } if (!data.isEmpty()) { From 84174074edec708db7bcc88cf51b70b8b1f1429c Mon Sep 17 00:00:00 2001 From: ashni <105304831+ashni-mongodb@users.noreply.github.com> Date: Tue, 14 Mar 2023 17:17:24 -0400 Subject: [PATCH 05/11] Update README.md (#1095) JAVA-4899 --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ade4b7cebbb..b3713d0ad1f 100644 --- a/README.md +++ b/README.md @@ -6,18 +6,24 @@ Release notes are available [here](https://github.com/mongodb/mongo-java-driver/ Reference and API documentation is available [here](http://mongodb.github.io/mongo-java-driver/). +## Tutorials / Training + +For tutorials on how to use the MongoDB Java Driver, please reference MongoDB University's Java offerings [here](https://learn.mongodb.com/catalog?labels=%5B%22Language%22%5D&values=%5B%22Java%22%5D). Additional tutorials, videos, and code examples using the Java Driver can also be found in the [MongoDB Developer Center](https://www.mongodb.com/developer/languages/java/). + ## Support / Feedback For issues with, questions about, or feedback for the MongoDB Java driver, please look into our [support channels](https://www.mongodb.com/docs/manual/support/). Please do not email any of the Java driver developers directly with issues or -questions - you're more likely to get an answer on the [MongoDB Community Forums](https://community.mongodb.com/tags/c/drivers-odms-connectors/7/java-driver). +questions - you're more likely to get an answer on the [MongoDB Community Forums](https://community.mongodb.com/tags/c/drivers-odms-connectors/7/java-driver) or [StackOverflow](https://stackoverflow.com/questions/tagged/mongodb+java). At a minimum, please include in your description the exact version of the driver that you are using. If you are having connectivity issues, it's often also useful to paste in the line of code where you construct the MongoClient instance, along with the values of all parameters that you pass to the constructor. You should also check your application logs for any connectivity-related exceptions and post those as well. + + ## Bugs / Feature Requests Think you’ve found a bug? Want to see a new feature in the Java driver? Please open a From ed0f209f0a5cf0b72db17f0952512c2b2d55d2dc Mon Sep 17 00:00:00 2001 From: Jeff Yemin Date: Mon, 20 Mar 2023 14:18:19 -0400 Subject: [PATCH 06/11] Update .gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 8a32e200bbf..ebebd4073e5 100644 --- a/.gitignore +++ b/.gitignore @@ -43,7 +43,7 @@ local.properties .java-version # mongocryptd -**/mongocryptd.pid +**/mongocryptd*.pid # shell scripts *.sh From c36e7ee18502a9095bcf89e26f651f0b696732ab Mon Sep 17 00:00:00 2001 From: Ross Lawley Date: Tue, 21 Mar 2023 14:34:34 +0000 Subject: [PATCH 07/11] Initial driver-kotlin-sync implementation Wraps the driver-sync to provide a kotlin sync API In general a faithful wrapping of the sync java driver. Uses reified overloads to provide synaptic sugar instead of passing resultClass. MongoIterable deviates from the sync API so to not implement Iterable in order to protect the user from leaving open cursors. In Kotlin there are two Iterable style implementations: Iterable and Sequence neither are auto closable. Sequence is more appropriate here as it lazily consumes the iterator, but it does not auto close. See: https://youtrack.jetbrains.com/issue/KT-34719 So MongoIterable provides a loan method that treats the cursor as a Sequence and automatically closes after use. JAVA-4869 --- .evergreen/.evg.yml | 24 + .evergreen/run-kotlin-tests.sh | 37 + config/detekt/baseline.xml | 15 + config/detekt/detekt.yml | 712 +++++++++ config/mongodb.license | 15 + config/spotbugs/exclude.xml | 5 + driver-kotlin-sync/build.gradle.kts | 185 +++ .../com/mongodb/kotlin/client/CrudTest.kt | 49 + .../mongodb/kotlin/client/UnifiedCrudTest.kt | 52 + .../com/mongodb/kotlin/client/UnifiedTest.kt | 51 + .../syncadapter/SyncAggregateIterable.kt | 69 + .../syncadapter/SyncChangeStreamIterable.kt | 61 + .../client/syncadapter/SyncClientSession.kt | 93 ++ .../syncadapter/SyncDistinctIterable.kt | 35 + .../client/syncadapter/SyncFindIterable.kt | 92 ++ .../SyncListCollectionsIterable.kt | 36 + .../syncadapter/SyncListDatabasesIterable.kt | 44 + .../syncadapter/SyncListIndexesIterable.kt | 31 + .../SyncMongoChangeStreamCursor.kt | 25 + .../client/syncadapter/SyncMongoClient.kt | 89 ++ .../client/syncadapter/SyncMongoCollection.kt | 505 ++++++ .../client/syncadapter/SyncMongoCursor.kt | 40 + .../client/syncadapter/SyncMongoDatabase.kt | 197 +++ .../client/syncadapter/SyncMongoIterable.kt | 40 + .../kotlin/client/AggregateIterable.kt | 191 +++ .../kotlin/client/ChangeStreamIterable.kt | 185 +++ .../mongodb/kotlin/client/ClientSession.kt | 98 ++ .../mongodb/kotlin/client/DistinctIterable.kt | 85 + .../com/mongodb/kotlin/client/FindIterable.kt | 287 ++++ .../kotlin/client/ListCollectionsIterable.kt | 75 + .../kotlin/client/ListDatabasesIterable.kt | 94 ++ .../kotlin/client/ListIndexesIterable.kt | 65 + .../com/mongodb/kotlin/client/MongoClient.kt | 282 ++++ .../mongodb/kotlin/client/MongoCollection.kt | 1381 +++++++++++++++++ .../com/mongodb/kotlin/client/MongoCursor.kt | 135 ++ .../mongodb/kotlin/client/MongoDatabase.kt | 532 +++++++ .../mongodb/kotlin/client/MongoIterable.kt | 90 ++ .../kotlin/client/AggregateIterableTest.kt | 105 ++ .../kotlin/client/ChangeStreamIterableTest.kt | 93 ++ .../kotlin/client/ClientSessionTest.kt | 98 ++ .../kotlin/client/DistinctIterableTest.kt | 69 + .../kotlin/client/ExtensionMethodsTest.kt | 56 + .../mongodb/kotlin/client/FindIterableTest.kt | 128 ++ .../client/ListCollectionsIterableTest.kt | 66 + .../client/ListDatabasesIterableTest.kt | 69 + .../kotlin/client/ListIndexesIterableTest.kt | 61 + .../mongodb/kotlin/client/MockitoHelper.kt | 53 + .../client/MongoChangeStreamCursorTest.kt | 72 + .../mongodb/kotlin/client/MongoClientTest.kt | 171 ++ .../kotlin/client/MongoCollectionTest.kt | 882 +++++++++++ .../mongodb/kotlin/client/MongoCursorTest.kt | 71 + .../kotlin/client/MongoDatabaseTest.kt | 375 +++++ .../kotlin/client/MongoIterableTest.kt | 127 ++ settings.gradle | 1 + 54 files changed, 8499 insertions(+) create mode 100755 .evergreen/run-kotlin-tests.sh create mode 100644 config/detekt/baseline.xml create mode 100644 config/detekt/detekt.yml create mode 100644 config/mongodb.license create mode 100644 driver-kotlin-sync/build.gradle.kts create mode 100644 driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/CrudTest.kt create mode 100644 driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/UnifiedCrudTest.kt create mode 100644 driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/UnifiedTest.kt create mode 100644 driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncAggregateIterable.kt create mode 100644 driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncChangeStreamIterable.kt create mode 100644 driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncClientSession.kt create mode 100644 driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncDistinctIterable.kt create mode 100644 driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncFindIterable.kt create mode 100644 driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncListCollectionsIterable.kt create mode 100644 driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncListDatabasesIterable.kt create mode 100644 driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncListIndexesIterable.kt create mode 100644 driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncMongoChangeStreamCursor.kt create mode 100644 driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncMongoClient.kt create mode 100644 driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncMongoCollection.kt create mode 100644 driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncMongoCursor.kt create mode 100644 driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncMongoDatabase.kt create mode 100644 driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncMongoIterable.kt create mode 100644 driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/AggregateIterable.kt create mode 100644 driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/ChangeStreamIterable.kt create mode 100644 driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/ClientSession.kt create mode 100644 driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/DistinctIterable.kt create mode 100644 driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/FindIterable.kt create mode 100644 driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/ListCollectionsIterable.kt create mode 100644 driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/ListDatabasesIterable.kt create mode 100644 driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/ListIndexesIterable.kt create mode 100644 driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/MongoClient.kt create mode 100644 driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/MongoCollection.kt create mode 100644 driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/MongoCursor.kt create mode 100644 driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/MongoDatabase.kt create mode 100644 driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/MongoIterable.kt create mode 100644 driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/AggregateIterableTest.kt create mode 100644 driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/ChangeStreamIterableTest.kt create mode 100644 driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/ClientSessionTest.kt create mode 100644 driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/DistinctIterableTest.kt create mode 100644 driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/ExtensionMethodsTest.kt create mode 100644 driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/FindIterableTest.kt create mode 100644 driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/ListCollectionsIterableTest.kt create mode 100644 driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/ListDatabasesIterableTest.kt create mode 100644 driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/ListIndexesIterableTest.kt create mode 100644 driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/MockitoHelper.kt create mode 100644 driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/MongoChangeStreamCursorTest.kt create mode 100644 driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/MongoClientTest.kt create mode 100644 driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/MongoCollectionTest.kt create mode 100644 driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/MongoCursorTest.kt create mode 100644 driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/MongoDatabaseTest.kt create mode 100644 driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/MongoIterableTest.kt diff --git a/.evergreen/.evg.yml b/.evergreen/.evg.yml index 40c104ddbf2..9187ff62edb 100644 --- a/.evergreen/.evg.yml +++ b/.evergreen/.evg.yml @@ -289,6 +289,16 @@ functions: ${PREPARE_SHELL} SCALA="${SCALA}" AUTH="${AUTH}" SSL="${SSL}" MONGODB_URI="${MONGODB_URI}" SAFE_FOR_MULTI_MONGOS="${SAFE_FOR_MULTI_MONGOS}" TOPOLOGY="${TOPOLOGY}" JAVA_VERSION="${JAVA_VERSION}" .evergreen/run-scala-tests.sh + "run kotlin tests": + - command: shell.exec + type: test + params: + working_dir: "src" + script: | + ${PREPARE_SHELL} + AUTH="${AUTH}" SSL="${SSL}" MONGODB_URI="${MONGODB_URI}" SAFE_FOR_MULTI_MONGOS="${SAFE_FOR_MULTI_MONGOS}" TOPOLOGY="${TOPOLOGY}" JAVA_VERSION="${JAVA_VERSION}" .evergreen/run-kotlin-tests.sh + + "run socket tests": - command: shell.exec type: test @@ -904,6 +914,11 @@ tasks: - func: "bootstrap mongo-orchestration" - func: "run scala tests" + - name: "kotlin-tests" + commands: + - func: "bootstrap mongo-orchestration" + - func: "run kotlin tests" + - name: "reactive-streams-tck-test" commands: - func: "bootstrap mongo-orchestration" @@ -1437,6 +1452,8 @@ tasks: name: "plain-auth-test" - variant: ".test-scala-variant" name: "scala-tests" + - variant: ".test-kotlin-variant" + name: "kotlin-tests" - variant: ".tests-netty-variant" name: "netty-test" commands: @@ -2052,6 +2069,13 @@ buildvariants: tasks: - name: "scala-tests" +- matrix_name: "kotlin-tests" + matrix_spec: { auth: "noauth", ssl: "nossl", jdk: ["jdk8", "jdk17"], version: ["6.0"], topology: "replicaset", os: "ubuntu" } + display_name: "Kotlin: ${jdk} ${version} ${topology} ${os}" + tags: ["test-kotlin-variant"] + tasks: + - name: "kotlin-tests" + - name: publish-snapshot display_name: "Publish Snapshot" run_on: ubuntu1804-test diff --git a/.evergreen/run-kotlin-tests.sh b/.evergreen/run-kotlin-tests.sh new file mode 100755 index 00000000000..836a48516b7 --- /dev/null +++ b/.evergreen/run-kotlin-tests.sh @@ -0,0 +1,37 @@ +#!/bin/bash + +set -o xtrace # Write all commands first to stderr +set -o errexit # Exit the script with error if any of the commands fail + + +AUTH=${AUTH:-noauth} +SSL=${SSL:-nossl} +MONGODB_URI=${MONGODB_URI:-} +TOPOLOGY=${TOPOLOGY:-standalone} +SAFE_FOR_MULTI_MONGOS=${SAFE_FOR_MULTI_MONGOS:-} + +############################################ +# Main Program # +############################################ +RELATIVE_DIR_PATH="$(dirname "${BASH_SOURCE:-$0}")" +. "${RELATIVE_DIR_PATH}/javaConfig.bash" + + +if [ "$SSL" != "nossl" ]; then + echo -e "\nSSL support not configured for Kotlin tests" + exit 1 +fi + +if [ "$AUTH" != "noauth" ]; then + echo -e "\nAuth support not configured for Kotlin tests" + exit 1 +fi + +if [ "$SAFE_FOR_MULTI_MONGOS" == "true" ]; then + export MULTI_MONGOS_URI_SYSTEM_PROPERTY="-Dorg.mongodb.test.multi.mongos.uri=${MONGODB_URI}" +fi + +echo "Running Kotlin tests" + +./gradlew -version +./gradlew :driver-kotlin-sync:kCheck -Dorg.mongodb.test.uri=${MONGODB_URI} ${MULTI_MONGOS_URI_SYSTEM_PROPERTY} diff --git a/config/detekt/baseline.xml b/config/detekt/baseline.xml new file mode 100644 index 00000000000..2297ba60044 --- /dev/null +++ b/config/detekt/baseline.xml @@ -0,0 +1,15 @@ + + + + + IteratorNotThrowingNoSuchElementException:MongoCursor.kt$MongoCursor<T : Any> : IteratorCloseable + LargeClass:MongoCollectionTest.kt$MongoCollectionTest + LongMethod:FindIterableTest.kt$FindIterableTest$@Suppress("DEPRECATION") @Test fun shouldCallTheUnderlyingMethods() + MaxLineLength:MapReduceIterable.kt$MapReduceIterable$* + SwallowedException:MockitoHelper.kt$MockitoHelper.DeepReflectionEqMatcher$e: Throwable + TooManyFunctions:ClientSession.kt$ClientSession : jClientSession + TooManyFunctions:FindIterable.kt$FindIterable<T : Any> : MongoIterable + TooManyFunctions:MongoCollection.kt$MongoCollection<T : Any> + TooManyFunctions:MongoDatabase.kt$MongoDatabase + + diff --git a/config/detekt/detekt.yml b/config/detekt/detekt.yml new file mode 100644 index 00000000000..4c083b0bce5 --- /dev/null +++ b/config/detekt/detekt.yml @@ -0,0 +1,712 @@ +build: + maxIssues: 0 + excludeCorrectable: false + weights: + # complexity: 2 + # LongParameterList: 1 + # style: 1 + # comments: 1 + +config: + validation: true + warningsAsErrors: false + # when writing own rules with new properties, exclude the property path e.g.: 'my_rule_set,.*>.*>[my_property]' + excludes: '' + +processors: + active: true + exclude: + - 'DetektProgressListener' + # - 'KtFileCountProcessor' + # - 'PackageCountProcessor' + # - 'ClassCountProcessor' + # - 'FunctionCountProcessor' + # - 'PropertyCountProcessor' + # - 'ProjectComplexityProcessor' + # - 'ProjectCognitiveComplexityProcessor' + # - 'ProjectLLOCProcessor' + # - 'ProjectCLOCProcessor' + # - 'ProjectLOCProcessor' + # - 'ProjectSLOCProcessor' + # - 'LicenseHeaderLoaderExtension' + +console-reports: + active: true + exclude: + - 'ProjectStatisticsReport' + - 'ComplexityReport' + - 'NotificationReport' + - 'FindingsReport' + - 'FileBasedFindingsReport' + # - 'LiteFindingsReport' + +output-reports: + active: true + exclude: + # - 'TxtOutputReport' + # - 'XmlOutputReport' + # - 'HtmlOutputReport' + # - 'MdOutputReport' + +comments: + active: true + AbsentOrWrongFileLicense: + active: false + licenseTemplateFile: 'license.template' + licenseTemplateIsRegex: false + CommentOverPrivateFunction: + active: false + CommentOverPrivateProperty: + active: false + DeprecatedBlockTag: + active: false + EndOfSentenceFormat: + active: false + endOfSentenceFormat: '([.?!][ \t\n\r\f<])|([.?!:]$)' + KDocReferencesNonPublicProperty: + active: false + excludes: ['**/test/**'] + OutdatedDocumentation: + active: false + matchTypeParameters: true + matchDeclarationsOrder: true + allowParamOnConstructorProperties: false + UndocumentedPublicClass: + active: false + excludes: ['**/test/**'] + searchInNestedClass: true + searchInInnerClass: true + searchInInnerObject: true + searchInInnerInterface: true + UndocumentedPublicFunction: + active: false + excludes: ['**/test/**'] + UndocumentedPublicProperty: + active: false + excludes: ['**/test/**'] + +complexity: + active: true + ComplexCondition: + active: true + threshold: 4 + ComplexInterface: + active: false + threshold: 10 + includeStaticDeclarations: false + includePrivateDeclarations: false + ComplexMethod: + active: true + threshold: 15 + ignoreSingleWhenExpression: false + ignoreSimpleWhenEntries: false + ignoreNestingFunctions: false + nestingFunctions: + - 'also' + - 'apply' + - 'forEach' + - 'isNotNull' + - 'ifNull' + - 'let' + - 'run' + - 'use' + - 'with' + LabeledExpression: + active: false + ignoredLabels: [] + LargeClass: + active: true + threshold: 600 + LongMethod: + active: true + threshold: 60 + LongParameterList: + active: true + functionThreshold: 6 + constructorThreshold: 7 + ignoreDefaultParameters: false + ignoreDataClasses: true + ignoreAnnotatedParameter: [] + MethodOverloading: + active: false + threshold: 6 + NamedArguments: + active: false + threshold: 3 + ignoreArgumentsMatchingNames: false + NestedBlockDepth: + active: true + threshold: 4 + NestedScopeFunctions: + active: false + threshold: 1 + functions: + - 'kotlin.apply' + - 'kotlin.run' + - 'kotlin.with' + - 'kotlin.let' + - 'kotlin.also' + ReplaceSafeCallChainWithRun: + active: false + StringLiteralDuplication: + active: false + excludes: ['**/test/**'] + threshold: 3 + ignoreAnnotation: true + excludeStringsWithLessThan5Characters: true + ignoreStringsRegex: '$^' + TooManyFunctions: + active: true + excludes: ['**/test/**'] + thresholdInFiles: 25 + thresholdInClasses: 25 + thresholdInInterfaces: 25 + thresholdInObjects: 25 + thresholdInEnums: 25 + ignoreDeprecated: false + ignorePrivate: false + ignoreOverridden: false + +coroutines: + active: true + GlobalCoroutineUsage: + active: false + InjectDispatcher: + active: true + dispatcherNames: + - 'IO' + - 'Default' + - 'Unconfined' + RedundantSuspendModifier: + active: true + SleepInsteadOfDelay: + active: true + SuspendFunWithCoroutineScopeReceiver: + active: false + SuspendFunWithFlowReturnType: + active: true + +empty-blocks: + active: true + EmptyCatchBlock: + active: true + allowedExceptionNameRegex: '_|(ignore|expected).*' + EmptyClassBlock: + active: true + EmptyDefaultConstructor: + active: true + EmptyDoWhileBlock: + active: true + EmptyElseBlock: + active: true + EmptyFinallyBlock: + active: true + EmptyForBlock: + active: true + EmptyFunctionBlock: + active: true + ignoreOverridden: false + EmptyIfBlock: + active: true + EmptyInitBlock: + active: true + EmptyKtFile: + active: true + EmptySecondaryConstructor: + active: true + EmptyTryBlock: + active: true + EmptyWhenBlock: + active: true + EmptyWhileBlock: + active: true + +exceptions: + active: true + ExceptionRaisedInUnexpectedLocation: + active: true + methodNames: + - 'equals' + - 'finalize' + - 'hashCode' + - 'toString' + InstanceOfCheckForException: + active: true + excludes: ['**/test/**'] + NotImplementedDeclaration: + active: false + ObjectExtendsThrowable: + active: false + PrintStackTrace: + active: true + RethrowCaughtException: + active: true + ReturnFromFinally: + active: true + ignoreLabeled: false + SwallowedException: + active: true + ignoredExceptionTypes: + - 'InterruptedException' + - 'MalformedURLException' + - 'NumberFormatException' + - 'ParseException' + allowedExceptionNameRegex: '_|(ignore|expected).*' + ThrowingExceptionFromFinally: + active: true + ThrowingExceptionInMain: + active: false + ThrowingExceptionsWithoutMessageOrCause: + active: true + excludes: ['**/test/**'] + exceptions: + - 'ArrayIndexOutOfBoundsException' + - 'Exception' + - 'IllegalArgumentException' + - 'IllegalMonitorStateException' + - 'IllegalStateException' + - 'IndexOutOfBoundsException' + - 'NullPointerException' + - 'RuntimeException' + - 'Throwable' + ThrowingNewInstanceOfSameException: + active: true + TooGenericExceptionCaught: + active: true + excludes: ['**/test/**'] + exceptionNames: + - 'ArrayIndexOutOfBoundsException' + - 'Error' + - 'Exception' + - 'IllegalMonitorStateException' + - 'IndexOutOfBoundsException' + - 'NullPointerException' + - 'RuntimeException' + - 'Throwable' + allowedExceptionNameRegex: '_|(ignore|expected).*' + TooGenericExceptionThrown: + active: true + exceptionNames: + - 'Error' + - 'Exception' + - 'RuntimeException' + - 'Throwable' + +naming: + active: true + BooleanPropertyNaming: + active: false + allowedPattern: '^(is|has|are)' + ignoreOverridden: true + ClassNaming: + active: true + classPattern: '[A-Z][a-zA-Z0-9]*' + ConstructorParameterNaming: + active: true + parameterPattern: '[a-z][A-Za-z0-9]*' + privateParameterPattern: '[a-z][A-Za-z0-9]*' + excludeClassPattern: '$^' + ignoreOverridden: true + EnumNaming: + active: true + enumEntryPattern: '[A-Z][_a-zA-Z0-9]*' + ForbiddenClassName: + active: false + forbiddenName: [] + FunctionMaxLength: + active: false + maximumFunctionNameLength: 30 + FunctionMinLength: + active: false + minimumFunctionNameLength: 3 + FunctionNaming: + active: true + excludes: ['**/test/**'] + functionPattern: '[a-z][a-zA-Z0-9]*' + excludeClassPattern: '$^' + ignoreOverridden: true + FunctionParameterNaming: + active: true + parameterPattern: '[a-z][A-Za-z0-9]*' + excludeClassPattern: '$^' + ignoreOverridden: true + InvalidPackageDeclaration: + active: true + rootPackage: '' + requireRootInDeclaration: false + LambdaParameterNaming: + active: false + parameterPattern: '[a-z][A-Za-z0-9]*|_' + MatchingDeclarationName: + active: true + mustBeFirst: true + MemberNameEqualsClassName: + active: true + ignoreOverridden: true + NoNameShadowing: + active: true + NonBooleanPropertyPrefixedWithIs: + active: false + ObjectPropertyNaming: + active: true + constantPattern: '[A-Za-z][_A-Za-z0-9]*' + propertyPattern: '[A-Za-z][_A-Za-z0-9]*' + privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*' + PackageNaming: + active: true + packagePattern: '[a-z]+(\.[a-z][A-Za-z0-9]*)*' + TopLevelPropertyNaming: + active: true + constantPattern: '[A-Z][_A-Z0-9]*' + propertyPattern: '[A-Za-z][_A-Za-z0-9]*' + privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*' + VariableMaxLength: + active: false + maximumVariableNameLength: 64 + VariableMinLength: + active: false + minimumVariableNameLength: 1 + VariableNaming: + active: true + variablePattern: '[a-z][A-Za-z0-9]*' + privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*' + excludeClassPattern: '$^' + ignoreOverridden: true + +performance: + active: true + ArrayPrimitive: + active: true + CouldBeSequence: + active: false + threshold: 3 + ForEachOnRange: + active: true + excludes: ['**/test/**'] + SpreadOperator: + active: true + excludes: ['**/test/**'] + UnnecessaryTemporaryInstantiation: + active: true + +potential-bugs: + active: true + AvoidReferentialEquality: + active: true + forbiddenTypePatterns: + - 'kotlin.String' + CastToNullableType: + active: false + Deprecation: + active: false + DontDowncastCollectionTypes: + active: false + DoubleMutabilityForCollection: + active: true + mutableTypes: + - 'kotlin.collections.MutableList' + - 'kotlin.collections.MutableMap' + - 'kotlin.collections.MutableSet' + - 'java.util.ArrayList' + - 'java.util.LinkedHashSet' + - 'java.util.HashSet' + - 'java.util.LinkedHashMap' + - 'java.util.HashMap' + DuplicateCaseInWhenExpression: + active: true + ElseCaseInsteadOfExhaustiveWhen: + active: false + EqualsAlwaysReturnsTrueOrFalse: + active: true + EqualsWithHashCodeExist: + active: true + ExitOutsideMain: + active: false + ExplicitGarbageCollectionCall: + active: true + HasPlatformType: + active: true + IgnoredReturnValue: + active: true + restrictToAnnotatedMethods: true + returnValueAnnotations: + - '*.CheckResult' + - '*.CheckReturnValue' + ignoreReturnValueAnnotations: + - '*.CanIgnoreReturnValue' + ignoreFunctionCall: [] + ImplicitDefaultLocale: + active: true + ImplicitUnitReturnType: + active: false + allowExplicitReturnType: true + InvalidRange: + active: true + IteratorHasNextCallsNextMethod: + active: true + IteratorNotThrowingNoSuchElementException: + active: true + LateinitUsage: + active: false + excludes: ['**/test/**'] + ignoreOnClassesPattern: '' + MapGetWithNotNullAssertionOperator: + active: true + MissingPackageDeclaration: + active: false + excludes: ['**/*.kts'] + MissingWhenCase: + active: true + allowElseExpression: true + NullCheckOnMutableProperty: + active: false + NullableToStringCall: + active: false + RedundantElseInWhen: + active: true + UnconditionalJumpStatementInLoop: + active: false + UnnecessaryNotNullOperator: + active: true + UnnecessarySafeCall: + active: true + UnreachableCatchBlock: + active: true + UnreachableCode: + active: true + UnsafeCallOnNullableType: + active: true + excludes: ['**/test/**'] + UnsafeCast: + active: true + UnusedUnaryOperator: + active: true + UselessPostfixExpression: + active: true + WrongEqualsTypeParameter: + active: true + +style: + active: true + CanBeNonNullable: + active: false + CascadingCallWrapping: + active: false + includeElvis: true + ClassOrdering: + active: false + CollapsibleIfStatements: + active: false + DataClassContainsFunctions: + active: false + conversionFunctionPrefix: 'to' + DataClassShouldBeImmutable: + active: false + DestructuringDeclarationWithTooManyEntries: + active: true + maxDestructuringEntries: 3 + EqualsNullCall: + active: true + EqualsOnSignatureLine: + active: false + ExplicitCollectionElementAccessMethod: + active: false + ExplicitItLambdaParameter: + active: true + ExpressionBodySyntax: + active: false + includeLineWrapping: false + ForbiddenComment: + active: true + values: + - 'FIXME:' + - 'STOPSHIP:' + - 'TODO:' + allowedPatterns: '' + customMessage: '' + ForbiddenImport: + active: false + imports: [] + forbiddenPatterns: '' + ForbiddenMethodCall: + active: false + methods: + - 'kotlin.io.print' + - 'kotlin.io.println' + ForbiddenPublicDataClass: + active: true + excludes: ['**'] + ignorePackages: + - '*.internal' + - '*.internal.*' + ForbiddenSuppress: + active: false + rules: [] + ForbiddenVoid: + active: true + ignoreOverridden: false + ignoreUsageInGenerics: false + FunctionOnlyReturningConstant: + active: true + ignoreOverridableFunction: true + ignoreActualFunction: true + excludedFunctions: '' + LibraryCodeMustSpecifyReturnType: + active: true + excludes: ['**'] + LibraryEntitiesShouldNotBePublic: + active: true + excludes: ['**'] + LoopWithTooManyJumpStatements: + active: true + maxJumpCount: 1 + MagicNumber: + active: true + excludes: ['**/test/**', '**/*.kts'] + ignoreNumbers: + - '-1' + - '0' + - '1' + - '2' + ignoreHashCodeFunction: true + ignorePropertyDeclaration: false + ignoreLocalVariableDeclaration: false + ignoreConstantDeclaration: true + ignoreCompanionObjectPropertyDeclaration: true + ignoreAnnotation: false + ignoreNamedArgument: true + ignoreEnums: true + ignoreRanges: false + ignoreExtensionFunctions: true + MandatoryBracesIfStatements: + active: false + MandatoryBracesLoops: + active: false + MaxChainedCallsOnSameLine: + active: false + maxChainedCalls: 5 + MaxLineLength: + active: true + maxLineLength: 120 + excludePackageStatements: true + excludeImportStatements: true + excludeCommentStatements: false + MayBeConst: + active: true + ModifierOrder: + active: true + MultilineLambdaItParameter: + active: false + NestedClassesVisibility: + active: true + NewLineAtEndOfFile: + active: true + NoTabs: + active: false + NullableBooleanCheck: + active: false + ObjectLiteralToLambda: + active: true + OptionalAbstractKeyword: + active: true + OptionalUnit: + active: false + OptionalWhenBraces: + active: false + PreferToOverPairSyntax: + active: false + ProtectedMemberInFinalClass: + active: true + RedundantExplicitType: + active: false + RedundantHigherOrderMapUsage: + active: true + RedundantVisibilityModifierRule: + active: false + ReturnCount: + active: true + max: 2 + excludedFunctions: 'equals' + excludeLabeled: false + excludeReturnFromLambda: true + excludeGuardClauses: false + SafeCast: + active: true + SerialVersionUIDInSerializableClass: + active: true + SpacingBetweenPackageAndImports: + active: false + ThrowsCount: + active: true + max: 2 + excludeGuardClauses: false + TrailingWhitespace: + active: false + UnderscoresInNumericLiterals: + active: false + acceptableLength: 4 + allowNonStandardGrouping: false + UnnecessaryAbstractClass: + active: true + UnnecessaryAnnotationUseSiteTarget: + active: false + UnnecessaryApply: + active: true + UnnecessaryBackticks: + active: false + UnnecessaryFilter: + active: true + UnnecessaryInheritance: + active: true + UnnecessaryInnerClass: + active: false + UnnecessaryLet: + active: false + UnnecessaryParentheses: + active: false + UntilInsteadOfRangeTo: + active: false + UnusedImports: + active: false + UnusedPrivateClass: + active: true + UnusedPrivateMember: + active: true + allowedNames: '(_|ignored|expected|serialVersionUID)' + UseAnyOrNoneInsteadOfFind: + active: true + UseArrayLiteralsInAnnotations: + active: true + UseCheckNotNull: + active: true + UseCheckOrError: + active: true + UseDataClass: + active: false + allowVars: false + UseEmptyCounterpart: + active: false + UseIfEmptyOrIfBlank: + active: false + UseIfInsteadOfWhen: + active: false + UseIsNullOrEmpty: + active: true + UseOrEmpty: + active: true + UseRequire: + active: true + UseRequireNotNull: + active: true + UselessCallOnNotNull: + active: true + UtilityClassWithPublicConstructor: + active: true + VarCouldBeVal: + active: true + ignoreLateinitVar: false + WildcardImport: + active: true + excludes: ['**/test/**'] + excludeImports: + - 'java.util.*' diff --git a/config/mongodb.license b/config/mongodb.license new file mode 100644 index 00000000000..6a2444433a7 --- /dev/null +++ b/config/mongodb.license @@ -0,0 +1,15 @@ +/* + * 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. + */ diff --git a/config/spotbugs/exclude.xml b/config/spotbugs/exclude.xml index 2a8b69a2e7b..f53c390741a 100644 --- a/config/spotbugs/exclude.xml +++ b/config/spotbugs/exclude.xml @@ -170,6 +170,11 @@ + + + + + diff --git a/driver-kotlin-sync/build.gradle.kts b/driver-kotlin-sync/build.gradle.kts new file mode 100644 index 00000000000..32ace53c46f --- /dev/null +++ b/driver-kotlin-sync/build.gradle.kts @@ -0,0 +1,185 @@ +/* + * 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-sync") + +description = "The MongoDB Kotlin Driver" + +ext.set("pomName", "MongoDB Kotlin 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") + + api(project(path = ":bson", configuration = "default")) + api(project(path = ":driver-sync", 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-sync/src/integration/kotlin/com/mongodb/kotlin/client/CrudTest.kt b/driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/CrudTest.kt new file mode 100644 index 00000000000..f7a9c88c89a --- /dev/null +++ b/driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/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 + +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.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-sync/src/integration/kotlin/com/mongodb/kotlin/client/UnifiedCrudTest.kt b/driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/UnifiedCrudTest.kt new file mode 100644 index 00000000000..4818497a1c5 --- /dev/null +++ b/driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/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 + +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-sync/src/integration/kotlin/com/mongodb/kotlin/client/UnifiedTest.kt b/driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/UnifiedTest.kt new file mode 100644 index 00000000000..99a56849d7c --- /dev/null +++ b/driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/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 + +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.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-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncAggregateIterable.kt b/driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncAggregateIterable.kt new file mode 100644 index 00000000000..2640e6250d7 --- /dev/null +++ b/driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncAggregateIterable.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.syncadapter + +import com.mongodb.ExplainVerbosity +import com.mongodb.client.AggregateIterable as JAggregateIterable +import com.mongodb.client.model.Collation +import com.mongodb.kotlin.client.AggregateIterable +import java.util.concurrent.TimeUnit +import org.bson.BsonValue +import org.bson.Document +import org.bson.conversions.Bson + +internal class SyncAggregateIterable(val wrapped: AggregateIterable) : + JAggregateIterable, SyncMongoIterable(wrapped) { + override fun batchSize(batchSize: Int): SyncAggregateIterable = apply { wrapped.batchSize(batchSize) } + + override fun toCollection() = 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 = wrapped.explain() + + override fun explain(verbosity: ExplainVerbosity): Document = wrapped.explain(verbosity) + + override fun explain(explainResultClass: Class): E = wrapped.explain(explainResultClass) + + override fun explain(explainResultClass: Class, verbosity: ExplainVerbosity): E = + wrapped.explain(explainResultClass, verbosity) +} diff --git a/driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncChangeStreamIterable.kt b/driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncChangeStreamIterable.kt new file mode 100644 index 00000000000..0d579e68006 --- /dev/null +++ b/driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncChangeStreamIterable.kt @@ -0,0 +1,61 @@ +/* + * 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.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.ChangeStreamIterable +import java.util.concurrent.TimeUnit +import org.bson.BsonDocument +import org.bson.BsonTimestamp +import org.bson.BsonValue + +internal class SyncChangeStreamIterable(val wrapped: ChangeStreamIterable) : + JChangeStreamIterable, SyncMongoIterable>(wrapped) { + override fun withDocumentClass(clazz: Class): MongoIterable = + 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.cursor()) + 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-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncClientSession.kt b/driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncClientSession.kt new file mode 100644 index 00000000000..53d791bd423 --- /dev/null +++ b/driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncClientSession.kt @@ -0,0 +1,93 @@ +/* + * 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.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.ClientSession +import com.mongodb.session.ServerSession +import org.bson.BsonDocument +import org.bson.BsonTimestamp + +internal class SyncClientSession(internal val wrapped: ClientSession, private val originator: Any) : JClientSession { + private val delegate: JClientSession = wrapped.wrapped + + override fun close(): Unit = delegate.close() + + override fun getPinnedServerAddress(): ServerAddress? = delegate.pinnedServerAddress + + override fun getTransactionContext(): Any? = delegate.transactionContext + + override fun setTransactionContext(address: ServerAddress, transactionContext: Any): Unit = + delegate.setTransactionContext(address, transactionContext) + + override fun clearTransactionContext(): Unit = delegate.clearTransactionContext() + + override fun getRecoveryToken(): BsonDocument? = delegate.recoveryToken + + override fun setRecoveryToken(recoveryToken: BsonDocument): Unit { + delegate.recoveryToken = recoveryToken + } + + override fun getOptions(): ClientSessionOptions = delegate.options + + override fun isCausallyConsistent(): Boolean = delegate.isCausallyConsistent + + override fun getOriginator(): Any = originator + + override fun getServerSession(): ServerSession = delegate.serverSession + + override fun getOperationTime(): BsonTimestamp = delegate.operationTime + + override fun advanceOperationTime(operationTime: BsonTimestamp?): Unit = + delegate.advanceOperationTime(operationTime) + + override fun advanceClusterTime(clusterTime: BsonDocument?): Unit = delegate.advanceClusterTime(clusterTime) + + override fun setSnapshotTimestamp(snapshotTimestamp: BsonTimestamp?) { + delegate.snapshotTimestamp = snapshotTimestamp + } + + override fun getSnapshotTimestamp(): BsonTimestamp? = delegate.snapshotTimestamp + + override fun getClusterTime(): BsonDocument = delegate.clusterTime + + override fun hasActiveTransaction(): Boolean = delegate.hasActiveTransaction() + + override fun notifyMessageSent(): Boolean = delegate.notifyMessageSent() + + override fun notifyOperationInitiated(operation: Any): Unit = delegate.notifyOperationInitiated(operation) + + override fun getTransactionOptions(): TransactionOptions = delegate.transactionOptions + + override fun startTransaction(): Unit = delegate.startTransaction() + + override fun startTransaction(transactionOptions: TransactionOptions): Unit = + delegate.startTransaction(transactionOptions) + + override fun commitTransaction(): Unit = delegate.commitTransaction() + + override fun abortTransaction(): Unit = delegate.abortTransaction() + + override fun withTransaction(transactionBody: TransactionBody): T = + throw UnsupportedOperationException() + + override fun withTransaction(transactionBody: TransactionBody, options: TransactionOptions): T = + throw UnsupportedOperationException() +} diff --git a/driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncDistinctIterable.kt b/driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncDistinctIterable.kt new file mode 100644 index 00000000000..ef580954e20 --- /dev/null +++ b/driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/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.syncadapter + +import com.mongodb.client.DistinctIterable as JDistinctIterable +import com.mongodb.client.model.Collation +import com.mongodb.kotlin.client.DistinctIterable +import java.util.concurrent.TimeUnit +import org.bson.BsonValue +import org.bson.conversions.Bson + +internal class SyncDistinctIterable(val wrapped: DistinctIterable) : + 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-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncFindIterable.kt b/driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncFindIterable.kt new file mode 100644 index 00000000000..c52866f1243 --- /dev/null +++ b/driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncFindIterable.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.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.FindIterable +import java.util.concurrent.TimeUnit +import org.bson.BsonValue +import org.bson.Document +import org.bson.conversions.Bson + +@Suppress("DEPRECATION") +internal class SyncFindIterable(val wrapped: FindIterable) : + 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 = wrapped.explain() + + override fun explain(verbosity: ExplainVerbosity): Document = wrapped.explain(verbosity) + + override fun explain(explainResultClass: Class): E = wrapped.explain(explainResultClass) + + override fun explain(explainResultClass: Class, verbosity: ExplainVerbosity): E = + wrapped.explain(explainResultClass, verbosity) +} diff --git a/driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncListCollectionsIterable.kt b/driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncListCollectionsIterable.kt new file mode 100644 index 00000000000..74579b15a20 --- /dev/null +++ b/driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/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.syncadapter + +import com.mongodb.client.ListCollectionsIterable as JListCollectionsIterable +import com.mongodb.kotlin.client.ListCollectionsIterable +import java.util.concurrent.TimeUnit +import org.bson.BsonValue +import org.bson.conversions.Bson + +internal class SyncListCollectionsIterable(val wrapped: ListCollectionsIterable) : + 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-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncListDatabasesIterable.kt b/driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncListDatabasesIterable.kt new file mode 100644 index 00000000000..2e0e662a65d --- /dev/null +++ b/driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/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.syncadapter + +import com.mongodb.client.ListDatabasesIterable as JListDatabasesIterable +import com.mongodb.kotlin.client.ListDatabasesIterable +import java.util.concurrent.TimeUnit +import org.bson.BsonValue +import org.bson.conversions.Bson + +internal class SyncListDatabasesIterable(val wrapped: ListDatabasesIterable) : + 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-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncListIndexesIterable.kt b/driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncListIndexesIterable.kt new file mode 100644 index 00000000000..b9133970cb3 --- /dev/null +++ b/driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/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.syncadapter + +import com.mongodb.client.ListIndexesIterable as JListIndexesIterable +import com.mongodb.kotlin.client.ListIndexesIterable +import java.util.concurrent.TimeUnit +import org.bson.BsonValue + +internal class SyncListIndexesIterable(val wrapped: ListIndexesIterable) : + 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-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncMongoChangeStreamCursor.kt b/driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncMongoChangeStreamCursor.kt new file mode 100644 index 00000000000..1ddfe72a866 --- /dev/null +++ b/driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/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.syncadapter + +import com.mongodb.client.MongoChangeStreamCursor as JMongoChangeStreamCursor +import com.mongodb.kotlin.client.MongoChangeStreamCursor +import org.bson.BsonDocument + +internal class SyncMongoChangeStreamCursor(val wrapped: MongoChangeStreamCursor) : + JMongoChangeStreamCursor, SyncMongoCursor(wrapped) { + override fun getResumeToken(): BsonDocument? = wrapped.resumeToken +} diff --git a/driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncMongoClient.kt b/driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncMongoClient.kt new file mode 100644 index 00000000000..9c3af8af290 --- /dev/null +++ b/driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncMongoClient.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.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.MongoClient +import org.bson.Document +import org.bson.conversions.Bson + +internal 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(wrapped.startSession(), this) + + override fun startSession(options: ClientSessionOptions): ClientSession = + SyncClientSession(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.clusterDescription + + private fun ClientSession.unwrapped() = (this as SyncClientSession).wrapped +} diff --git a/driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncMongoCollection.kt b/driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncMongoCollection.kt new file mode 100644 index 00000000000..23a18872afc --- /dev/null +++ b/driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncMongoCollection.kt @@ -0,0 +1,505 @@ +/* + * 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.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.MongoCollection +import java.lang.UnsupportedOperationException +import org.bson.Document +import org.bson.codecs.configuration.CodecRegistry +import org.bson.conversions.Bson + +@Suppress("OVERRIDE_DEPRECATION") +internal 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 = wrapped.countDocuments() + + override fun countDocuments(filter: Bson): Long = wrapped.countDocuments(filter) + + override fun countDocuments(filter: Bson, options: CountOptions): Long = wrapped.countDocuments(filter, options) + + override fun countDocuments(clientSession: ClientSession): Long = wrapped.countDocuments(clientSession.unwrapped()) + + override fun countDocuments(clientSession: ClientSession, filter: Bson): Long = + wrapped.countDocuments(clientSession.unwrapped(), filter) + + override fun countDocuments(clientSession: ClientSession, filter: Bson, options: CountOptions): Long = + wrapped.countDocuments(clientSession.unwrapped(), filter, options) + + override fun estimatedDocumentCount(): Long = wrapped.estimatedDocumentCount() + + override fun estimatedDocumentCount(options: EstimatedDocumentCountOptions): Long = + 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 = + throw UnsupportedOperationException("No MapReduce support") + + override fun mapReduce( + mapFunction: String, + reduceFunction: String, + resultClass: Class + ): MapReduceIterable = throw UnsupportedOperationException("No MapReduce support") + + override fun mapReduce( + clientSession: ClientSession, + mapFunction: String, + reduceFunction: String + ): MapReduceIterable = throw UnsupportedOperationException("No MapReduce support") + + override fun mapReduce( + clientSession: ClientSession, + mapFunction: String, + reduceFunction: String, + resultClass: Class + ): MapReduceIterable = throw UnsupportedOperationException("No MapReduce support") + + override fun deleteOne(filter: Bson): DeleteResult = wrapped.deleteOne(filter) + + override fun deleteOne(filter: Bson, options: DeleteOptions): DeleteResult = wrapped.deleteOne(filter, options) + + override fun deleteOne(clientSession: ClientSession, filter: Bson): DeleteResult = + wrapped.deleteOne(clientSession.unwrapped(), filter) + + override fun deleteOne(clientSession: ClientSession, filter: Bson, options: DeleteOptions): DeleteResult = + wrapped.deleteOne(clientSession.unwrapped(), filter, options) + + override fun deleteMany(filter: Bson): DeleteResult = wrapped.deleteMany(filter) + + override fun deleteMany(filter: Bson, options: DeleteOptions): DeleteResult = wrapped.deleteMany(filter, options) + + override fun deleteMany(clientSession: ClientSession, filter: Bson): DeleteResult = + wrapped.deleteMany(clientSession.unwrapped(), filter) + + override fun deleteMany(clientSession: ClientSession, filter: Bson, options: DeleteOptions): DeleteResult = + wrapped.deleteMany(clientSession.unwrapped(), filter, options) + + override fun updateOne(filter: Bson, update: Bson): UpdateResult = wrapped.updateOne(filter, update) + + override fun updateOne(filter: Bson, update: Bson, updateOptions: UpdateOptions): UpdateResult = + wrapped.updateOne(filter, update, updateOptions) + + override fun updateOne(clientSession: ClientSession, filter: Bson, update: Bson): UpdateResult = + wrapped.updateOne(clientSession.unwrapped(), filter, update) + + override fun updateOne( + clientSession: ClientSession, + filter: Bson, + update: Bson, + updateOptions: UpdateOptions + ): UpdateResult = wrapped.updateOne(clientSession.unwrapped(), filter, update, updateOptions) + + override fun updateOne(filter: Bson, update: MutableList): UpdateResult = + wrapped.updateOne(filter, update) + + override fun updateOne(filter: Bson, update: MutableList, updateOptions: UpdateOptions): UpdateResult = + wrapped.updateOne(filter, update, updateOptions) + + override fun updateOne(clientSession: ClientSession, filter: Bson, update: MutableList): UpdateResult = + wrapped.updateOne(clientSession.unwrapped(), filter, update) + + override fun updateOne( + clientSession: ClientSession, + filter: Bson, + update: MutableList, + updateOptions: UpdateOptions + ): UpdateResult = wrapped.updateOne(clientSession.unwrapped(), filter, update, updateOptions) + + override fun updateMany(filter: Bson, update: Bson): UpdateResult = wrapped.updateMany(filter, update) + + override fun updateMany(filter: Bson, update: Bson, updateOptions: UpdateOptions): UpdateResult = + wrapped.updateMany(filter, update, updateOptions) + + override fun updateMany(clientSession: ClientSession, filter: Bson, update: Bson): UpdateResult = + wrapped.updateMany(clientSession.unwrapped(), filter, update) + + override fun updateMany( + clientSession: ClientSession, + filter: Bson, + update: Bson, + updateOptions: UpdateOptions + ): UpdateResult = wrapped.updateMany(clientSession.unwrapped(), filter, update, updateOptions) + + override fun updateMany(filter: Bson, update: MutableList): UpdateResult = + wrapped.updateMany(filter, update) + + override fun updateMany(filter: Bson, update: MutableList, updateOptions: UpdateOptions): UpdateResult = + wrapped.updateMany(filter, update, updateOptions) + + override fun updateMany(clientSession: ClientSession, filter: Bson, update: MutableList): UpdateResult = + wrapped.updateMany(clientSession.unwrapped(), filter, update) + + override fun updateMany( + clientSession: ClientSession, + filter: Bson, + update: MutableList, + updateOptions: UpdateOptions + ): UpdateResult = wrapped.updateMany(clientSession.unwrapped(), filter, update, updateOptions) + + override fun findOneAndDelete(filter: Bson): T? = wrapped.findOneAndDelete(filter) + + override fun findOneAndDelete(filter: Bson, options: FindOneAndDeleteOptions): T? = + wrapped.findOneAndDelete(filter, options) + + override fun findOneAndDelete(clientSession: ClientSession, filter: Bson): T? = + wrapped.findOneAndDelete(clientSession.unwrapped(), filter) + + override fun findOneAndDelete(clientSession: ClientSession, filter: Bson, options: FindOneAndDeleteOptions): T? = + wrapped.findOneAndDelete(clientSession.unwrapped(), filter, options) + + override fun findOneAndUpdate(filter: Bson, update: Bson): T? = wrapped.findOneAndUpdate(filter, update) + + override fun findOneAndUpdate(filter: Bson, update: Bson, options: FindOneAndUpdateOptions): T? = + wrapped.findOneAndUpdate(filter, update, options) + + override fun findOneAndUpdate(clientSession: ClientSession, filter: Bson, update: Bson): T? = + wrapped.findOneAndUpdate(clientSession.unwrapped(), filter, update) + + override fun findOneAndUpdate( + clientSession: ClientSession, + filter: Bson, + update: Bson, + options: FindOneAndUpdateOptions + ): T? = wrapped.findOneAndUpdate(clientSession.unwrapped(), filter, update, options) + + override fun findOneAndUpdate(filter: Bson, update: MutableList): T? = + wrapped.findOneAndUpdate(filter, update) + + override fun findOneAndUpdate(filter: Bson, update: MutableList, options: FindOneAndUpdateOptions): T? = + wrapped.findOneAndUpdate(filter, update, options) + + override fun findOneAndUpdate(clientSession: ClientSession, filter: Bson, update: MutableList): T? = + wrapped.findOneAndUpdate(clientSession.unwrapped(), filter, update) + + override fun findOneAndUpdate( + clientSession: ClientSession, + filter: Bson, + update: MutableList, + options: FindOneAndUpdateOptions + ): T? = wrapped.findOneAndUpdate(clientSession.unwrapped(), filter, update, options) + + override fun drop() = wrapped.drop() + + override fun drop(clientSession: ClientSession) = wrapped.drop(clientSession.unwrapped()) + + override fun drop(dropCollectionOptions: DropCollectionOptions) = wrapped.drop(dropCollectionOptions) + + override fun drop(clientSession: ClientSession, dropCollectionOptions: DropCollectionOptions) = + wrapped.drop(clientSession.unwrapped(), dropCollectionOptions) + + override fun createIndex(keys: Bson): String = wrapped.createIndex(keys) + + override fun createIndex(keys: Bson, indexOptions: IndexOptions): String = wrapped.createIndex(keys, indexOptions) + + override fun createIndex(clientSession: ClientSession, keys: Bson): String = + wrapped.createIndex(clientSession.unwrapped(), keys) + + override fun createIndex(clientSession: ClientSession, keys: Bson, indexOptions: IndexOptions): String = + wrapped.createIndex(clientSession.unwrapped(), keys, indexOptions) + + override fun createIndexes(indexes: MutableList): MutableList = + wrapped.createIndexes(indexes).toMutableList() + + override fun createIndexes( + indexes: MutableList, + createIndexOptions: CreateIndexOptions + ): MutableList = wrapped.createIndexes(indexes, createIndexOptions).toMutableList() + + override fun createIndexes(clientSession: ClientSession, indexes: MutableList): MutableList = + wrapped.createIndexes(clientSession.unwrapped(), indexes).toMutableList() + + override fun createIndexes( + clientSession: ClientSession, + indexes: MutableList, + createIndexOptions: CreateIndexOptions + ): MutableList = + wrapped.createIndexes(clientSession.unwrapped(), indexes, createIndexOptions).toMutableList() + + 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) = wrapped.dropIndex(indexName) + + override fun dropIndex(indexName: String, dropIndexOptions: DropIndexOptions) = + wrapped.dropIndex(indexName, dropIndexOptions) + + override fun dropIndex(keys: Bson) = wrapped.dropIndex(keys) + + override fun dropIndex(keys: Bson, dropIndexOptions: DropIndexOptions) = wrapped.dropIndex(keys, dropIndexOptions) + + override fun dropIndex(clientSession: ClientSession, indexName: String) = + wrapped.dropIndex(clientSession.unwrapped(), indexName) + + override fun dropIndex(clientSession: ClientSession, keys: Bson) = + wrapped.dropIndex(clientSession.unwrapped(), keys) + override fun dropIndex(clientSession: ClientSession, indexName: String, dropIndexOptions: DropIndexOptions) = + wrapped.dropIndex(clientSession.unwrapped(), indexName, dropIndexOptions) + + override fun dropIndex(clientSession: ClientSession, keys: Bson, dropIndexOptions: DropIndexOptions) = + wrapped.dropIndex(clientSession.unwrapped(), keys, dropIndexOptions) + + override fun dropIndexes() = wrapped.dropIndexes() + + override fun dropIndexes(clientSession: ClientSession) = wrapped.dropIndexes(clientSession.unwrapped()) + + override fun dropIndexes(dropIndexOptions: DropIndexOptions) = wrapped.dropIndexes(dropIndexOptions) + + override fun dropIndexes(clientSession: ClientSession, dropIndexOptions: DropIndexOptions) = + wrapped.dropIndexes(clientSession.unwrapped(), dropIndexOptions) + + override fun renameCollection(newCollectionNamespace: MongoNamespace) = + wrapped.renameCollection(newCollectionNamespace) + + override fun renameCollection( + newCollectionNamespace: MongoNamespace, + renameCollectionOptions: RenameCollectionOptions + ) = wrapped.renameCollection(newCollectionNamespace, renameCollectionOptions) + + override fun renameCollection(clientSession: ClientSession, newCollectionNamespace: MongoNamespace) = + wrapped.renameCollection(clientSession.unwrapped(), newCollectionNamespace) + + override fun renameCollection( + clientSession: ClientSession, + newCollectionNamespace: MongoNamespace, + renameCollectionOptions: RenameCollectionOptions + ) = wrapped.renameCollection(clientSession.unwrapped(), newCollectionNamespace, renameCollectionOptions) + override fun findOneAndReplace( + clientSession: ClientSession, + filter: Bson, + replacement: T, + options: FindOneAndReplaceOptions + ): T? = wrapped.findOneAndReplace(clientSession.unwrapped(), filter, replacement, options) + + override fun findOneAndReplace(clientSession: ClientSession, filter: Bson, replacement: T): T? = + wrapped.findOneAndReplace(clientSession.unwrapped(), filter, replacement) + + override fun findOneAndReplace(filter: Bson, replacement: T, options: FindOneAndReplaceOptions): T? = + wrapped.findOneAndReplace(filter, replacement, options) + + override fun findOneAndReplace(filter: Bson, replacement: T): T? = wrapped.findOneAndReplace(filter, replacement) + + override fun replaceOne( + clientSession: ClientSession, + filter: Bson, + replacement: T, + replaceOptions: ReplaceOptions + ): UpdateResult = wrapped.replaceOne(clientSession.unwrapped(), filter, replacement, replaceOptions) + + override fun replaceOne(clientSession: ClientSession, filter: Bson, replacement: T): UpdateResult = + wrapped.replaceOne(clientSession.unwrapped(), filter, replacement) + + override fun replaceOne(filter: Bson, replacement: T, replaceOptions: ReplaceOptions): UpdateResult = + wrapped.replaceOne(filter, replacement, replaceOptions) + + override fun replaceOne(filter: Bson, replacement: T): UpdateResult = wrapped.replaceOne(filter, replacement) + + override fun insertMany( + clientSession: ClientSession, + documents: MutableList, + options: InsertManyOptions + ): InsertManyResult = wrapped.insertMany(clientSession.unwrapped(), documents, options) + + override fun insertMany(clientSession: ClientSession, documents: MutableList): InsertManyResult = + wrapped.insertMany(clientSession.unwrapped(), documents) + + override fun insertMany(documents: MutableList, options: InsertManyOptions): InsertManyResult = + wrapped.insertMany(documents, options) + + override fun insertMany(documents: MutableList): InsertManyResult = wrapped.insertMany(documents) + + override fun insertOne(clientSession: ClientSession, document: T, options: InsertOneOptions): InsertOneResult = + wrapped.insertOne(clientSession.unwrapped(), document, options) + + override fun insertOne(clientSession: ClientSession, document: T): InsertOneResult = + wrapped.insertOne(clientSession.unwrapped(), document) + + override fun insertOne(document: T, options: InsertOneOptions): InsertOneResult = + wrapped.insertOne(document, options) + + override fun insertOne(document: T): InsertOneResult = wrapped.insertOne(document) + + override fun bulkWrite( + clientSession: ClientSession, + requests: MutableList>, + options: BulkWriteOptions + ): BulkWriteResult = wrapped.bulkWrite(clientSession.unwrapped(), requests, options) + + override fun bulkWrite( + clientSession: ClientSession, + requests: MutableList> + ): BulkWriteResult = wrapped.bulkWrite(clientSession.unwrapped(), requests) + + override fun bulkWrite(requests: MutableList>, options: BulkWriteOptions): BulkWriteResult = + wrapped.bulkWrite(requests, options) + + override fun bulkWrite(requests: MutableList>): BulkWriteResult = wrapped.bulkWrite(requests) + + private fun ClientSession.unwrapped() = (this as SyncClientSession).wrapped +} diff --git a/driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncMongoCursor.kt b/driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncMongoCursor.kt new file mode 100644 index 00000000000..d115e979521 --- /dev/null +++ b/driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncMongoCursor.kt @@ -0,0 +1,40 @@ +/* + * 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.syncadapter + +import com.mongodb.ServerAddress +import com.mongodb.ServerCursor +import com.mongodb.client.MongoCursor as JMongoCursor +import com.mongodb.kotlin.client.MongoCursor + +internal open class SyncMongoCursor(private val delegate: MongoCursor) : JMongoCursor { + override fun remove() { + TODO("Not yet implemented") + } + + override fun hasNext(): Boolean = delegate.hasNext() + override fun next(): T = delegate.next() + + override fun close() = delegate.close() + + override fun available(): Int = delegate.available + + override fun tryNext(): T? = delegate.tryNext() + + override fun getServerCursor(): ServerCursor? = delegate.serverCursor + + override fun getServerAddress(): ServerAddress = delegate.serverAddress +} diff --git a/driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncMongoDatabase.kt b/driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncMongoDatabase.kt new file mode 100644 index 00000000000..20b0051488f --- /dev/null +++ b/driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncMongoDatabase.kt @@ -0,0 +1,197 @@ +/* + * 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.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.MongoDatabase +import org.bson.Document +import org.bson.codecs.configuration.CodecRegistry +import org.bson.conversions.Bson + +internal 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 = wrapped.runCommand(command) + + override fun runCommand(command: Bson, readPreference: ReadPreference): Document = + wrapped.runCommand(command, readPreference) + + override fun runCommand(command: Bson, resultClass: Class): T = + wrapped.runCommand(command, resultClass = resultClass) + + override fun runCommand(command: Bson, readPreference: ReadPreference, resultClass: Class): T = + wrapped.runCommand(command, readPreference, resultClass) + + override fun runCommand(clientSession: ClientSession, command: Bson): Document = + wrapped.runCommand(clientSession.unwrapped(), command) + + override fun runCommand(clientSession: ClientSession, command: Bson, readPreference: ReadPreference): Document = + wrapped.runCommand(clientSession.unwrapped(), command, readPreference) + + override fun runCommand(clientSession: ClientSession, command: Bson, resultClass: Class): T = + wrapped.runCommand(clientSession.unwrapped(), command, resultClass = resultClass) + + override fun runCommand( + clientSession: ClientSession, + command: Bson, + readPreference: ReadPreference, + resultClass: Class + ): T = wrapped.runCommand(clientSession.unwrapped(), command, readPreference, resultClass) + + override fun drop() = wrapped.drop() + + override fun drop(clientSession: ClientSession) = 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) = wrapped.createCollection(collectionName) + + override fun createCollection(collectionName: String, createCollectionOptions: CreateCollectionOptions) = + wrapped.createCollection(collectionName, createCollectionOptions) + + override fun createCollection(clientSession: ClientSession, collectionName: String) = + wrapped.createCollection(clientSession.unwrapped(), collectionName) + + override fun createCollection( + clientSession: ClientSession, + collectionName: String, + createCollectionOptions: CreateCollectionOptions + ) = wrapped.createCollection(clientSession.unwrapped(), collectionName, createCollectionOptions) + + override fun createView(viewName: String, viewOn: String, pipeline: MutableList) = + wrapped.createView(viewName, viewOn, pipeline) + + override fun createView( + viewName: String, + viewOn: String, + pipeline: MutableList, + createViewOptions: CreateViewOptions + ) = wrapped.createView(viewName, viewOn, pipeline, createViewOptions) + + override fun createView( + clientSession: ClientSession, + viewName: String, + viewOn: String, + pipeline: MutableList + ) = wrapped.createView(clientSession.unwrapped(), viewName, viewOn, pipeline) + + override fun createView( + clientSession: ClientSession, + viewName: String, + viewOn: String, + pipeline: MutableList, + createViewOptions: CreateViewOptions + ) = 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-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncMongoIterable.kt b/driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncMongoIterable.kt new file mode 100644 index 00000000000..7a7f7af5221 --- /dev/null +++ b/driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncMongoIterable.kt @@ -0,0 +1,40 @@ +/* + * 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.syncadapter + +import com.mongodb.Function +import com.mongodb.client.MongoCursor +import com.mongodb.client.MongoIterable as JMongoIterable +import com.mongodb.kotlin.client.MongoIterable + +internal open class SyncMongoIterable(val delegate: MongoIterable) : JMongoIterable { + override fun iterator(): MongoCursor = cursor() + + override fun cursor(): MongoCursor = SyncMongoCursor(delegate.cursor()) + + override fun first(): T? = delegate.firstOrNull() + + override fun batchSize(batchSize: Int): SyncMongoIterable = apply { delegate.batchSize(batchSize) } + + @Suppress("UNCHECKED_CAST") + override fun ?> into(target: A): A & Any { + delegate.forEach { target?.add(it) } + return target as (A & Any) + } + + override fun map(mapper: Function): SyncMongoIterable = + SyncMongoIterable(delegate.map { mapper.apply(it) }) +} diff --git a/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/AggregateIterable.kt b/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/AggregateIterable.kt new file mode 100644 index 00000000000..4940cad99d0 --- /dev/null +++ b/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/AggregateIterable.kt @@ -0,0 +1,191 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mongodb.kotlin.client + +import com.mongodb.ExplainVerbosity +import com.mongodb.client.AggregateIterable as JAggregateIterable +import com.mongodb.client.model.Collation +import java.util.concurrent.TimeUnit +import org.bson.BsonValue +import org.bson.Document +import org.bson.conversions.Bson + +/** + * Iterable like 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 AggregateIterable(private val wrapped: JAggregateIterable) : MongoIterable(wrapped) { + /** + * 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 override fun batchSize(batchSize: Int): AggregateIterable = 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 fun toCollection(): Unit = wrapped.toCollection() + + /** + * 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?): AggregateIterable = 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): AggregateIterable = 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): AggregateIterable = + 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?): AggregateIterable = 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?): AggregateIterable = 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?): AggregateIterable = 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?): AggregateIterable = 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?): AggregateIterable = apply { wrapped.hint(hint) } + + /** + * Sets the hint to apply. + * + * Note: If [AggregateIterable.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?): AggregateIterable = 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?): AggregateIterable = apply { wrapped.let(variables) } + + /** + * Explain the execution plan for this operation with the given verbosity level + * + * @param verbosity the verbosity of the explanation + * @return the execution plan + * @see [Explain command](https://www.mongodb.com/docs/manual/reference/command/explain/) + */ + public 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 fun explain(resultClass: Class, verbosity: ExplainVerbosity? = null): R = + if (verbosity == null) wrapped.explain(resultClass) else wrapped.explain(resultClass, verbosity) + + /** + * 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 inline fun explain(verbosity: ExplainVerbosity? = null): R = + explain(R::class.java, verbosity) +} diff --git a/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/ChangeStreamIterable.kt b/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/ChangeStreamIterable.kt new file mode 100644 index 00000000000..95660682f0b --- /dev/null +++ b/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/ChangeStreamIterable.kt @@ -0,0 +1,185 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mongodb.kotlin.client + +import com.mongodb.client.ChangeStreamIterable as JChangeStreamIterable +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 java.util.concurrent.TimeUnit +import org.bson.BsonDocument +import org.bson.BsonTimestamp +import org.bson.BsonValue + +/** + * Iterable like 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 ChangeStreamIterable(private val wrapped: JChangeStreamIterable) : + MongoIterable>(wrapped) { + + /** + * Returns a cursor used for iterating over elements of type {@code ChangeStreamDocument}. The cursor has a + * covariant return type to additionally provide a method to access the resume token in change stream batches. + * + * @return the change stream cursor + */ + public override fun cursor(): MongoChangeStreamCursor> = + MongoChangeStreamCursorImpl(wrapped.cursor()) + + /** + * Sets the fullDocument value. + * + * @param fullDocument the fullDocument + * @return this + */ + public fun fullDocument(fullDocument: FullDocument): ChangeStreamIterable = apply { + wrapped.fullDocument(fullDocument) + } + + /** + * Sets the fullDocumentBeforeChange value. + * + * @param fullDocumentBeforeChange the fullDocumentBeforeChange + * @return this + */ + public fun fullDocumentBeforeChange(fullDocumentBeforeChange: FullDocumentBeforeChange): ChangeStreamIterable = + 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): ChangeStreamIterable = 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 override fun batchSize(batchSize: Int): ChangeStreamIterable = 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): ChangeStreamIterable = + 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?): ChangeStreamIterable = 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): MongoIterable = + MongoIterable(wrapped.withDocumentClass(resultClass)) + + /** + * 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(): MongoIterable = 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): ChangeStreamIterable = 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): ChangeStreamIterable = 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?): ChangeStreamIterable = 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?): ChangeStreamIterable = 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): ChangeStreamIterable = apply { + wrapped.showExpandedEvents(showExpandedEvents) + } +} diff --git a/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/ClientSession.kt b/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/ClientSession.kt new file mode 100644 index 00000000000..9103689b251 --- /dev/null +++ b/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/ClientSession.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 + +import com.mongodb.ClientSessionOptions +import com.mongodb.TransactionOptions +import com.mongodb.client.ClientSession as JClientSession +import java.io.Closeable +import java.util.concurrent.TimeUnit + +/** A client session that supports transactions. */ +public class ClientSession(public val wrapped: JClientSession) : Closeable { + + public override fun close(): Unit = wrapped.close() + + /** The options for this session. */ + public val options: ClientSessionOptions + get() = wrapped.options + + /** Returns true if operations in this session must be causally consistent */ + public val isCausallyConsistent: Boolean + get() = wrapped.isCausallyConsistent + + /** Returns true if there is an active transaction on this session, and false otherwise */ + public val hasActiveTransaction: Boolean + get() = wrapped.hasActiveTransaction() + + /** + * Gets the transaction options. + * + * Only call this method of the session has an active transaction + */ + public val transactionOptions: TransactionOptions + get() = 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. + */ + public fun commitTransaction(): Unit = wrapped.commitTransaction() + + /** + * Abort a transaction in the context of this session. + * + * A transaction can only be aborted if one has first been started. + */ + public fun abortTransaction(): Unit = wrapped.abortTransaction() + + /** + * Execute the given function within a transaction. + * + * @param T the return type of the transaction body + * @param transactionBody the body of the transaction + * @param options the transaction options + * @return the return value of the transaction body + */ + public fun withTransaction( + transactionBody: () -> T, + options: TransactionOptions = TransactionOptions.builder().build() + ): T = wrapped.withTransaction(transactionBody, options) +} + +/** + * 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-sync/src/main/kotlin/com/mongodb/kotlin/client/DistinctIterable.kt b/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/DistinctIterable.kt new file mode 100644 index 00000000000..b630af52517 --- /dev/null +++ b/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/DistinctIterable.kt @@ -0,0 +1,85 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mongodb.kotlin.client + +import com.mongodb.client.DistinctIterable as JDistinctIterable +import com.mongodb.client.model.Collation +import java.util.concurrent.TimeUnit +import org.bson.BsonValue +import org.bson.conversions.Bson + +/** + * Iterable like 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 DistinctIterable(private val wrapped: JDistinctIterable) : MongoIterable(wrapped) { + /** + * 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 override fun batchSize(batchSize: Int): DistinctIterable = 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?): DistinctIterable = 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): DistinctIterable = 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?): DistinctIterable = 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?): DistinctIterable = 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?): DistinctIterable = apply { wrapped.comment(comment) } +} diff --git a/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/FindIterable.kt b/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/FindIterable.kt new file mode 100644 index 00000000000..45b51a1e9c9 --- /dev/null +++ b/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/FindIterable.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 + +import com.mongodb.CursorType +import com.mongodb.ExplainVerbosity +import com.mongodb.client.FindIterable as JFindIterable +import com.mongodb.client.model.Collation +import java.util.concurrent.TimeUnit +import org.bson.BsonValue +import org.bson.Document +import org.bson.conversions.Bson + +/** + * Iterable like 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 FindIterable(private val wrapped: JFindIterable) : MongoIterable(wrapped) { + /** + * 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 override fun batchSize(batchSize: Int): FindIterable = 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?): FindIterable = 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): FindIterable = 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): FindIterable = 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): FindIterable = 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): FindIterable = 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?): FindIterable = 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?): FindIterable = 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): FindIterable = 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): FindIterable = 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): FindIterable = apply { wrapped.partial(partial) } + + /** + * Sets the cursor type. + * + * @param cursorType the cursor type + * @return this + */ + public fun cursorType(cursorType: CursorType): FindIterable = 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?): FindIterable = 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?): FindIterable = 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?): FindIterable = 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?): FindIterable = apply { wrapped.hint(hint) } + + /** + * Sets the hint to apply. + * + * Note: If [FindIterable.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?): FindIterable = 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?): FindIterable = 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?): FindIterable = 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?): FindIterable = 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): FindIterable = 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): FindIterable = 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?): FindIterable = apply { wrapped.allowDiskUse(allowDiskUse) } + + /** + * Explain the execution plan for this operation with the given verbosity level + * + * @param verbosity the verbosity of the explanation + * @return the execution plan + * @see [Explain command](https://www.mongodb.com/docs/manual/reference/command/explain/) + */ + public 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 fun explain(resultClass: Class, verbosity: ExplainVerbosity? = null): R = + if (verbosity == null) wrapped.explain(resultClass) else wrapped.explain(resultClass, verbosity) + + /** + * 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 inline fun explain(verbosity: ExplainVerbosity? = null): R = + explain(R::class.java, verbosity) +} diff --git a/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/ListCollectionsIterable.kt b/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/ListCollectionsIterable.kt new file mode 100644 index 00000000000..6ff8bc9c3fa --- /dev/null +++ b/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/ListCollectionsIterable.kt @@ -0,0 +1,75 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mongodb.kotlin.client + +import com.mongodb.client.ListCollectionsIterable as JListCollectionsIterable +import java.util.concurrent.TimeUnit +import org.bson.BsonValue +import org.bson.conversions.Bson + +/** + * Iterable like 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 ListCollectionsIterable(private val wrapped: JListCollectionsIterable) : + MongoIterable(wrapped) { + /** + * 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): ListCollectionsIterable = 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 override fun batchSize(batchSize: Int): ListCollectionsIterable = 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?): ListCollectionsIterable = 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?): ListCollectionsIterable = 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?): ListCollectionsIterable = apply { wrapped.comment(comment) } +} diff --git a/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/ListDatabasesIterable.kt b/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/ListDatabasesIterable.kt new file mode 100644 index 00000000000..560920b5e0d --- /dev/null +++ b/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/ListDatabasesIterable.kt @@ -0,0 +1,94 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mongodb.kotlin.client + +import com.mongodb.client.ListDatabasesIterable as JListDatabasesIterable +import java.util.concurrent.TimeUnit +import org.bson.BsonValue +import org.bson.conversions.Bson + +/** + * Iterable like 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 ListDatabasesIterable(private val wrapped: JListDatabasesIterable) : + MongoIterable(wrapped) { + /** + * 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): ListDatabasesIterable = 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 override fun batchSize(batchSize: Int): ListDatabasesIterable = 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?): ListDatabasesIterable = 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?): ListDatabasesIterable = 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?): ListDatabasesIterable = 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?): ListDatabasesIterable = 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?): ListDatabasesIterable = apply { wrapped.comment(comment) } +} diff --git a/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/ListIndexesIterable.kt b/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/ListIndexesIterable.kt new file mode 100644 index 00000000000..36847cb49d8 --- /dev/null +++ b/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/ListIndexesIterable.kt @@ -0,0 +1,65 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mongodb.kotlin.client + +import com.mongodb.client.ListIndexesIterable as JListIndexesIterable +import java.util.concurrent.TimeUnit +import org.bson.BsonValue + +/** + * Iterable like 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 ListIndexesIterable(private val wrapped: JListIndexesIterable) : MongoIterable(wrapped) { + /** + * 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): ListIndexesIterable = 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 override fun batchSize(batchSize: Int): ListIndexesIterable = 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?): ListIndexesIterable = 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?): ListIndexesIterable = apply { wrapped.comment(comment) } +} diff --git a/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/MongoClient.kt b/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/MongoClient.kt new file mode 100644 index 00000000000..4cae28c973f --- /dev/null +++ b/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/MongoClient.kt @@ -0,0 +1,282 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mongodb.kotlin.client + +import com.mongodb.ClientSessionOptions +import com.mongodb.ConnectionString +import com.mongodb.MongoClientSettings +import com.mongodb.MongoDriverInformation +import com.mongodb.client.MongoClient as JMongoClient +import com.mongodb.client.MongoClients as JMongoClients +import com.mongodb.connection.ClusterDescription +import java.io.Closeable +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 + */ + 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"), + 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, + 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 val clusterDescription: ClusterDescription + get() = wrapped.clusterDescription + + /** + * Gets a [MongoDatabase] instance for the given database name. + * + * @param databaseName the name of the database to retrieve + * @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 fun startSession(options: ClientSessionOptions = ClientSessionOptions.builder().build()): ClientSession = + ClientSession(wrapped.startSession(options)) + + /** + * 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(): MongoIterable = MongoIterable(wrapped.listDatabaseNames()) + + /** + * 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): MongoIterable = + MongoIterable(wrapped.listDatabaseNames(clientSession.wrapped)) + + /** + * Gets the list of databases + * + * @return the list databases iterable interface + */ + @JvmName("listDatabasesAsDocument") + public fun listDatabases(): ListDatabasesIterable = 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): ListDatabasesIterable = + 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): ListDatabasesIterable = + ListDatabasesIterable(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): ListDatabasesIterable = + ListDatabasesIterable(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(): ListDatabasesIterable = 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): ListDatabasesIterable = + 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()): ChangeStreamIterable = 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()): ChangeStreamIterable = + 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): ChangeStreamIterable = + ChangeStreamIterable(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 + ): ChangeStreamIterable = ChangeStreamIterable(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()): ChangeStreamIterable = + 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() + ): ChangeStreamIterable = watch(clientSession, pipeline, T::class.java) +} diff --git a/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/MongoCollection.kt b/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/MongoCollection.kt new file mode 100644 index 00000000000..c2cf060b754 --- /dev/null +++ b/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/MongoCollection.kt @@ -0,0 +1,1381 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mongodb.kotlin.client + +import com.mongodb.MongoNamespace +import com.mongodb.ReadConcern +import com.mongodb.ReadPreference +import com.mongodb.WriteConcern +import com.mongodb.bulk.BulkWriteResult +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 java.util.concurrent.TimeUnit +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 of documents the collection will encode documents from and decode documents to. + * @property wrapped the underlying sync MongoCollection + */ +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 fun countDocuments(filter: Bson = BsonDocument(), options: CountOptions = CountOptions()): Long = + wrapped.countDocuments(filter, options) + + /** + * 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 fun countDocuments( + clientSession: ClientSession, + filter: Bson = BsonDocument(), + options: CountOptions = CountOptions() + ): Long = wrapped.countDocuments(clientSession.wrapped, filter, options) + + /** + * 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 fun estimatedDocumentCount(options: EstimatedDocumentCountOptions = EstimatedDocumentCountOptions()): Long = + wrapped.estimatedDocumentCount(options) + + /** + * 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 + ): DistinctIterable = DistinctIterable(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 + ): DistinctIterable = DistinctIterable(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() + ): DistinctIterable = 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() + ): DistinctIterable = 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()): FindIterable = 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()): FindIterable = + 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): FindIterable = + FindIterable(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 + ): FindIterable = FindIterable(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()): FindIterable = + 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() + ): FindIterable = 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): AggregateIterable = + AggregateIterable(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): AggregateIterable = + AggregateIterable(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): AggregateIterable = + AggregateIterable(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 + ): AggregateIterable = AggregateIterable(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): AggregateIterable = + 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 + ): AggregateIterable = 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()): ChangeStreamIterable = 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()): ChangeStreamIterable = + 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): ChangeStreamIterable = + ChangeStreamIterable(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 + ): ChangeStreamIterable = ChangeStreamIterable(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()): ChangeStreamIterable = + 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() + ): ChangeStreamIterable = watch(clientSession, pipeline, 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 fun insertOne(document: T, options: InsertOneOptions = InsertOneOptions()): InsertOneResult = + wrapped.insertOne(document, options) + + /** + * 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 fun insertOne( + clientSession: ClientSession, + document: T, + options: InsertOneOptions = InsertOneOptions() + ): InsertOneResult = wrapped.insertOne(clientSession.wrapped, document, options) + + /** + * 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 fun insertMany(documents: List, options: InsertManyOptions = InsertManyOptions()): InsertManyResult = + wrapped.insertMany(documents, options) + + /** + * 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 fun insertMany( + clientSession: ClientSession, + documents: List, + options: InsertManyOptions = InsertManyOptions() + ): InsertManyResult = wrapped.insertMany(clientSession.wrapped, documents, options) + + /** + * 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 fun updateOne(filter: Bson, update: Bson, options: UpdateOptions = UpdateOptions()): UpdateResult = + wrapped.updateOne(filter, update, options) + + /** + * 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 fun updateOne( + clientSession: ClientSession, + filter: Bson, + update: Bson, + options: UpdateOptions = UpdateOptions() + ): UpdateResult = wrapped.updateOne(clientSession.wrapped, filter, update, options) + + /** + * 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 fun updateOne(filter: Bson, update: List, options: UpdateOptions = UpdateOptions()): UpdateResult = + wrapped.updateOne(filter, update, options) + + /** + * 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 fun updateOne( + clientSession: ClientSession, + filter: Bson, + update: List, + options: UpdateOptions = UpdateOptions() + ): UpdateResult = wrapped.updateOne(clientSession.wrapped, filter, update, options) + + /** + * 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 fun updateMany(filter: Bson, update: Bson, options: UpdateOptions = UpdateOptions()): UpdateResult = + wrapped.updateMany(filter, update, options) + + /** + * 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 fun updateMany( + clientSession: ClientSession, + filter: Bson, + update: Bson, + options: UpdateOptions = UpdateOptions() + ): UpdateResult = wrapped.updateMany(clientSession.wrapped, filter, update, options) + + /** + * 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 fun updateMany(filter: Bson, update: List, options: UpdateOptions = UpdateOptions()): UpdateResult = + wrapped.updateMany(filter, update, options) + + /** + * 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 fun updateMany( + clientSession: ClientSession, + filter: Bson, + update: List, + options: UpdateOptions = UpdateOptions() + ): UpdateResult = wrapped.updateMany(clientSession.wrapped, filter, update, options) + + /** + * 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/) + */ + public fun replaceOne(filter: Bson, replacement: T, options: ReplaceOptions = ReplaceOptions()): UpdateResult = + wrapped.replaceOne(filter, replacement, options) + + /** + * 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/) + */ + public fun replaceOne( + clientSession: ClientSession, + filter: Bson, + replacement: T, + options: ReplaceOptions = ReplaceOptions() + ): UpdateResult = wrapped.replaceOne(clientSession.wrapped, filter, replacement, options) + + /** + * 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 fun deleteOne(filter: Bson, options: DeleteOptions = DeleteOptions()): DeleteResult = + wrapped.deleteOne(filter, options) + + /** + * 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 fun deleteOne( + clientSession: ClientSession, + filter: Bson, + options: DeleteOptions = DeleteOptions() + ): DeleteResult = wrapped.deleteOne(clientSession.wrapped, filter, options) + + /** + * 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 fun deleteMany(filter: Bson, options: DeleteOptions = DeleteOptions()): DeleteResult = + wrapped.deleteMany(filter, options) + + /** + * 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 fun deleteMany( + clientSession: ClientSession, + filter: Bson, + options: DeleteOptions = DeleteOptions() + ): DeleteResult = wrapped.deleteMany(clientSession.wrapped, filter, options) + + /** + * 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 fun bulkWrite( + requests: List>, + options: BulkWriteOptions = BulkWriteOptions() + ): BulkWriteResult = wrapped.bulkWrite(requests, options) + + /** + * 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 fun bulkWrite( + clientSession: ClientSession, + requests: List>, + options: BulkWriteOptions = BulkWriteOptions() + ): BulkWriteResult = wrapped.bulkWrite(clientSession.wrapped, requests, options) + + /** + * 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 fun findOneAndDelete(filter: Bson, options: FindOneAndDeleteOptions = FindOneAndDeleteOptions()): T? = + wrapped.findOneAndDelete(filter, options) + + /** + * 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 fun findOneAndDelete( + clientSession: ClientSession, + filter: Bson, + options: FindOneAndDeleteOptions = FindOneAndDeleteOptions() + ): T? = wrapped.findOneAndDelete(clientSession.wrapped, filter, options) + + /** + * 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 fun findOneAndUpdate( + filter: Bson, + update: Bson, + options: FindOneAndUpdateOptions = FindOneAndUpdateOptions() + ): T? = wrapped.findOneAndUpdate(filter, update, options) + + /** + * 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 fun findOneAndUpdate( + clientSession: ClientSession, + filter: Bson, + update: Bson, + options: FindOneAndUpdateOptions = FindOneAndUpdateOptions() + ): T? = wrapped.findOneAndUpdate(clientSession.wrapped, filter, update, options) + + /** + * 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 fun findOneAndUpdate( + filter: Bson, + update: List, + options: FindOneAndUpdateOptions = FindOneAndUpdateOptions() + ): T? = wrapped.findOneAndUpdate(filter, update, options) + + /** + * 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 fun findOneAndUpdate( + clientSession: ClientSession, + filter: Bson, + update: List, + options: FindOneAndUpdateOptions = FindOneAndUpdateOptions() + ): T? = wrapped.findOneAndUpdate(clientSession.wrapped, filter, update, options) + + /** + * 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 fun findOneAndReplace( + filter: Bson, + replacement: T, + options: FindOneAndReplaceOptions = FindOneAndReplaceOptions() + ): T? = wrapped.findOneAndReplace(filter, replacement, options) + + /** + * 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 fun findOneAndReplace( + clientSession: ClientSession, + filter: Bson, + replacement: T, + options: FindOneAndReplaceOptions = FindOneAndReplaceOptions() + ): T? = wrapped.findOneAndReplace(clientSession.wrapped, filter, replacement, options) + + /** + * 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 fun drop(options: DropCollectionOptions = DropCollectionOptions()): Unit = wrapped.drop(options) + + /** + * 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 fun drop(clientSession: ClientSession, options: DropCollectionOptions = DropCollectionOptions()): Unit = + wrapped.drop(clientSession.wrapped, options) + + /** + * 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 fun createIndex(keys: Bson, options: IndexOptions = IndexOptions()): String = + wrapped.createIndex(keys, options) + + /** + * 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 fun createIndex(clientSession: ClientSession, keys: Bson, options: IndexOptions = IndexOptions()): String = + wrapped.createIndex(clientSession.wrapped, keys, options) + + /** + * 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() + ): List = wrapped.createIndexes(indexes, options) + + /** + * 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() + ): List = wrapped.createIndexes(clientSession.wrapped, indexes, options) + + /** + * 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(): ListIndexesIterable = 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): ListIndexesIterable = + 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): ListIndexesIterable = + ListIndexesIterable(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): ListIndexesIterable = + ListIndexesIterable(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(): ListIndexesIterable = 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): ListIndexesIterable = + 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 fun dropIndex(indexName: String, options: DropIndexOptions = DropIndexOptions()): Unit = + wrapped.dropIndex(indexName, options) + + /** + * 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 fun dropIndex(keys: Bson, options: DropIndexOptions = DropIndexOptions()): Unit = + wrapped.dropIndex(keys, options) + + /** + * 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 fun dropIndex( + clientSession: ClientSession, + indexName: String, + options: DropIndexOptions = DropIndexOptions() + ): Unit = wrapped.dropIndex(clientSession.wrapped, indexName, options) + + /** + * 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 fun dropIndex( + clientSession: ClientSession, + keys: Bson, + options: DropIndexOptions = DropIndexOptions() + ): Unit = wrapped.dropIndex(clientSession.wrapped, keys, options) + + /** + * 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 fun dropIndexes(options: DropIndexOptions = DropIndexOptions()): Unit = wrapped.dropIndexes(options) + + /** + * 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 fun dropIndexes(clientSession: ClientSession, options: DropIndexOptions = DropIndexOptions()): Unit = + wrapped.dropIndexes(clientSession.wrapped, options) + + /** + * 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 fun renameCollection( + newCollectionNamespace: MongoNamespace, + options: RenameCollectionOptions = RenameCollectionOptions() + ): Unit = wrapped.renameCollection(newCollectionNamespace, options) + + /** + * 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/) + */ + public fun renameCollection( + clientSession: ClientSession, + newCollectionNamespace: MongoNamespace, + options: RenameCollectionOptions = RenameCollectionOptions() + ): Unit = wrapped.renameCollection(clientSession.wrapped, newCollectionNamespace, options) +} + +/** + * 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-sync/src/main/kotlin/com/mongodb/kotlin/client/MongoCursor.kt b/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/MongoCursor.kt new file mode 100644 index 00000000000..5c757bf5e65 --- /dev/null +++ b/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/MongoCursor.kt @@ -0,0 +1,135 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mongodb.kotlin.client + +import com.mongodb.ServerAddress +import com.mongodb.ServerCursor +import com.mongodb.client.MongoChangeStreamCursor as JMongoChangeStreamCursor +import com.mongodb.client.MongoCursor as JMongoCursor +import java.io.Closeable +import org.bson.BsonDocument + +/** + * The Mongo Cursor interface implementing the iterator protocol. + * + * An application should ensure that a cursor is closed in all circumstances, e.g. using a `use` statement: + * ``` + * collection.find().cursor().use { c -> + * while (c.hasNext()) { + * println(c.next()) + * } + * } + * ``` + * + * @param T The type of documents the cursor contains + */ +public sealed interface MongoCursor : Iterator, Closeable { + + /** + * Gets the number of results available locally without blocking, which may be 0. + * + * If the cursor is known to be exhausted, returns 0. If the cursor is closed before it's been exhausted, it may + * return a non-zero value. + */ + public val available: Int + + /** + * A special [next] case that returns the next element in the iteration if available or null. + * + * Tailable cursors are an example where this is useful. A call to [tryNext] may return null, ut in the future + * calling [tryNext] would return a new element if a document had been added to the capped collection.

+ * + * @return the next element in the iteration if available or null. + * @see [Tailable Cursor](https://www.mongodb.com/docs/manual/reference/glossary/#term-tailable-cursor) + */ + public fun tryNext(): T? + + /** @return the ServerCursor if available */ + public val serverCursor: ServerCursor? + + /** @return the ServerAddress */ + public val serverAddress: ServerAddress +} + +/** + * The Mongo Cursor interface for change streams implementing the iterator protocol. + * + * An application should ensure that a cursor is closed in all circumstances, e.g. using a `use` statement: + * ``` + * collection.watch().cursor().use { c -> + * while (c.hasNext()) { + * println(c.next()) + * } + * } + * ``` + * + * @param T The type of documents the cursor contains + */ +public sealed interface MongoChangeStreamCursor : MongoCursor { + /** + * Returns the resume token. If a batch has been iterated to the last change stream document in the batch and a + * postBatchResumeToken is included in the document, the postBatchResumeToken will be returned. Otherwise, the + * resume token contained in the last change stream document will be returned. + * + * @return the resume token, which can be null if the cursor has either not been iterated yet, or the cursor is + * closed. + */ + public val resumeToken: BsonDocument? +} + +internal class MongoCursorImpl(private val wrapped: JMongoCursor) : MongoCursor { + + override fun hasNext(): Boolean = wrapped.hasNext() + + override fun next(): T = wrapped.next() + + override fun close(): Unit = wrapped.close() + + override val available: Int + get() = wrapped.available() + + override fun tryNext(): T? = wrapped.tryNext() + + override val serverCursor: ServerCursor? + get() = wrapped.serverCursor + + override val serverAddress: ServerAddress + get() = wrapped.serverAddress +} + +internal class MongoChangeStreamCursorImpl(private val wrapped: JMongoChangeStreamCursor) : + MongoChangeStreamCursor { + + override fun hasNext(): Boolean = wrapped.hasNext() + + override fun next(): T = wrapped.next() + + override fun close(): Unit = wrapped.close() + + override val available: Int + get() = wrapped.available() + + override fun tryNext(): T? = wrapped.tryNext() + + override val serverCursor: ServerCursor? + get() = wrapped.serverCursor + + override val serverAddress: ServerAddress + get() = wrapped.serverAddress + + override val resumeToken: BsonDocument? + get() = wrapped.resumeToken +} diff --git a/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/MongoDatabase.kt b/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/MongoDatabase.kt new file mode 100644 index 00000000000..6ddfbd2c652 --- /dev/null +++ b/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/MongoDatabase.kt @@ -0,0 +1,532 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mongodb.kotlin.client + +import com.mongodb.ReadConcern +import com.mongodb.ReadPreference +import com.mongodb.WriteConcern +import com.mongodb.client.MongoDatabase as JMongoDatabase +import com.mongodb.client.model.CreateCollectionOptions +import com.mongodb.client.model.CreateViewOptions +import java.util.concurrent.TimeUnit +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 + */ + public 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 + */ + public 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 fun runCommand( + command: Bson, + readPreference: ReadPreference = this.readPreference, + resultClass: Class + ): T = wrapped.runCommand(command, readPreference, resultClass) + + /** + * 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 fun runCommand( + clientSession: ClientSession, + command: Bson, + readPreference: ReadPreference = this.readPreference, + resultClass: Class + ): T = wrapped.runCommand(clientSession.wrapped, command, readPreference, resultClass) + + /** + * 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 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 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 fun drop(): Unit = wrapped.drop() + + /** + * 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 fun drop(clientSession: ClientSession): Unit = wrapped.drop(clientSession.wrapped) + + /** + * 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(): MongoIterable = MongoIterable(wrapped.listCollectionNames()) + + /** + * 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): MongoIterable = + MongoIterable(wrapped.listCollectionNames(clientSession.wrapped)) + + /** + * 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(): ListCollectionsIterable = 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): ListCollectionsIterable = + 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): ListCollectionsIterable = + ListCollectionsIterable(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 + ): ListCollectionsIterable = ListCollectionsIterable(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(): ListCollectionsIterable = 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): ListCollectionsIterable = + 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 fun createCollection( + collectionName: String, + createCollectionOptions: CreateCollectionOptions = CreateCollectionOptions() + ): Unit = wrapped.createCollection(collectionName, createCollectionOptions) + + /** + * 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 fun createCollection( + clientSession: ClientSession, + collectionName: String, + createCollectionOptions: CreateCollectionOptions = CreateCollectionOptions() + ): Unit = wrapped.createCollection(clientSession.wrapped, collectionName, createCollectionOptions) + + /** + * 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 fun createView( + viewName: String, + viewOn: String, + pipeline: List, + createViewOptions: CreateViewOptions = CreateViewOptions() + ): Unit = wrapped.createView(viewName, viewOn, pipeline, createViewOptions) + + /** + * 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 fun createView( + clientSession: ClientSession, + viewName: String, + viewOn: String, + pipeline: List, + createViewOptions: CreateViewOptions = CreateViewOptions() + ): Unit = wrapped.createView(clientSession.wrapped, viewName, viewOn, pipeline, createViewOptions) + + /** + * 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): AggregateIterable = 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): AggregateIterable = + 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): AggregateIterable = + AggregateIterable(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 + ): AggregateIterable = AggregateIterable(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): AggregateIterable = + 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 + ): AggregateIterable = 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()): ChangeStreamIterable = 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()): ChangeStreamIterable = + 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): ChangeStreamIterable = + ChangeStreamIterable(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 + ): ChangeStreamIterable = ChangeStreamIterable(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()): ChangeStreamIterable = + 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() + ): ChangeStreamIterable = 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-sync/src/main/kotlin/com/mongodb/kotlin/client/MongoIterable.kt b/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/MongoIterable.kt new file mode 100644 index 00000000000..fffcea2ce76 --- /dev/null +++ b/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/MongoIterable.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 + +import com.mongodb.MongoClientException +import com.mongodb.client.MongoIterable as JMongoIterable + +/** + * The MongoIterable is the results from an operation, such as a query. + * + * @param T The type that this iterable will decode documents to. + */ +public open class MongoIterable(private val delegate: JMongoIterable) { + + /** + * Returns a cursor used for iterating over elements of type `T. The cursor is primarily used for change streams. + * + * Note: Care must be taken to ensure the returned [MongoCursor] is closed after use. + * + * @return a cursor + */ + public open fun cursor(): MongoCursor = MongoCursorImpl(delegate.cursor()) + + /** @return the first item or null */ + public fun firstOrNull(): T? = delegate.first() + + /** @return the first item or throw a [MongoClientException] if no results are available */ + public fun first(): T = firstOrNull() ?: throw MongoClientException("No results available") + + /** + * 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 open fun batchSize(batchSize: Int): MongoIterable = apply { delegate.batchSize(batchSize) } + + /** + * Creates a new cursor and treats it as a [Sequence], invokes the given [consumer] function and closes the cursor + * down correctly whether an exception is thrown or not. + * + * This allows the [MongoIterable] to be safely treated as a lazily evaluated sequence. + * + * Note: Sequence filters and aggregations have a performance cost, it is best to use server side filters and + * aggregations where available. + * + * @param R the result type + * @param consumer the sequence consumer + * @return the result of the consumer + */ + public fun use(consumer: (Sequence) -> R): R = cursor().use { consumer.invoke(it.asSequence()) } + + /** + * Maps this iterable from the source document type to the target document type. + * + * @param R the result document type + * @param transform a function that maps from the source to the target document type + * @return an iterable which maps T to U + */ + public fun map(transform: (T) -> R): MongoIterable = MongoIterable(delegate.map(transform)) + + /** Performs the given [action] on each element and safely closes the cursor. */ + public fun forEach(action: (T) -> Unit): Unit = use { it.forEach(action) } + + /** + * Appends all elements to the given [destination] collection. + * + * @param C the type of the collection + * @param destination the destination collection + * @return the collection + */ + public fun > toCollection(destination: C): C = use { it.toCollection(destination) } + + /** @return a [List] containing all elements. */ + public fun toList(): List = toCollection(ArrayList()).toList() +} diff --git a/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/AggregateIterableTest.kt b/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/AggregateIterableTest.kt new file mode 100644 index 00000000000..ce1ed2dea47 --- /dev/null +++ b/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/AggregateIterableTest.kt @@ -0,0 +1,105 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mongodb.kotlin.client + +import com.mongodb.ExplainVerbosity +import com.mongodb.client.AggregateIterable as JAggregateIterable +import com.mongodb.client.model.Collation +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.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 + +class AggregateIterableTest { + + @Test + fun shouldHaveTheSameMethods() { + val jAggregateIterableFunctions = JAggregateIterable::class.declaredFunctions.map { it.name }.toSet() + val kAggregateIterableFunctions = AggregateIterable::class.declaredFunctions.map { it.name }.toSet() + + assertEquals(jAggregateIterableFunctions, kAggregateIterableFunctions) + } + + @Test + fun shouldCallTheUnderlyingMethods() { + val wrapped: JAggregateIterable = mock() + val iterable = AggregateIterable(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 + + whenever(wrapped.explain(Document::class.java)).doReturn(mock()) + whenever(wrapped.explain(Document::class.java, verbosity)).doReturn(mock()) + whenever(wrapped.explain(BsonDocument::class.java, verbosity)).doReturn(mock()) + + iterable.allowDiskUse(true) + iterable.batchSize(batchSize) + iterable.bypassDocumentValidation(true) + iterable.collation(collation) + iterable.comment(bsonComment) + iterable.comment(comment) + iterable.explain() + iterable.explain(verbosity) + iterable.explain(Document::class.java) + iterable.explain(BsonDocument::class.java, verbosity) + iterable.explain() + iterable.explain(verbosity) + iterable.hint(hint) + iterable.hintString(hintString) + iterable.let(bson) + iterable.maxAwaitTime(1) + iterable.maxAwaitTime(1, TimeUnit.SECONDS) + iterable.maxTime(1) + iterable.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, 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).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) + + iterable.toCollection() + verify(wrapped).toCollection() + + verifyNoMoreInteractions(wrapped) + } +} diff --git a/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/ChangeStreamIterableTest.kt b/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/ChangeStreamIterableTest.kt new file mode 100644 index 00000000000..53d56485c21 --- /dev/null +++ b/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/ChangeStreamIterableTest.kt @@ -0,0 +1,93 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mongodb.kotlin.client + +import com.mongodb.client.ChangeStreamIterable as JChangeStreamIterable +import com.mongodb.client.model.Collation +import com.mongodb.client.model.changestream.FullDocument +import com.mongodb.client.model.changestream.FullDocumentBeforeChange +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.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 ChangeStreamIterableTest { + + @Test + fun shouldHaveTheSameMethods() { + val jChangeStreamIterableFunctions = JChangeStreamIterable::class.declaredFunctions.map { it.name }.toSet() + val kChangeStreamIterableFunctions = ChangeStreamIterable::class.declaredFunctions.map { it.name }.toSet() + + assertEquals(jChangeStreamIterableFunctions, kChangeStreamIterableFunctions) + } + + @Test + fun shouldCallTheUnderlyingMethods() { + val wrapped: JChangeStreamIterable = mock() + val iterable = ChangeStreamIterable(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() + + whenever(wrapped.withDocumentClass(BsonDocument::class.java)).doReturn(mock()) + whenever(wrapped.cursor()).doReturn(mock()) + + iterable.batchSize(batchSize) + iterable.collation(collation) + iterable.comment(comment) + iterable.comment(bsonComment) + iterable.cursor() + iterable.fullDocument(FullDocument.UPDATE_LOOKUP) + iterable.fullDocumentBeforeChange(FullDocumentBeforeChange.REQUIRED) + iterable.maxAwaitTime(1) + iterable.maxAwaitTime(1, TimeUnit.SECONDS) + iterable.resumeAfter(resumeToken) + iterable.showExpandedEvents(true) + iterable.startAfter(resumeToken) + iterable.startAtOperationTime(operationTime) + iterable.withDocumentClass() + + verify(wrapped).batchSize(batchSize) + verify(wrapped).collation(collation) + verify(wrapped).comment(comment) + verify(wrapped).comment(bsonComment) + verify(wrapped).cursor() + 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) + verify(wrapped).withDocumentClass(BsonDocument::class.java) + + verifyNoMoreInteractions(wrapped) + } +} diff --git a/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/ClientSessionTest.kt b/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/ClientSessionTest.kt new file mode 100644 index 00000000000..63309969104 --- /dev/null +++ b/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/ClientSessionTest.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 + +import com.mongodb.ClientSessionOptions +import com.mongodb.TransactionOptions +import com.mongodb.client.ClientSession as JClientSession +import kotlin.reflect.full.declaredMemberProperties +import kotlin.reflect.full.functions +import kotlin.test.assertEquals +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 ClientSessionTest { + + @Test + fun shouldHaveTheSameMethods() { + val internalFunctions = + setOf( + "advanceClusterTime", + "advanceOperationTime", + "clearTransactionContext", + "getClusterTime", + "getOperationTime", + "getOriginator", + "getPinnedServerAddress", + "getRecoveryToken", + "getServerSession", + "getSnapshotTimestamp", + "getTransactionContext", + "notifyMessageSent", + "notifyOperationInitiated", + "setRecoveryToken", + "setSnapshotTimestamp", + "setTransactionContext") + + val jClientSessionFunctions = JClientSession::class.functions.map { it.name }.toSet() - internalFunctions + val kClientSessionFunctions = + ClientSession::class.functions.map { it.name }.toSet() + + ClientSession::class + .declaredMemberProperties + .filterNot { it.name == "wrapped" } + .map { + if (it.name.startsWith("is") || it.name.startsWith("has")) it.name + else "get${it.name.replaceFirstChar { c -> c.uppercaseChar()}}" + } + + 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.isCausallyConsistent).doReturn(true) + whenever(wrapped.transactionOptions).doReturn(transactionOptions) + + session.options + session.isCausallyConsistent + session.startTransaction() + session.startTransaction(transactionOptions) + session.transactionOptions + + verify(wrapped).options + verify(wrapped).isCausallyConsistent + verify(wrapped).startTransaction() + verify(wrapped).startTransaction(transactionOptions) + verify(wrapped).transactionOptions + + session.abortTransaction() + session.commitTransaction() + + verify(wrapped).abortTransaction() + verify(wrapped).commitTransaction() + verifyNoMoreInteractions(wrapped) + } +} diff --git a/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/DistinctIterableTest.kt b/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/DistinctIterableTest.kt new file mode 100644 index 00000000000..c9fc79e8128 --- /dev/null +++ b/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/DistinctIterableTest.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 + +import com.mongodb.client.DistinctIterable as JDistinctIterable +import com.mongodb.client.model.Collation +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 DistinctIterableTest { + @Test + fun shouldHaveTheSameMethods() { + val jDistinctIterableFunctions = JDistinctIterable::class.declaredFunctions.map { it.name }.toSet() + val kDistinctIterableFunctions = DistinctIterable::class.declaredFunctions.map { it.name }.toSet() + + assertEquals(jDistinctIterableFunctions, kDistinctIterableFunctions) + } + + @Test + fun shouldCallTheUnderlyingMethods() { + val wrapped: JDistinctIterable = mock() + val iterable = DistinctIterable(wrapped) + + val batchSize = 10 + val bsonComment = BsonString("a comment") + val collation = Collation.builder().locale("en").build() + val comment = "comment" + val filter = BsonDocument() + + iterable.batchSize(batchSize) + iterable.collation(collation) + iterable.comment(bsonComment) + iterable.comment(comment) + iterable.filter(filter) + iterable.maxTime(1) + iterable.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-sync/src/test/kotlin/com/mongodb/kotlin/client/ExtensionMethodsTest.kt b/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/ExtensionMethodsTest.kt new file mode 100644 index 00000000000..ad8571e5696 --- /dev/null +++ b/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/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 + +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-sync/src/test/kotlin/com/mongodb/kotlin/client/FindIterableTest.kt b/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/FindIterableTest.kt new file mode 100644 index 00000000000..c84176a0cbf --- /dev/null +++ b/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/FindIterableTest.kt @@ -0,0 +1,128 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mongodb.kotlin.client + +import com.mongodb.CursorType +import com.mongodb.ExplainVerbosity +import com.mongodb.client.FindIterable as JFindIterable +import com.mongodb.client.model.Collation +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.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 + +class FindIterableTest { + @Test + fun shouldHaveTheSameMethods() { + val jFindIterableFunctions = JFindIterable::class.declaredFunctions.map { it.name }.toSet() + val kFindIterableFunctions = FindIterable::class.declaredFunctions.map { it.name }.toSet() + + assertEquals(jFindIterableFunctions, kFindIterableFunctions) + } + + @Suppress("DEPRECATION") + @Test + fun shouldCallTheUnderlyingMethods() { + val wrapped: JFindIterable = mock() + val iterable = FindIterable(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 + + whenever(wrapped.explain(Document::class.java)).doReturn(mock()) + whenever(wrapped.explain(Document::class.java, verbosity)).doReturn(mock()) + whenever(wrapped.explain(BsonDocument::class.java, verbosity)).doReturn(mock()) + + iterable.allowDiskUse(true) + iterable.batchSize(batchSize) + iterable.collation(collation) + iterable.comment(bsonComment) + iterable.comment(comment) + iterable.cursorType(CursorType.NonTailable) + iterable.explain() + iterable.explain(verbosity) + iterable.explain(Document::class.java) + iterable.explain(BsonDocument::class.java, verbosity) + iterable.explain() + iterable.explain(verbosity) + iterable.filter(filter) + iterable.hint(hint) + iterable.hintString(hintString) + iterable.let(bson) + iterable.limit(1) + iterable.max(bson) + iterable.maxAwaitTime(1) + iterable.maxAwaitTime(1, TimeUnit.SECONDS) + iterable.maxTime(1) + iterable.maxTime(1, TimeUnit.SECONDS) + iterable.min(bson) + iterable.oplogReplay(true) + iterable.noCursorTimeout(true) + iterable.partial(true) + iterable.projection(bson) + iterable.returnKey(true) + iterable.showRecordId(true) + iterable.skip(1) + iterable.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, 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).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) + + verifyNoMoreInteractions(wrapped) + } +} diff --git a/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/ListCollectionsIterableTest.kt b/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/ListCollectionsIterableTest.kt new file mode 100644 index 00000000000..b0c23b331e4 --- /dev/null +++ b/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/ListCollectionsIterableTest.kt @@ -0,0 +1,66 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mongodb.kotlin.client + +import com.mongodb.client.ListCollectionsIterable as JListCollectionsIterable +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 ListCollectionsIterableTest { + @Test + fun shouldHaveTheSameMethods() { + val jListCollectionsIterableFunctions = + JListCollectionsIterable::class.declaredFunctions.map { it.name }.toSet() + val kListCollectionsIterableFunctions = ListCollectionsIterable::class.declaredFunctions.map { it.name }.toSet() + + assertEquals(jListCollectionsIterableFunctions, kListCollectionsIterableFunctions) + } + + @Test + fun shouldCallTheUnderlyingMethods() { + val wrapped: JListCollectionsIterable = mock() + val iterable = ListCollectionsIterable(wrapped) + + val batchSize = 10 + val bsonComment = BsonString("a comment") + val comment = "comment" + val filter = BsonDocument() + + iterable.batchSize(batchSize) + iterable.comment(bsonComment) + iterable.comment(comment) + iterable.filter(filter) + iterable.maxTime(1) + iterable.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-sync/src/test/kotlin/com/mongodb/kotlin/client/ListDatabasesIterableTest.kt b/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/ListDatabasesIterableTest.kt new file mode 100644 index 00000000000..c10ef133c1d --- /dev/null +++ b/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/ListDatabasesIterableTest.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 + +import com.mongodb.client.ListDatabasesIterable as JListDatabasesIterable +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 ListDatabasesIterableTest { + @Test + fun shouldHaveTheSameMethods() { + val jListDatabasesIterableFunctions = JListDatabasesIterable::class.declaredFunctions.map { it.name }.toSet() + val kListDatabasesIterableFunctions = ListDatabasesIterable::class.declaredFunctions.map { it.name }.toSet() + + assertEquals(jListDatabasesIterableFunctions, kListDatabasesIterableFunctions) + } + + @Test + fun shouldCallTheUnderlyingMethods() { + val wrapped: JListDatabasesIterable = mock() + val iterable = ListDatabasesIterable(wrapped) + + val batchSize = 10 + val bsonComment = BsonString("a comment") + val comment = "comment" + val filter = BsonDocument() + + iterable.authorizedDatabasesOnly(true) + iterable.batchSize(batchSize) + iterable.comment(bsonComment) + iterable.comment(comment) + iterable.filter(filter) + iterable.maxTime(1) + iterable.maxTime(1, TimeUnit.SECONDS) + iterable.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-sync/src/test/kotlin/com/mongodb/kotlin/client/ListIndexesIterableTest.kt b/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/ListIndexesIterableTest.kt new file mode 100644 index 00000000000..70c799eeee4 --- /dev/null +++ b/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/ListIndexesIterableTest.kt @@ -0,0 +1,61 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mongodb.kotlin.client + +import com.mongodb.client.ListIndexesIterable as JListIndexesIterable +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 ListIndexesIterableTest { + @Test + fun shouldHaveTheSameMethods() { + val jListIndexesIterableFunctions = JListIndexesIterable::class.declaredFunctions.map { it.name }.toSet() + val kListIndexesIterableFunctions = ListIndexesIterable::class.declaredFunctions.map { it.name }.toSet() + + assertEquals(jListIndexesIterableFunctions, kListIndexesIterableFunctions) + } + + @Test + fun shouldCallTheUnderlyingMethods() { + val wrapped: JListIndexesIterable = mock() + val iterable = ListIndexesIterable(wrapped) + + val batchSize = 10 + val bsonComment = BsonString("a comment") + val comment = "comment" + + iterable.batchSize(batchSize) + iterable.comment(bsonComment) + iterable.comment(comment) + iterable.maxTime(1) + iterable.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-sync/src/test/kotlin/com/mongodb/kotlin/client/MockitoHelper.kt b/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/MockitoHelper.kt new file mode 100644 index 00000000000..838a26f5dff --- /dev/null +++ b/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/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 + +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-sync/src/test/kotlin/com/mongodb/kotlin/client/MongoChangeStreamCursorTest.kt b/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/MongoChangeStreamCursorTest.kt new file mode 100644 index 00000000000..7bd6008df7c --- /dev/null +++ b/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/MongoChangeStreamCursorTest.kt @@ -0,0 +1,72 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mongodb.kotlin.client + +import com.mongodb.ServerAddress +import com.mongodb.ServerCursor +import com.mongodb.client.MongoChangeStreamCursor as JMongoChangeStreamCursor +import kotlin.reflect.full.declaredFunctions +import kotlin.reflect.full.declaredMemberProperties +import kotlin.test.assertEquals +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 MongoChangeStreamCursorTest { + @Test + fun shouldHaveTheSameMethods() { + val jMongoChangeStreamCursorFunctions = + JMongoChangeStreamCursor::class.declaredFunctions.map { it.name }.toSet() + val kMongoChangeStreamCursorFunctions = + MongoChangeStreamCursor::class.declaredFunctions.map { it.name }.toSet() + + MongoChangeStreamCursor::class + .declaredMemberProperties + .filterNot { it.name == "wrapped" } + .map { "get${it.name.replaceFirstChar{c -> c.uppercaseChar() }}" } + + assertEquals(jMongoChangeStreamCursorFunctions, kMongoChangeStreamCursorFunctions) + } + + @Test + fun shouldCallTheUnderlyingMethods() { + val wrapped: JMongoChangeStreamCursor = mock() + val cursor = MongoChangeStreamCursorImpl(wrapped) + + whenever(wrapped.resumeToken).doReturn(mock()) + whenever(wrapped.serverCursor).doReturn(ServerCursor(1, ServerAddress())) + whenever(wrapped.serverAddress).doReturn(mock()) + + cursor.serverCursor + cursor.serverAddress + cursor.hasNext() + cursor.tryNext() + cursor.available + cursor.resumeToken + + verify(wrapped).serverCursor + verify(wrapped).serverAddress + verify(wrapped).hasNext() + verify(wrapped).tryNext() + verify(wrapped).available() + verify(wrapped).resumeToken + + verifyNoMoreInteractions(wrapped) + } +} diff --git a/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/MongoClientTest.kt b/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/MongoClientTest.kt new file mode 100644 index 00000000000..0999e77080e --- /dev/null +++ b/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/MongoClientTest.kt @@ -0,0 +1,171 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mongodb.kotlin.client + +import com.mongodb.ClientSessionOptions +import com.mongodb.client.MongoClient as JMongoClient +import kotlin.reflect.full.declaredFunctions +import kotlin.reflect.full.declaredMemberProperties +import kotlin.test.assertEquals +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 + +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() + + MongoClient::class + .declaredMemberProperties + .filterNot { it.name == "wrapped" } + .map { "get${it.name.replaceFirstChar{c -> c.uppercaseChar() }}" } + + 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.clusterDescription + + 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(mock()) + whenever(wrapped.startSession(options)).doReturn(mock()) + + 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-sync/src/test/kotlin/com/mongodb/kotlin/client/MongoCollectionTest.kt b/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/MongoCollectionTest.kt new file mode 100644 index 00000000000..d458c9302ce --- /dev/null +++ b/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/MongoCollectionTest.kt @@ -0,0 +1,882 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mongodb.kotlin.client + +import com.mongodb.CreateIndexCommitQuorum +import com.mongodb.MongoNamespace +import com.mongodb.ReadConcern +import com.mongodb.ReadPreference +import com.mongodb.WriteConcern +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.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 java.util.concurrent.TimeUnit +import kotlin.reflect.full.declaredFunctions +import kotlin.reflect.full.declaredMemberProperties +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals +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 + +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() - "mapReduce" + 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(1) + whenever(wrapped.countDocuments(eq(filter), refEq(defaultOptions))).doReturn(2) + whenever(wrapped.countDocuments(eq(filter), eq(options))).doReturn(3) + whenever(wrapped.countDocuments(eq(clientSession.wrapped), eq(defaultFilter), refEq(defaultOptions))) + .doReturn(4) + whenever(wrapped.countDocuments(eq(clientSession.wrapped), eq(filter), refEq(defaultOptions))).doReturn(5) + whenever(wrapped.countDocuments(eq(clientSession.wrapped), eq(filter), eq(options))).doReturn(6) + + 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(1) + whenever(wrapped.estimatedDocumentCount(options)).doReturn(2) + + 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) + } + + @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(mock()) + whenever(wrapped.insertOne(eq(value), eq(options))).doReturn(mock()) + whenever(wrapped.insertOne(eq(clientSession.wrapped), eq(value), refEq(defaultOptions))).doReturn(mock()) + whenever(wrapped.insertOne(eq(clientSession.wrapped), eq(value), eq(options))).doReturn(mock()) + + 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(mock()) + whenever(wrapped.insertMany(eq(value), eq(options))).doReturn(mock()) + whenever(wrapped.insertMany(eq(clientSession.wrapped), eq(value), refEq(defaultOptions))).doReturn(mock()) + whenever(wrapped.insertMany(eq(clientSession.wrapped), eq(value), eq(options))).doReturn(mock()) + + 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(mock()) + whenever(wrapped.bulkWrite(eq(value), eq(options))).doReturn(mock()) + whenever(wrapped.bulkWrite(eq(clientSession.wrapped), eq(value), refEq(defaultOptions))).doReturn(mock()) + whenever(wrapped.bulkWrite(eq(clientSession.wrapped), eq(value), eq(options))).doReturn(mock()) + + 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(mock()) + whenever(wrapped.updateOne(eq(filter), eq(update), eq(options))).doReturn(mock()) + whenever(wrapped.updateOne(eq(filter), eq(updates), refEq(defaultOptions))).doReturn(mock()) + whenever(wrapped.updateOne(eq(filter), eq(updates), eq(options))).doReturn(mock()) + whenever(wrapped.updateOne(eq(clientSession.wrapped), eq(filter), eq(update), refEq(defaultOptions))) + .doReturn(mock()) + whenever(wrapped.updateOne(eq(clientSession.wrapped), eq(filter), eq(update), eq(options))).doReturn(mock()) + whenever(wrapped.updateOne(eq(clientSession.wrapped), eq(filter), eq(updates), refEq(defaultOptions))) + .doReturn(mock()) + whenever(wrapped.updateOne(eq(clientSession.wrapped), eq(filter), eq(updates), eq(options))).doReturn(mock()) + + 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(mock()) + whenever(wrapped.updateMany(eq(filter), eq(update), eq(options))).doReturn(mock()) + whenever(wrapped.updateMany(eq(filter), eq(updates), refEq(defaultOptions))).doReturn(mock()) + whenever(wrapped.updateMany(eq(filter), eq(updates), eq(options))).doReturn(mock()) + whenever(wrapped.updateMany(eq(clientSession.wrapped), eq(filter), eq(update), refEq(defaultOptions))) + .doReturn(mock()) + whenever(wrapped.updateMany(eq(clientSession.wrapped), eq(filter), eq(update), eq(options))).doReturn(mock()) + whenever(wrapped.updateMany(eq(clientSession.wrapped), eq(filter), eq(updates), refEq(defaultOptions))) + .doReturn(mock()) + whenever(wrapped.updateMany(eq(clientSession.wrapped), eq(filter), eq(updates), eq(options))).doReturn(mock()) + + 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(mock()) + whenever(wrapped.replaceOne(eq(filter), eq(replacement), eq(options))).doReturn(mock()) + whenever(wrapped.replaceOne(eq(clientSession.wrapped), eq(filter), eq(replacement), refEq(defaultOptions))) + .doReturn(mock()) + whenever(wrapped.replaceOne(eq(clientSession.wrapped), eq(filter), eq(replacement), eq(options))) + .doReturn(mock()) + + 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(mock()) + whenever(wrapped.deleteOne(eq(filter), eq(options))).doReturn(mock()) + whenever(wrapped.deleteOne(eq(clientSession.wrapped), eq(filter), refEq(defaultOptions))).doReturn(mock()) + whenever(wrapped.deleteOne(eq(clientSession.wrapped), eq(filter), eq(options))).doReturn(mock()) + + 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(mock()) + whenever(wrapped.deleteMany(eq(filter), eq(options))).doReturn(mock()) + whenever(wrapped.deleteMany(eq(clientSession.wrapped), eq(filter), refEq(defaultOptions))).doReturn(mock()) + whenever(wrapped.deleteMany(eq(clientSession.wrapped), eq(filter), eq(options))).doReturn(mock()) + + 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(mock()) + whenever(wrapped.findOneAndDelete(eq(filter), eq(options))).doReturn(mock()) + whenever(wrapped.findOneAndDelete(eq(clientSession.wrapped), eq(filter), refEq(defaultOptions))) + .doReturn(mock()) + whenever(wrapped.findOneAndDelete(eq(clientSession.wrapped), eq(filter), eq(options))).doReturn(mock()) + + 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(mock()) + whenever(wrapped.findOneAndUpdate(eq(filter), eq(update), eq(options))).doReturn(mock()) + whenever(wrapped.findOneAndUpdate(eq(filter), eq(updateList), refEq(defaultOptions))).doReturn(mock()) + whenever(wrapped.findOneAndUpdate(eq(filter), eq(updateList), eq(options))).doReturn(mock()) + whenever(wrapped.findOneAndUpdate(eq(clientSession.wrapped), eq(filter), eq(update), refEq(defaultOptions))) + .doReturn(mock()) + whenever(wrapped.findOneAndUpdate(eq(clientSession.wrapped), eq(filter), eq(update), eq(options))) + .doReturn(mock()) + whenever(wrapped.findOneAndUpdate(eq(clientSession.wrapped), eq(filter), eq(updateList), refEq(defaultOptions))) + .doReturn(mock()) + whenever(wrapped.findOneAndUpdate(eq(clientSession.wrapped), eq(filter), eq(updateList), eq(options))) + .doReturn(mock()) + + 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(mock()) + whenever(wrapped.findOneAndReplace(eq(filter), eq(replacement), eq(options))).doReturn(mock()) + whenever( + wrapped.findOneAndReplace( + eq(clientSession.wrapped), eq(filter), eq(replacement), refEq(defaultOptions))) + .doReturn(mock()) + whenever(wrapped.findOneAndReplace(eq(clientSession.wrapped), eq(filter), eq(replacement), eq(options))) + .doReturn(mock()) + + 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()) + + 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("1") + whenever(wrapped.createIndex(eq(key), eq(options))).doReturn("2") + whenever(wrapped.createIndex(eq(clientSession.wrapped), eq(key), refEq(defaultOptions))).doReturn("3") + whenever(wrapped.createIndex(eq(clientSession.wrapped), eq(key), eq(options))).doReturn("4") + + 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(listOf("1")) + whenever(wrapped.createIndexes(eq(indexes), eq(options))).doReturn(listOf("2")) + whenever(wrapped.createIndexes(eq(clientSession.wrapped), eq(indexes), refEq(defaultOptions))) + .doReturn(listOf("3")) + whenever(wrapped.createIndexes(eq(clientSession.wrapped), eq(indexes), eq(options))).doReturn(listOf("4")) + + assertContentEquals(listOf("1"), mongoCollection.createIndexes(indexes)) + assertContentEquals(listOf("2"), mongoCollection.createIndexes(indexes, options)) + assertContentEquals(listOf("3"), mongoCollection.createIndexes(clientSession, indexes)) + assertContentEquals(listOf("4"), 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) + + 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) + + 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) + + 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-sync/src/test/kotlin/com/mongodb/kotlin/client/MongoCursorTest.kt b/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/MongoCursorTest.kt new file mode 100644 index 00000000000..b7530561d73 --- /dev/null +++ b/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/MongoCursorTest.kt @@ -0,0 +1,71 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mongodb.kotlin.client + +import com.mongodb.ServerAddress +import com.mongodb.ServerCursor +import com.mongodb.client.MongoCursor as JMongoCursor +import kotlin.reflect.full.declaredFunctions +import kotlin.reflect.full.declaredMemberProperties +import kotlin.test.assertEquals +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 MongoCursorTest { + @Test + fun shouldHaveTheSameMethods() { + val jMongoCursorFunctions = JMongoCursor::class.declaredFunctions.map { it.name }.toSet() + val kMongoCursorFunctions = + MongoCursorImpl::class.declaredFunctions.map { it.name }.toSet() + + MongoCursorImpl::class + .declaredMemberProperties + .filterNot { it.name == "wrapped" } + .map { + if (it.name == "available") it.name + else "get${it.name.replaceFirstChar{c -> c.uppercaseChar() }}" + } + + assertEquals(jMongoCursorFunctions, kMongoCursorFunctions) + } + + @Test + fun shouldCallTheUnderlyingMethods() { + val wrapped: JMongoCursor = mock() + val cursor = MongoCursorImpl(wrapped) + + whenever(wrapped.serverCursor).doReturn(ServerCursor(1, ServerAddress())) + whenever(wrapped.serverAddress).doReturn(mock()) + + cursor.serverCursor + cursor.serverAddress + cursor.hasNext() + cursor.tryNext() + cursor.available + + verify(wrapped).serverCursor + verify(wrapped).serverAddress + verify(wrapped).hasNext() + verify(wrapped).tryNext() + verify(wrapped).available() + + verifyNoMoreInteractions(wrapped) + } +} diff --git a/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/MongoDatabaseTest.kt b/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/MongoDatabaseTest.kt new file mode 100644 index 00000000000..6a7264545dc --- /dev/null +++ b/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/MongoDatabaseTest.kt @@ -0,0 +1,375 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mongodb.kotlin.client + +import com.mongodb.ReadConcern +import com.mongodb.ReadPreference +import com.mongodb.WriteConcern +import com.mongodb.client.MongoDatabase as JMongoDatabase +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.MockitoHelper.deepRefEq +import java.util.concurrent.TimeUnit +import kotlin.reflect.full.declaredFunctions +import kotlin.reflect.full.declaredMemberProperties +import kotlin.test.assertEquals +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 + +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(mock()) + whenever(wrapped.runCommand(clientSession.wrapped, command, primary, Document::class.java)).doReturn(mock()) + whenever(wrapped.runCommand(command, primary, BsonDocument::class.java)).doReturn(mock()) + whenever(wrapped.runCommand(clientSession.wrapped, command, primary, BsonDocument::class.java)).doReturn(mock()) + whenever(wrapped.runCommand(command, primaryPreferred, BsonDocument::class.java)).doReturn(mock()) + whenever(wrapped.runCommand(clientSession.wrapped, command, primaryPreferred, BsonDocument::class.java)) + .doReturn(mock()) + + 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) + + 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)) + + 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()) + + 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/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/MongoIterableTest.kt b/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/MongoIterableTest.kt new file mode 100644 index 00000000000..e2b0419e49d --- /dev/null +++ b/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/MongoIterableTest.kt @@ -0,0 +1,127 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mongodb.kotlin.client + +import com.mongodb.Function +import com.mongodb.client.MongoCursor as JMongoCursor +import com.mongodb.client.MongoIterable as JMongoIterable +import kotlin.test.assertContentEquals +import org.bson.Document +import org.junit.jupiter.api.Test +import org.mockito.ArgumentMatchers +import org.mockito.kotlin.any +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 + +class MongoIterableTest { + + @Suppress("UNCHECKED_CAST") + @Test + fun shouldCallTheUnderlyingMethods() { + val delegate: JMongoIterable = mock() + val cursor: JMongoCursor = mock() + val iterable = MongoIterable(delegate) + + val batchSize = 10 + val documents = listOf(Document("a", 1), Document("b", 2), Document("c", 3)) + val transform: (Document) -> String = { it.toJson() } + val transformClass: Class> = + Function::class.java as Class> + + whenever(cursor.hasNext()).thenReturn(true, true, true, false, true, true, true, false, true, true, true, false) + whenever(cursor.next()) + .thenReturn( + documents[0], + documents[1], + documents[2], + documents[0], + documents[1], + documents[2], + documents[0], + documents[1], + documents[2]) + whenever(delegate.cursor()).doReturn(cursor) + whenever(delegate.first()).doReturn(documents[0]) + + whenever(delegate.map(ArgumentMatchers.any(transformClass))).doReturn(mock()) + + iterable.batchSize(batchSize) + iterable.cursor() + iterable.first() + iterable.firstOrNull() + iterable.forEach { it.toString() } + iterable.toCollection(mutableListOf()) + iterable.use { it.take(2) } + iterable.map(transform) + + verify(delegate, times(1)).batchSize(batchSize) + verify(delegate, times(4)).cursor() + verify(delegate, times(2)).first() + verify(delegate, times(1)).map(ArgumentMatchers.any(transformClass)) + + verifyNoMoreInteractions(delegate) + } + + @Test + fun shouldCloseTheUnderlyingCursorWhenUsingUse() { + val delegate: JMongoIterable = mock() + val cursor: JMongoCursor = mock() + val iterable = MongoIterable(delegate) + + val documents = listOf(Document("a", 1), Document("b", 2), Document("c", 3)) + + whenever(cursor.hasNext()).thenReturn(true, true, true, false) + whenever(cursor.next()).thenReturn(documents[0], documents[1], documents[2]) + whenever(delegate.cursor()).doReturn(cursor) + + assertContentEquals(documents.subList(0, 2), iterable.use { it.take(2) }.toList()) + + verify(delegate, times(1)).cursor() + verify(cursor, times(2)).hasNext() + verify(cursor, times(2)).next() + verify(cursor, times(1)).close() + + verifyNoMoreInteractions(delegate) + verifyNoMoreInteractions(cursor) + } + + @Test + fun shouldCloseTheUnderlyingCursorWhenUsingToList() { + val delegate: JMongoIterable = mock() + val cursor: JMongoCursor = mock() + val iterable = MongoIterable(delegate) + + val documents = listOf(Document("a", 1), Document("b", 2), Document("c", 3)) + + whenever(cursor.hasNext()).thenReturn(true, true, true, false) + whenever(cursor.next()).thenReturn(documents[0], documents[1], documents[2]) + whenever(delegate.cursor()).doReturn(cursor) + + assertContentEquals(documents, iterable.toList()) + + verify(delegate, times(1)).cursor() + verify(cursor, times(4)).hasNext() + verify(cursor, times(3)).next() + verify(cursor, times(1)).close() + + verifyNoMoreInteractions(delegate) + verifyNoMoreInteractions(cursor) + } +} diff --git a/settings.gradle b/settings.gradle index 38dcb900739..1e1e661fd5c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -22,6 +22,7 @@ include ':driver-core' include ':driver-legacy' include ':driver-sync' include ':driver-reactive-streams' +include ':driver-kotlin-sync' include ':bson-scala' include ':driver-scala' include 'util:spock' From e815dad93a32bc5a88fbbf6a88daa252f0da0699 Mon Sep 17 00:00:00 2001 From: Ross Lawley Date: Thu, 2 Mar 2023 10:30:11 +0000 Subject: [PATCH 08/11] Bson-kotlin Library adds Bson support for Kotlin data classes Follows the spirit of the bson-record-codec library JAVA-4872 --- bson-kotlin/build.gradle.kts | 145 +++++++ .../org/bson/codecs/kotlin/DataClassCodec.kt | 271 +++++++++++++ .../codecs/kotlin/DataClassCodecProvider.kt | 25 ++ .../kotlin/DataClassCodecProviderTest.kt | 87 +++++ .../bson/codecs/kotlin/DataClassCodecTest.kt | 364 ++++++++++++++++++ .../bson/codecs/kotlin/samples/DataClasses.kt | 120 ++++++ .../pojo/annotations/BsonExtraElements.java | 2 +- .../codecs/pojo/annotations/BsonIgnore.java | 2 +- .../pojo/annotations/BsonRepresentation.java | 3 +- config/spotbugs/exclude.xml | 7 + driver-core/build.gradle | 1 + .../mongodb/KotlinDataClassCodecProvider.java | 56 +++ .../main/com/mongodb/MongoClientSettings.java | 4 +- settings.gradle | 1 + 14 files changed, 1084 insertions(+), 4 deletions(-) create mode 100644 bson-kotlin/build.gradle.kts create mode 100644 bson-kotlin/src/main/kotlin/org/bson/codecs/kotlin/DataClassCodec.kt create mode 100644 bson-kotlin/src/main/kotlin/org/bson/codecs/kotlin/DataClassCodecProvider.kt create mode 100644 bson-kotlin/src/test/kotlin/org/bson/codecs/kotlin/DataClassCodecProviderTest.kt create mode 100644 bson-kotlin/src/test/kotlin/org/bson/codecs/kotlin/DataClassCodecTest.kt create mode 100644 bson-kotlin/src/test/kotlin/org/bson/codecs/kotlin/samples/DataClasses.kt create mode 100644 driver-core/src/main/com/mongodb/KotlinDataClassCodecProvider.java diff --git a/bson-kotlin/build.gradle.kts b/bson-kotlin/build.gradle.kts new file mode 100644 index 00000000000..9b82584f4db --- /dev/null +++ b/bson-kotlin/build.gradle.kts @@ -0,0 +1,145 @@ +/* + * 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("bson-kotlin") + +description = "Bson Kotlin Codecs" + +ext.set("pomName", "Bson Kotlin") + +dependencies { + // Align versions of all Kotlin components + implementation(platform("org.jetbrains.kotlin:kotlin-bom")) + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") + + api(project(path = ":bson", configuration = "default")) + implementation("org.jetbrains.kotlin:kotlin-reflect") + + testImplementation("org.jetbrains.kotlin:kotlin-test-junit") + testImplementation(project(path = ":driver-core", configuration = "default")) +} + +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) } + +// =========================== +// Test Configuration +// =========================== +tasks.create("kCheck") { + description = "Runs all the kotlin checks" + group = "verification" + + dependsOn("clean", "check") + tasks.findByName("check")?.mustRunAfter("clean") +} + +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/bson-kotlin/src/main/kotlin/org/bson/codecs/kotlin/DataClassCodec.kt b/bson-kotlin/src/main/kotlin/org/bson/codecs/kotlin/DataClassCodec.kt new file mode 100644 index 00000000000..922e1a51e87 --- /dev/null +++ b/bson-kotlin/src/main/kotlin/org/bson/codecs/kotlin/DataClassCodec.kt @@ -0,0 +1,271 @@ +/* + * 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 org.bson.codecs.kotlin + +import java.lang.reflect.ParameterizedType +import java.lang.reflect.Type +import kotlin.reflect.KClass +import kotlin.reflect.KFunction +import kotlin.reflect.KParameter +import kotlin.reflect.KProperty1 +import kotlin.reflect.KType +import kotlin.reflect.KTypeParameter +import kotlin.reflect.full.createType +import kotlin.reflect.full.findAnnotation +import kotlin.reflect.full.findAnnotations +import kotlin.reflect.full.hasAnnotation +import kotlin.reflect.full.primaryConstructor +import kotlin.reflect.jvm.javaType +import org.bson.BsonReader +import org.bson.BsonType +import org.bson.BsonWriter +import org.bson.codecs.Codec +import org.bson.codecs.DecoderContext +import org.bson.codecs.EncoderContext +import org.bson.codecs.Parameterizable +import org.bson.codecs.RepresentationConfigurable +import org.bson.codecs.configuration.CodecConfigurationException +import org.bson.codecs.configuration.CodecRegistry +import org.bson.codecs.pojo.annotations.BsonCreator +import org.bson.codecs.pojo.annotations.BsonDiscriminator +import org.bson.codecs.pojo.annotations.BsonExtraElements +import org.bson.codecs.pojo.annotations.BsonId +import org.bson.codecs.pojo.annotations.BsonIgnore +import org.bson.codecs.pojo.annotations.BsonProperty +import org.bson.codecs.pojo.annotations.BsonRepresentation +import org.bson.diagnostics.Loggers + +internal data class DataClassCodec( + private val kClass: KClass, + private val primaryConstructor: KFunction, + private val propertyModels: List, +) : Codec { + + private val fieldNamePropertyModelMap = propertyModels.associateBy { it.fieldName } + private val propertyModelId: PropertyModel? = fieldNamePropertyModelMap[idFieldName] + + data class PropertyModel(val param: KParameter, val fieldName: String, val codec: Codec) + + override fun encode(writer: BsonWriter, value: T, encoderContext: EncoderContext) { + writer.writeStartDocument() + if (propertyModelId != null) { + encodeProperty(propertyModelId, value, writer, encoderContext) + } + propertyModels + .filter { it != propertyModelId } + .forEach { propertyModel -> encodeProperty(propertyModel, value, writer, encoderContext) } + writer.writeEndDocument() + } + + override fun getEncoderClass(): Class = kClass.java + + @Suppress("TooGenericExceptionCaught") + override fun decode(reader: BsonReader, decoderContext: DecoderContext): T { + val args: MutableMap = mutableMapOf() + fieldNamePropertyModelMap.values.forEach { args[it.param] = null } + + reader.readStartDocument() + while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) { + val fieldName = reader.readName() + val propertyModel = fieldNamePropertyModelMap[fieldName] + if (propertyModel == null) { + reader.skipValue() + if (logger.isTraceEnabled) { + logger.trace("Found property not present in the DataClass: $fieldName") + } + } else { + try { + args[propertyModel.param] = decoderContext.decodeWithChildContext(propertyModel.codec, reader) + } catch (e: Exception) { + throw CodecConfigurationException( + "Unable to decode $fieldName for ${kClass.simpleName} data class.", e) + } + } + } + reader.readEndDocument() + + try { + return primaryConstructor.callBy(args) + } catch (e: Exception) { + throw CodecConfigurationException( + "Unable to invoke primary constructor of ${kClass.simpleName} data class", e) + } + } + + @Suppress("UNCHECKED_CAST") + private fun encodeProperty( + propertyModel: PropertyModel, + value: T, + writer: BsonWriter, + encoderContext: EncoderContext + ) { + value::class + .members + .firstOrNull { it.name == propertyModel.param.name } + ?.let { + val propertyValue = (it as KProperty1).get(value) + propertyValue?.let { pValue -> + writer.writeName(propertyModel.fieldName) + encoderContext.encodeWithChildContext(propertyModel.codec, writer, pValue) + } + } + } + + companion object { + + internal val logger = Loggers.getLogger("DataClassCodec") + private const val idFieldName = "_id" + + fun create(kClass: KClass, codecRegistry: CodecRegistry): Codec? { + return if (!kClass.isData) null + else if (kClass.typeParameters.isEmpty()) createDataClassCodec(kClass, codecRegistry) + else RawDataClassCodec(kClass) + } + + internal fun createDataClassCodec( + kClass: KClass, + codecRegistry: CodecRegistry, + types: List = emptyList() + ): DataClassCodec { + validateAnnotations(kClass) + val primaryConstructor = + kClass.primaryConstructor ?: throw CodecConfigurationException("No primary constructor for $kClass") + val typeMap = types.mapIndexed { i, k -> primaryConstructor.typeParameters[i].createType() to k }.toMap() + + val propertyModels = + primaryConstructor.parameters.map { kParameter -> + PropertyModel( + kParameter, computeFieldName(kParameter), getCodec(kParameter, typeMap, codecRegistry)) + } + return DataClassCodec(kClass, primaryConstructor, propertyModels) + } + + private fun validateAnnotations(kClass: KClass) { + codecConfigurationRequires(kClass.findAnnotation() == null) { + """Annotation 'BsonDiscriminator' is not supported on kotlin data classes, + | but found on ${kClass.simpleName}.""" + .trimMargin() + } + + codecConfigurationRequires(kClass.constructors.all { it.findAnnotations().isEmpty() }) { + """Annotation 'BsonCreator' is not supported on kotlin data classes, + | but found in ${kClass.simpleName}.""" + .trimMargin() + } + + kClass.primaryConstructor?.parameters?.map { param -> + codecConfigurationRequires(param.findAnnotations().isEmpty()) { + """Annotation 'BsonIgnore' is not supported in kotlin data classes, + | found on the parameter for ${param.name}.""" + .trimMargin() + } + codecConfigurationRequires(param.findAnnotations().isEmpty()) { + """Annotation 'BsonExtraElements' is not supported in kotlin data classes, + | found on the parameter for ${param.name}.""" + .trimMargin() + } + } + } + + private fun computeFieldName(parameter: KParameter): String { + return if (parameter.hasAnnotation()) { + idFieldName + } else { + parameter.findAnnotation()?.value ?: requireNotNull(parameter.name) + } + } + + @Suppress("UNCHECKED_CAST") + private fun getCodec( + kParameter: KParameter, + typeMap: Map, + codecRegistry: CodecRegistry + ): Codec { + return when (kParameter.type.classifier) { + is KClass<*> -> { + codecRegistry.getCodec( + kParameter, + (kParameter.type.classifier as KClass).javaObjectType, + kParameter.type.arguments.mapNotNull { typeMap[it.type] ?: it.type?.javaType }.toList()) + } + is KTypeParameter -> { + when (val pType = typeMap[kParameter.type] ?: kParameter.type.javaType) { + is Class<*> -> + codecRegistry.getCodec(kParameter, (pType as Class).kotlin.javaObjectType, emptyList()) + is ParameterizedType -> + codecRegistry.getCodec( + kParameter, + (pType.rawType as Class).kotlin.javaObjectType, + pType.actualTypeArguments.toList()) + else -> null + } + } + else -> null + } + ?: throw CodecConfigurationException( + "Could not find codec for ${kParameter.name} with type ${kParameter.type}") + } + + @Suppress("UNCHECKED_CAST") + private fun CodecRegistry.getCodec(kParameter: KParameter, clazz: Class, types: List): Codec { + val codec = + if (types.isEmpty()) { + this.get(clazz) + } else { + this.get(clazz, types) + } + return kParameter.findAnnotation()?.let { + if (codec !is RepresentationConfigurable<*>) { + throw CodecConfigurationException( + "Codec for `${kParameter.name}` must implement RepresentationConfigurable" + + " to supportBsonRepresentation") + } + codec.withRepresentation(it.value) as Codec + } + ?: codec + } + + private fun codecConfigurationRequires(value: Boolean, lazyMessage: () -> String) { + if (!value) { + throw CodecConfigurationException(lazyMessage.invoke()) + } + } + + /** + * A Raw unparameterized data class + * + * It cannot encode or decode it just can create parameterized DataClassCodecs + */ + internal data class RawDataClassCodec(private val kClass: KClass) : Codec, Parameterizable { + + override fun getEncoderClass(): Class = kClass.java + + override fun parameterize(codecRegistry: CodecRegistry, types: List): Codec<*> { + return createDataClassCodec(kClass, codecRegistry, types) + } + + override fun decode(reader: BsonReader?, decoderContext: DecoderContext?): T { + throw CodecConfigurationException( + "Can not decode to ${kClass.simpleName} as it has type parameters and has not been parameterized.") + } + + override fun encode(writer: BsonWriter?, value: T, encoderContext: EncoderContext?) { + throw CodecConfigurationException( + "Can not encode to ${kClass.simpleName} as it has type parameters and has not been parameterized.") + } + } + } +} diff --git a/bson-kotlin/src/main/kotlin/org/bson/codecs/kotlin/DataClassCodecProvider.kt b/bson-kotlin/src/main/kotlin/org/bson/codecs/kotlin/DataClassCodecProvider.kt new file mode 100644 index 00000000000..7d55effe886 --- /dev/null +++ b/bson-kotlin/src/main/kotlin/org/bson/codecs/kotlin/DataClassCodecProvider.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 org.bson.codecs.kotlin + +import org.bson.codecs.Codec +import org.bson.codecs.configuration.CodecProvider +import org.bson.codecs.configuration.CodecRegistry + +public class DataClassCodecProvider : CodecProvider { + override fun get(clazz: Class, registry: CodecRegistry): Codec? = + DataClassCodec.create(clazz.kotlin, registry) +} diff --git a/bson-kotlin/src/test/kotlin/org/bson/codecs/kotlin/DataClassCodecProviderTest.kt b/bson-kotlin/src/test/kotlin/org/bson/codecs/kotlin/DataClassCodecProviderTest.kt new file mode 100644 index 00000000000..a1596359271 --- /dev/null +++ b/bson-kotlin/src/test/kotlin/org/bson/codecs/kotlin/DataClassCodecProviderTest.kt @@ -0,0 +1,87 @@ +/* + * 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 org.bson.codecs.kotlin + +import com.mongodb.MongoClientSettings +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull +import kotlin.test.assertTrue +import org.bson.BsonDocument +import org.bson.BsonDocumentReader +import org.bson.BsonDocumentWriter +import org.bson.codecs.DecoderContext +import org.bson.codecs.EncoderContext +import org.bson.codecs.configuration.CodecConfigurationException +import org.bson.codecs.kotlin.samples.DataClass +import org.bson.codecs.kotlin.samples.DataClassEmbedded +import org.bson.codecs.kotlin.samples.DataClassParameterized +import org.bson.codecs.kotlin.samples.DataClassWithParameterizedDataClass +import org.bson.conversions.Bson +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + +class DataClassCodecProviderTest { + + @Test + fun shouldReturnNullForNonDataClass() { + assertNull(DataClassCodecProvider().get(String::class.java, Bson.DEFAULT_CODEC_REGISTRY)) + } + + @Test + fun shouldReturnDataClassCodecForDataClass() { + val provider = DataClassCodecProvider() + val codec = provider.get(DataClass::class.java, Bson.DEFAULT_CODEC_REGISTRY) + + assertNotNull(codec) + assertTrue { codec is DataClassCodec } + assertEquals(DataClass::class.java, codec.encoderClass) + } + + @Test + fun shouldReturnRawDataClassCodecForParameterizedDataClass() { + val provider = DataClassCodecProvider() + val codec = provider.get(DataClassParameterized::class.java, Bson.DEFAULT_CODEC_REGISTRY) + + assertNotNull(codec) + assertTrue { codec is DataClassCodec.Companion.RawDataClassCodec } + assertEquals(DataClassParameterized::class.java, codec.encoderClass) + + assertThrows { + val writer = BsonDocumentWriter(BsonDocument()) + val dataClass = + DataClassWithParameterizedDataClass( + "myId", DataClassParameterized(2.0, "myString", listOf(DataClassEmbedded("embedded1")))) + codec.encode(writer, dataClass.parameterizedDataClass, EncoderContext.builder().build()) + } + + assertThrows { + val value = + BsonDocument.parse( + """{"number": 2.0, "string": "myString", "parameterizedList": [{"name": "embedded1"}]}""") + codec.decode(BsonDocumentReader(value), DecoderContext.builder().build()) + } + } + + @Test + fun shouldReturnDataClassCodecUsingDefaultRegistry() { + val codec = MongoClientSettings.getDefaultCodecRegistry().get(DataClass::class.java) + + assertNotNull(codec) + assertTrue { codec is DataClassCodec } + assertEquals(DataClass::class.java, codec.encoderClass) + } +} diff --git a/bson-kotlin/src/test/kotlin/org/bson/codecs/kotlin/DataClassCodecTest.kt b/bson-kotlin/src/test/kotlin/org/bson/codecs/kotlin/DataClassCodecTest.kt new file mode 100644 index 00000000000..cbc65e12e9a --- /dev/null +++ b/bson-kotlin/src/test/kotlin/org/bson/codecs/kotlin/DataClassCodecTest.kt @@ -0,0 +1,364 @@ +/* + * 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 org.bson.codecs.kotlin + +import kotlin.test.assertEquals +import org.bson.BsonDocument +import org.bson.BsonDocumentReader +import org.bson.BsonDocumentWriter +import org.bson.codecs.Codec +import org.bson.codecs.DecoderContext +import org.bson.codecs.EncoderContext +import org.bson.codecs.configuration.CodecConfigurationException +import org.bson.codecs.configuration.CodecRegistries.fromProviders +import org.bson.codecs.kotlin.DataClassCodec.Companion.createDataClassCodec +import org.bson.codecs.kotlin.samples.DataClass +import org.bson.codecs.kotlin.samples.DataClassEmbedded +import org.bson.codecs.kotlin.samples.DataClassListOfDataClasses +import org.bson.codecs.kotlin.samples.DataClassListOfListOfDataClasses +import org.bson.codecs.kotlin.samples.DataClassMapOfDataClasses +import org.bson.codecs.kotlin.samples.DataClassMapOfListOfDataClasses +import org.bson.codecs.kotlin.samples.DataClassNestedParameterizedTypes +import org.bson.codecs.kotlin.samples.DataClassParameterized +import org.bson.codecs.kotlin.samples.DataClassSelfReferential +import org.bson.codecs.kotlin.samples.DataClassWithAnnotations +import org.bson.codecs.kotlin.samples.DataClassWithBsonConstructor +import org.bson.codecs.kotlin.samples.DataClassWithBsonDiscriminator +import org.bson.codecs.kotlin.samples.DataClassWithBsonExtraElements +import org.bson.codecs.kotlin.samples.DataClassWithBsonIgnore +import org.bson.codecs.kotlin.samples.DataClassWithDefaults +import org.bson.codecs.kotlin.samples.DataClassWithEmbedded +import org.bson.codecs.kotlin.samples.DataClassWithFailingInit +import org.bson.codecs.kotlin.samples.DataClassWithInvalidRepresentation +import org.bson.codecs.kotlin.samples.DataClassWithMutableList +import org.bson.codecs.kotlin.samples.DataClassWithMutableMap +import org.bson.codecs.kotlin.samples.DataClassWithMutableSet +import org.bson.codecs.kotlin.samples.DataClassWithNestedParameterized +import org.bson.codecs.kotlin.samples.DataClassWithNestedParameterizedDataClass +import org.bson.codecs.kotlin.samples.DataClassWithNulls +import org.bson.codecs.kotlin.samples.DataClassWithPair +import org.bson.codecs.kotlin.samples.DataClassWithParameterizedDataClass +import org.bson.codecs.kotlin.samples.DataClassWithSequence +import org.bson.codecs.kotlin.samples.DataClassWithTriple +import org.bson.conversions.Bson +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + +class DataClassCodecTest { + + @Test + fun testDataClass() { + val expected = """{"_id": "myId", "name": "Felix", "age": 14, "hobbies": ["rugby", "weights"]}""" + val dataClass = DataClass("myId", "Felix", 14, listOf("rugby", "weights")) + + assertRoundTrips(expected, dataClass) + } + + @Test + fun testDataClassWithEmbedded() { + val expected = """{"_id": "myId", "embedded": {"name": "embedded1"}}""" + val dataClass = DataClassWithEmbedded("myId", DataClassEmbedded("embedded1")) + + assertRoundTrips(expected, dataClass) + } + + @Test + fun testDataClassWithAnnotations() { + val oid = "\$oid" + val expected = + """{"_id": {"$oid": "111111111111111111111111"}, + |"nom": "Felix", "age": 14, "hobbies": ["rugby", "weights"]}""" + .trimMargin() + val dataClass = DataClassWithAnnotations("111111111111111111111111", "Felix", 14, listOf("rugby", "weights")) + + assertRoundTrips(expected, dataClass) + } + + @Test + fun testDataClassListOfDataClasses() { + val expected = """{"_id": "myId", "nested": [{"name": "embedded1"}, {"name": "embedded2"}]}""" + val dataClass = + DataClassListOfDataClasses("myId", listOf(DataClassEmbedded("embedded1"), DataClassEmbedded("embedded2"))) + + assertRoundTrips(expected, dataClass) + } + + @Test + fun testDataClassListOfListOfDataClasses() { + val expected = """{"_id": "myId", "nested": [[{"name": "embedded1"}], [{"name": "embedded2"}]]}""" + val dataClass = + DataClassListOfListOfDataClasses( + "myId", listOf(listOf(DataClassEmbedded("embedded1")), listOf(DataClassEmbedded("embedded2")))) + + assertRoundTrips(expected, dataClass) + } + + @Test + fun testDataClassMapOfDataClasses() { + val expected = + """{"_id": "myId", "nested": {"first": {"name": "embedded1"}, "second": {"name": "embedded2"}}}""" + val dataClass = + DataClassMapOfDataClasses( + "myId", mapOf("first" to DataClassEmbedded("embedded1"), "second" to DataClassEmbedded("embedded2"))) + + assertRoundTrips(expected, dataClass) + } + + @Test + fun testDataClassMapOfListOfDataClasses() { + val expected = + """{"_id": "myId", "nested": {"first": [{"name": "embedded1"}], "second": [{"name": "embedded2"}]}}""" + val dataClass = + DataClassMapOfListOfDataClasses( + "myId", + mapOf( + "first" to listOf(DataClassEmbedded("embedded1")), + "second" to listOf(DataClassEmbedded("embedded2")))) + + assertRoundTrips(expected, dataClass) + } + + @Test + fun testDataClassWithNulls() { + val expected = """{"name": "Felix", "hobbies": ["rugby", "weights"]}""" + val dataClass = DataClassWithNulls(null, "Felix", null, listOf("rugby", "weights")) + + assertRoundTrips(expected, dataClass) + } + + @Test + fun testDataClassWithDefaults() { + val expected = """{"_id": "myId", "name": "Arthur Dent", "age": 42, "hobbies": ["computers", "databases"]}""" + val dataClass = DataClassWithDefaults("myId", "Arthur Dent") + + assertRoundTrips(expected, dataClass) + } + + @Test + fun testHandlesExtraData() { + val expected = + """{"_id": "myId", "extra1": "extraField", "name": "Felix", "extra2": "extraField", "age": 14, + | "extra3": "extraField", "hobbies": ["rugby", "weights"], "extra4": "extraField"}""" + .trimMargin() + val dataClass = DataClass("myId", "Felix", 14, listOf("rugby", "weights")) + + assertDecodesTo(dataClass, BsonDocument.parse(expected)) + } + + @Test + fun testDataClassSelfReferential() { + val expected = + """{"_id": "myId", "name": "tree", + | "left": {"name": "L", "left": {"name": "LL"}, "right": {"name": "LR"}}, + | "right": {"name": "R", + | "left": {"name": "RL", + | "left": {"name": "RLL"}, + | "right": {"name": "RLR"}}, + | "right": {"name": "RR"}} + |}""" + .trimMargin() + val dataClass = + DataClassSelfReferential( + "tree", + DataClassSelfReferential("L", DataClassSelfReferential("LL"), DataClassSelfReferential("LR")), + DataClassSelfReferential( + "R", + DataClassSelfReferential("RL", DataClassSelfReferential("RLL"), DataClassSelfReferential("RLR")), + DataClassSelfReferential("RR")), + id = "myId") + + assertRoundTrips(expected, dataClass) + } + + @Test + fun testDataClassWithParameterizedDataClass() { + val expected = + """{"_id": "myId", + | "parameterizedDataClass": {"number": 2.0, "string": "myString", + | "parameterizedList": [{"name": "embedded1"}]} + |}""" + .trimMargin() + val dataClass = + DataClassWithParameterizedDataClass( + "myId", DataClassParameterized(2.0, "myString", listOf(DataClassEmbedded("embedded1")))) + + assertRoundTrips(expected, dataClass) + } + + @Test + fun testDataClassWithNestedParameterizedDataClass() { + val expected = + """{"_id": "myId", + |"nestedParameterized": { + | "parameterizedDataClass": + | {"number": 4.2, "string": "myString", "parameterizedList": [{"name": "embedded1"}]}, + | "other": "myOtherString" + | } + |}""" + .trimMargin() + val dataClass = + DataClassWithNestedParameterizedDataClass( + "myId", + DataClassWithNestedParameterized( + DataClassParameterized(4.2, "myString", listOf(DataClassEmbedded("embedded1"))), "myOtherString")) + + assertRoundTrips(expected, dataClass) + } + + @Test + fun testDataClassWithPair() { + val expected = """{"pair": {"first": "a", "second": 1}}""" + val dataClass = DataClassWithPair("a" to 1) + + assertRoundTrips(expected, dataClass) + } + + @Test + fun testDataClassWithTriple() { + val expected = """{"triple": {"first": "a", "second": 1, "third": 2.1}}""" + val dataClass = DataClassWithTriple(Triple("a", 1, 2.1)) + + assertRoundTrips(expected, dataClass) + } + + @Test + fun testDataClassNestedParameterizedTypes() { + + val expected = + """{ + |"triple": { + | "first": "0", + | "second": {"first": 1, "second": {"first": 1.2, "second": {"first": "1.3", "second": 1.3}}}, + | "third": {"first": 2, "second": {"first": 2.1, "second": "two dot two"}, + | "third": {"first": "3.1", "second": {"first": 3.2, "second": "three dot two" }, + | "third": 3.3 }} + | } + |}""" + .trimMargin() + val dataClass = + DataClassNestedParameterizedTypes( + Triple( + "0", + Pair(1, Pair(1.2, Pair("1.3", 1.3))), + Triple(2, Pair(2.1, "two dot two"), Triple("3.1", Pair(3.2, "three dot two"), 3.3)))) + + assertRoundTrips(expected, dataClass) + } + + @Test + fun testDataClassWithMutableList() { + val expected = """{"value": ["A", "B", "C"]}""" + val dataClass = DataClassWithMutableList(mutableListOf("A", "B", "C")) + + assertRoundTrips(expected, dataClass) + } + + @Test + fun testDataClassWithMutableSet() { + val expected = """{"value": ["A", "B", "C"]}""" + val dataClass = DataClassWithMutableSet(mutableSetOf("A", "B", "C")) + + assertRoundTrips(expected, dataClass) + } + + @Test + fun testDataClassWithMutableMap() { + val expected = """{"value": {"a": "A", "b": "B", "c": "C"}}""" + val dataClass = DataClassWithMutableMap(mutableMapOf("a" to "A", "b" to "B", "c" to "C")) + + assertRoundTrips(expected, dataClass) + } + + @Test + fun testDataFailures() { + assertThrows("Missing data") { + val codec: Codec = createDataClassCodec(DataClass::class, registry()) + codec.decode(BsonDocumentReader(BsonDocument()), DecoderContext.builder().build()) + } + + assertThrows("Unsupported type Sequence") { + createDataClassCodec(DataClassWithSequence::class, registry()) + } + + assertThrows("Invalid types") { + val data = + BsonDocument.parse("""{"_id": "myId", "name": "Imogen", "age": "16", "hobbies": ["rugby", "gym"]}""") + val codec: Codec = createDataClassCodec(DataClass::class, registry()) + codec.decode(BsonDocumentReader(data), DecoderContext.builder().build()) + } + + assertThrows("Invalid complex types") { + val data = BsonDocument.parse("""{"_id": "myId", "embedded": 123}""") + val codec: Codec = createDataClassCodec(DataClassWithEmbedded::class, registry()) + codec.decode(BsonDocumentReader(data), DecoderContext.builder().build()) + } + + assertThrows("Failing init") { + val data = BsonDocument.parse("""{"_id": "myId"}""") + val codec: DataClassCodec = + createDataClassCodec(DataClassWithFailingInit::class, registry()) + codec.decode(BsonDocumentReader(data), DecoderContext.builder().build()) + } + } + + @Test + fun testInvalidAnnotations() { + assertThrows { + createDataClassCodec(DataClassWithBsonDiscriminator::class, Bson.DEFAULT_CODEC_REGISTRY) + } + assertThrows { + createDataClassCodec(DataClassWithBsonConstructor::class, Bson.DEFAULT_CODEC_REGISTRY) + } + assertThrows { + createDataClassCodec(DataClassWithBsonIgnore::class, Bson.DEFAULT_CODEC_REGISTRY) + } + assertThrows { + createDataClassCodec(DataClassWithBsonExtraElements::class, Bson.DEFAULT_CODEC_REGISTRY) + } + assertThrows { + createDataClassCodec(DataClassWithInvalidRepresentation::class, Bson.DEFAULT_CODEC_REGISTRY) + } + } + + private fun assertRoundTrips(expected: String, value: T) { + assertDecodesTo(value, assertEncodesTo(expected, value)) + } + + @Suppress("UNCHECKED_CAST") + private fun assertEncodesTo(json: String, value: T): BsonDocument { + val expected = BsonDocument.parse(json) + val codec: DataClassCodec = createDataClassCodec(value::class, registry()) as DataClassCodec + val document = BsonDocument() + val writer = BsonDocumentWriter(document) + + codec.encode(writer, value, EncoderContext.builder().build()) + assertEquals(expected, document) + if (expected.contains("_id")) { + assertEquals("_id", document.firstKey) + } + return document + } + + @Suppress("UNCHECKED_CAST") + private fun assertDecodesTo(expected: T, actual: BsonDocument) { + val codec: DataClassCodec = createDataClassCodec(expected::class, registry()) as DataClassCodec + val decoded: T = codec.decode(BsonDocumentReader(actual), DecoderContext.builder().build()) + + assertEquals(expected, decoded) + } + + private fun registry() = fromProviders(DataClassCodecProvider(), Bson.DEFAULT_CODEC_REGISTRY) +} diff --git a/bson-kotlin/src/test/kotlin/org/bson/codecs/kotlin/samples/DataClasses.kt b/bson-kotlin/src/test/kotlin/org/bson/codecs/kotlin/samples/DataClasses.kt new file mode 100644 index 00000000000..a11f069be59 --- /dev/null +++ b/bson-kotlin/src/test/kotlin/org/bson/codecs/kotlin/samples/DataClasses.kt @@ -0,0 +1,120 @@ +/* + * 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 org.bson.codecs.kotlin.samples + +import org.bson.BsonMaxKey +import org.bson.BsonType +import org.bson.Document +import org.bson.codecs.pojo.annotations.BsonCreator +import org.bson.codecs.pojo.annotations.BsonDiscriminator +import org.bson.codecs.pojo.annotations.BsonExtraElements +import org.bson.codecs.pojo.annotations.BsonId +import org.bson.codecs.pojo.annotations.BsonIgnore +import org.bson.codecs.pojo.annotations.BsonProperty +import org.bson.codecs.pojo.annotations.BsonRepresentation + +@Suppress("PropertyName", "ConstructorParameterNaming") +data class DataClass(val _id: String, val name: String, val age: Int, val hobbies: List) + +data class DataClassWithAnnotations( + @BsonRepresentation(BsonType.OBJECT_ID) @BsonId val oid: String, + @BsonProperty("nom") val name: String, + val age: Int, + val hobbies: List +) + +data class DataClassEmbedded(val name: String) + +data class DataClassWithEmbedded(@BsonId val id: String, val embedded: DataClassEmbedded) + +data class DataClassListOfDataClasses(@BsonId val id: String, val nested: List) + +data class DataClassListOfListOfDataClasses(@BsonId val id: String, val nested: List>) + +data class DataClassMapOfDataClasses(@BsonId val id: String, val nested: Map) + +data class DataClassMapOfListOfDataClasses(@BsonId val id: String, val nested: Map>) + +data class DataClassWithNulls(@BsonId val id: String?, val name: String, val age: Int?, val hobbies: List) + +data class DataClassWithDefaults( + @BsonId val id: String, + val name: String, + val age: Int = 42, + val hobbies: List = listOf("computers", "databases") +) + +data class DataClassSelfReferential( + val name: String, + val left: DataClassSelfReferential? = null, + val right: DataClassSelfReferential? = null, + @BsonId val id: String? = null +) + +data class DataClassWithParameterizedDataClass( + @BsonId val id: String, + val parameterizedDataClass: DataClassParameterized +) + +data class DataClassParameterized(val number: N, val string: String, val parameterizedList: List) + +data class DataClassWithNestedParameterizedDataClass( + @BsonId val id: String, + val nestedParameterized: DataClassWithNestedParameterized +) + +data class DataClassWithNestedParameterized( + val parameterizedDataClass: DataClassParameterized, + val other: B +) + +data class DataClassWithPair(val pair: Pair) + +data class DataClassWithTriple(val triple: Triple) + +data class DataClassNestedParameterizedTypes( + val triple: + Triple< + String, + Pair>>, + Triple, Triple, Double>>> +) + +data class DataClassWithMutableList(val value: MutableList) + +data class DataClassWithMutableSet(val value: MutableSet) + +data class DataClassWithMutableMap(val value: MutableMap) + +@BsonDiscriminator data class DataClassWithBsonDiscriminator(val id: String) + +data class DataClassWithBsonIgnore(val id: String, @BsonIgnore val ignored: String) + +data class DataClassWithBsonExtraElements(val id: String, @BsonExtraElements val extraElements: Document) + +data class DataClassWithBsonConstructor(val id: String, val count: Int) { + @BsonCreator constructor(id: String) : this(id, -1) +} + +data class DataClassWithInvalidRepresentation(@BsonRepresentation(BsonType.STRING) val id: BsonMaxKey) + +data class DataClassWithFailingInit(@BsonId val id: String) { + init { + require(false) + } +} + +data class DataClassWithSequence(val value: Sequence) diff --git a/bson/src/main/org/bson/codecs/pojo/annotations/BsonExtraElements.java b/bson/src/main/org/bson/codecs/pojo/annotations/BsonExtraElements.java index 7362ee66839..1ae25e5da3d 100644 --- a/bson/src/main/org/bson/codecs/pojo/annotations/BsonExtraElements.java +++ b/bson/src/main/org/bson/codecs/pojo/annotations/BsonExtraElements.java @@ -37,6 +37,6 @@ */ @Documented @Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.METHOD, ElementType.FIELD}) +@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER}) public @interface BsonExtraElements { } diff --git a/bson/src/main/org/bson/codecs/pojo/annotations/BsonIgnore.java b/bson/src/main/org/bson/codecs/pojo/annotations/BsonIgnore.java index 98adcdb7145..96b91051995 100644 --- a/bson/src/main/org/bson/codecs/pojo/annotations/BsonIgnore.java +++ b/bson/src/main/org/bson/codecs/pojo/annotations/BsonIgnore.java @@ -32,7 +32,7 @@ * @see org.bson.codecs.pojo.Conventions#ANNOTATION_CONVENTION */ @Documented -@Target({ElementType.METHOD, ElementType.FIELD}) +@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface BsonIgnore { } diff --git a/bson/src/main/org/bson/codecs/pojo/annotations/BsonRepresentation.java b/bson/src/main/org/bson/codecs/pojo/annotations/BsonRepresentation.java index 3f3056f77ef..465e64d016f 100644 --- a/bson/src/main/org/bson/codecs/pojo/annotations/BsonRepresentation.java +++ b/bson/src/main/org/bson/codecs/pojo/annotations/BsonRepresentation.java @@ -29,13 +29,14 @@ * *

For POJOs, requires the {@link org.bson.codecs.pojo.Conventions#ANNOTATION_CONVENTION}

*

For Java records, the annotation is only supported on the record component.

+ *

For Kotlin data classes, the annotation is only supported on the constructor parameter.

* * @since 4.2 * @see org.bson.codecs.pojo.Conventions#ANNOTATION_CONVENTION */ @Documented @Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.FIELD, ElementType.METHOD}) +@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER}) public @interface BsonRepresentation { /** * The type that the property is stored as in the database. diff --git a/config/spotbugs/exclude.xml b/config/spotbugs/exclude.xml index f53c390741a..7e17f2529fc 100644 --- a/config/spotbugs/exclude.xml +++ b/config/spotbugs/exclude.xml @@ -245,4 +245,11 @@
+ + + + + + + diff --git a/driver-core/build.gradle b/driver-core/build.gradle index f62014ee38b..067af77087e 100644 --- a/driver-core/build.gradle +++ b/driver-core/build.gradle @@ -37,6 +37,7 @@ def classifiers = ["linux-x86_64", "linux-aarch_64", "osx-x86_64", "osx-aarch_64 dependencies { api project(path: ':bson', configuration: 'default') implementation project(path: ':bson-record-codec', configuration: 'default') + implementation project(path: ':bson-kotlin', configuration: 'default') implementation "com.github.jnr:jnr-unixsocket:$jnrUnixsocketVersion", optional api platform("io.netty:netty-bom:$nettyVersion") diff --git a/driver-core/src/main/com/mongodb/KotlinDataClassCodecProvider.java b/driver-core/src/main/com/mongodb/KotlinDataClassCodecProvider.java new file mode 100644 index 00000000000..9a6a7955153 --- /dev/null +++ b/driver-core/src/main/com/mongodb/KotlinDataClassCodecProvider.java @@ -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; + +import com.mongodb.lang.Nullable; +import org.bson.codecs.Codec; +import org.bson.codecs.configuration.CodecProvider; +import org.bson.codecs.configuration.CodecRegistry; +import org.bson.codecs.kotlin.DataClassCodecProvider; + + +/** + * A CodecProvider for Kotlin data classes. + * + *

Requires the bson-kotlin package.

+ * + * @since 4.10 + */ +public class KotlinDataClassCodecProvider implements CodecProvider { + + @Nullable + private static final CodecProvider DATA_CLASS_CODEC_PROVIDER; + static { + + CodecProvider possibleCodecProvider; + try { + Class.forName("org.bson.codecs.kotlin.DataClassCodecProvider"); // Kotlin bson canary test + possibleCodecProvider = new DataClassCodecProvider(); + } catch (ClassNotFoundException e) { + // No kotlin data class support + possibleCodecProvider = null; + } + DATA_CLASS_CODEC_PROVIDER = possibleCodecProvider; + } + + @Override + @Nullable + public Codec get(final Class clazz, final CodecRegistry registry) { + return DATA_CLASS_CODEC_PROVIDER != null ? DATA_CLASS_CODEC_PROVIDER.get(clazz, registry) : null; + } + +} + diff --git a/driver-core/src/main/com/mongodb/MongoClientSettings.java b/driver-core/src/main/com/mongodb/MongoClientSettings.java index 7fd7e116043..14dc46f1711 100644 --- a/driver-core/src/main/com/mongodb/MongoClientSettings.java +++ b/driver-core/src/main/com/mongodb/MongoClientSettings.java @@ -78,7 +78,8 @@ public final class MongoClientSettings { new BsonCodecProvider(), new EnumCodecProvider(), new ExpressionCodecProvider(), - new Jep395RecordCodecProvider())); + new Jep395RecordCodecProvider(), + new KotlinDataClassCodecProvider())); private final ReadPreference readPreference; private final WriteConcern writeConcern; @@ -127,6 +128,7 @@ public final class MongoClientSettings { *
  • {@link org.bson.codecs.EnumCodecProvider}
  • *
  • {@link ExpressionCodecProvider}
  • *
  • {@link com.mongodb.Jep395RecordCodecProvider}
  • + *
  • {@link com.mongodb.KotlinDataClassCodecProvider}
  • * * *

    diff --git a/settings.gradle b/settings.gradle index 1e1e661fd5c..7ef5498b954 100644 --- a/settings.gradle +++ b/settings.gradle @@ -22,6 +22,7 @@ include ':driver-core' include ':driver-legacy' include ':driver-sync' include ':driver-reactive-streams' +include ':bson-kotlin' include ':driver-kotlin-sync' include ':bson-scala' include ':driver-scala' From dc79ce4db554b78444c32e0d79657cfb47fa597b Mon Sep 17 00:00:00 2001 From: Ross Lawley Date: Tue, 21 Mar 2023 15:57:36 +0000 Subject: [PATCH 09/11] Update scala ApiAliasAndCompanionSpec --- .../scala/org/mongodb/scala/ApiAliasAndCompanionSpec.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/driver-scala/src/test/scala/org/mongodb/scala/ApiAliasAndCompanionSpec.scala b/driver-scala/src/test/scala/org/mongodb/scala/ApiAliasAndCompanionSpec.scala index 61ca7e3a934..d0b7d0fe5ec 100644 --- a/driver-scala/src/test/scala/org/mongodb/scala/ApiAliasAndCompanionSpec.scala +++ b/driver-scala/src/test/scala/org/mongodb/scala/ApiAliasAndCompanionSpec.scala @@ -57,6 +57,7 @@ class ApiAliasAndCompanionSpec extends BaseSpec { "FutureResultCallback", "Jep395RecordCodecProvider", "KerberosSubjectProvider", + "KotlinDataClassCodecProvider", "MongoClients", "NonNull", "NonNullApi", @@ -128,6 +129,8 @@ class ApiAliasAndCompanionSpec extends BaseSpec { |-com.mongodb.management.*, |-com.mongodb.operation.*, |-com.mongodb.selector.*, + |-com.mongodb.kotlin.*, + |-com.mongodb.test.*, |-com.mongodb.client.gridfs.*, |-com.mongodb.async.client.*, |-com.mongodb.async.client.gridfs.*, From 814fd15e595490e05f9915dcfe146c3a7acdd94c Mon Sep 17 00:00:00 2001 From: Ross Lawley Date: Wed, 22 Mar 2023 11:59:55 +0000 Subject: [PATCH 10/11] Updated bson-kotlin in prep for bson-kotlinx Expanded the test cases Simplified the DataClassCodecProvider JAVA-4872 JAVA-4873 --- .../org/bson/codecs/kotlin/DataClassCodec.kt | 20 +- .../codecs/kotlin/DataClassCodecProvider.kt | 1 + .../kotlin/DataClassCodecProviderTest.kt | 10 +- .../bson/codecs/kotlin/DataClassCodecTest.kt | 336 ++++++++++++------ .../bson/codecs/kotlin/samples/DataClasses.kt | 107 ++++-- 5 files changed, 328 insertions(+), 146 deletions(-) diff --git a/bson-kotlin/src/main/kotlin/org/bson/codecs/kotlin/DataClassCodec.kt b/bson-kotlin/src/main/kotlin/org/bson/codecs/kotlin/DataClassCodec.kt index 922e1a51e87..42e493b9706 100644 --- a/bson-kotlin/src/main/kotlin/org/bson/codecs/kotlin/DataClassCodec.kt +++ b/bson-kotlin/src/main/kotlin/org/bson/codecs/kotlin/DataClassCodec.kt @@ -129,17 +129,25 @@ internal data class DataClassCodec( internal val logger = Loggers.getLogger("DataClassCodec") private const val idFieldName = "_id" - fun create(kClass: KClass, codecRegistry: CodecRegistry): Codec? { - return if (!kClass.isData) null - else if (kClass.typeParameters.isEmpty()) createDataClassCodec(kClass, codecRegistry) - else RawDataClassCodec(kClass) + internal fun create( + kClass: KClass, + codecRegistry: CodecRegistry, + types: List = emptyList() + ): Codec? { + return if (!kClass.isData) { + null + } else if (kClass.typeParameters.isNotEmpty()) { + RawDataClassCodec(kClass) + } else { + createDataClassCodec(kClass, codecRegistry, types) + } } - internal fun createDataClassCodec( + private fun createDataClassCodec( kClass: KClass, codecRegistry: CodecRegistry, types: List = emptyList() - ): DataClassCodec { + ): Codec { validateAnnotations(kClass) val primaryConstructor = kClass.primaryConstructor ?: throw CodecConfigurationException("No primary constructor for $kClass") diff --git a/bson-kotlin/src/main/kotlin/org/bson/codecs/kotlin/DataClassCodecProvider.kt b/bson-kotlin/src/main/kotlin/org/bson/codecs/kotlin/DataClassCodecProvider.kt index 7d55effe886..8053529002b 100644 --- a/bson-kotlin/src/main/kotlin/org/bson/codecs/kotlin/DataClassCodecProvider.kt +++ b/bson-kotlin/src/main/kotlin/org/bson/codecs/kotlin/DataClassCodecProvider.kt @@ -19,6 +19,7 @@ import org.bson.codecs.Codec import org.bson.codecs.configuration.CodecProvider import org.bson.codecs.configuration.CodecRegistry +/** A Kotlin reflection based Codec Provider for data classes */ public class DataClassCodecProvider : CodecProvider { override fun get(clazz: Class, registry: CodecRegistry): Codec? = DataClassCodec.create(clazz.kotlin, registry) diff --git a/bson-kotlin/src/test/kotlin/org/bson/codecs/kotlin/DataClassCodecProviderTest.kt b/bson-kotlin/src/test/kotlin/org/bson/codecs/kotlin/DataClassCodecProviderTest.kt index a1596359271..7de4bfca944 100644 --- a/bson-kotlin/src/test/kotlin/org/bson/codecs/kotlin/DataClassCodecProviderTest.kt +++ b/bson-kotlin/src/test/kotlin/org/bson/codecs/kotlin/DataClassCodecProviderTest.kt @@ -26,10 +26,10 @@ import org.bson.BsonDocumentWriter import org.bson.codecs.DecoderContext import org.bson.codecs.EncoderContext import org.bson.codecs.configuration.CodecConfigurationException -import org.bson.codecs.kotlin.samples.DataClass import org.bson.codecs.kotlin.samples.DataClassEmbedded import org.bson.codecs.kotlin.samples.DataClassParameterized import org.bson.codecs.kotlin.samples.DataClassWithParameterizedDataClass +import org.bson.codecs.kotlin.samples.DataClassWithSimpleValues import org.bson.conversions.Bson import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows @@ -44,11 +44,11 @@ class DataClassCodecProviderTest { @Test fun shouldReturnDataClassCodecForDataClass() { val provider = DataClassCodecProvider() - val codec = provider.get(DataClass::class.java, Bson.DEFAULT_CODEC_REGISTRY) + val codec = provider.get(DataClassWithSimpleValues::class.java, Bson.DEFAULT_CODEC_REGISTRY) assertNotNull(codec) assertTrue { codec is DataClassCodec } - assertEquals(DataClass::class.java, codec.encoderClass) + assertEquals(DataClassWithSimpleValues::class.java, codec.encoderClass) } @Test @@ -78,10 +78,10 @@ class DataClassCodecProviderTest { @Test fun shouldReturnDataClassCodecUsingDefaultRegistry() { - val codec = MongoClientSettings.getDefaultCodecRegistry().get(DataClass::class.java) + val codec = MongoClientSettings.getDefaultCodecRegistry().get(DataClassWithSimpleValues::class.java) assertNotNull(codec) assertTrue { codec is DataClassCodec } - assertEquals(DataClass::class.java, codec.encoderClass) + assertEquals(DataClassWithSimpleValues::class.java, codec.encoderClass) } } diff --git a/bson-kotlin/src/test/kotlin/org/bson/codecs/kotlin/DataClassCodecTest.kt b/bson-kotlin/src/test/kotlin/org/bson/codecs/kotlin/DataClassCodecTest.kt index cbc65e12e9a..c99f9ad5f5f 100644 --- a/bson-kotlin/src/test/kotlin/org/bson/codecs/kotlin/DataClassCodecTest.kt +++ b/bson-kotlin/src/test/kotlin/org/bson/codecs/kotlin/DataClassCodecTest.kt @@ -19,77 +19,150 @@ import kotlin.test.assertEquals import org.bson.BsonDocument import org.bson.BsonDocumentReader import org.bson.BsonDocumentWriter -import org.bson.codecs.Codec import org.bson.codecs.DecoderContext import org.bson.codecs.EncoderContext import org.bson.codecs.configuration.CodecConfigurationException import org.bson.codecs.configuration.CodecRegistries.fromProviders -import org.bson.codecs.kotlin.DataClassCodec.Companion.createDataClassCodec -import org.bson.codecs.kotlin.samples.DataClass import org.bson.codecs.kotlin.samples.DataClassEmbedded import org.bson.codecs.kotlin.samples.DataClassListOfDataClasses import org.bson.codecs.kotlin.samples.DataClassListOfListOfDataClasses +import org.bson.codecs.kotlin.samples.DataClassListOfSealed import org.bson.codecs.kotlin.samples.DataClassMapOfDataClasses import org.bson.codecs.kotlin.samples.DataClassMapOfListOfDataClasses import org.bson.codecs.kotlin.samples.DataClassNestedParameterizedTypes import org.bson.codecs.kotlin.samples.DataClassParameterized +import org.bson.codecs.kotlin.samples.DataClassSealedA +import org.bson.codecs.kotlin.samples.DataClassSealedB +import org.bson.codecs.kotlin.samples.DataClassSealedC import org.bson.codecs.kotlin.samples.DataClassSelfReferential -import org.bson.codecs.kotlin.samples.DataClassWithAnnotations +import org.bson.codecs.kotlin.samples.DataClassWithBooleanMapKey import org.bson.codecs.kotlin.samples.DataClassWithBsonConstructor import org.bson.codecs.kotlin.samples.DataClassWithBsonDiscriminator import org.bson.codecs.kotlin.samples.DataClassWithBsonExtraElements +import org.bson.codecs.kotlin.samples.DataClassWithBsonId import org.bson.codecs.kotlin.samples.DataClassWithBsonIgnore +import org.bson.codecs.kotlin.samples.DataClassWithBsonProperty +import org.bson.codecs.kotlin.samples.DataClassWithCollections +import org.bson.codecs.kotlin.samples.DataClassWithDataClassMapKey import org.bson.codecs.kotlin.samples.DataClassWithDefaults import org.bson.codecs.kotlin.samples.DataClassWithEmbedded +import org.bson.codecs.kotlin.samples.DataClassWithEnum +import org.bson.codecs.kotlin.samples.DataClassWithEnumMapKey import org.bson.codecs.kotlin.samples.DataClassWithFailingInit -import org.bson.codecs.kotlin.samples.DataClassWithInvalidRepresentation +import org.bson.codecs.kotlin.samples.DataClassWithInvalidBsonRepresentation import org.bson.codecs.kotlin.samples.DataClassWithMutableList import org.bson.codecs.kotlin.samples.DataClassWithMutableMap import org.bson.codecs.kotlin.samples.DataClassWithMutableSet import org.bson.codecs.kotlin.samples.DataClassWithNestedParameterized import org.bson.codecs.kotlin.samples.DataClassWithNestedParameterizedDataClass import org.bson.codecs.kotlin.samples.DataClassWithNulls +import org.bson.codecs.kotlin.samples.DataClassWithObjectIdAndBsonDocument import org.bson.codecs.kotlin.samples.DataClassWithPair import org.bson.codecs.kotlin.samples.DataClassWithParameterizedDataClass import org.bson.codecs.kotlin.samples.DataClassWithSequence +import org.bson.codecs.kotlin.samples.DataClassWithSimpleValues import org.bson.codecs.kotlin.samples.DataClassWithTriple +import org.bson.codecs.kotlin.samples.Key import org.bson.conversions.Bson +import org.bson.types.ObjectId import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows class DataClassCodecTest { + private val numberLong = "\$numberLong" + private val emptyDocument = "{}" @Test - fun testDataClass() { - val expected = """{"_id": "myId", "name": "Felix", "age": 14, "hobbies": ["rugby", "weights"]}""" - val dataClass = DataClass("myId", "Felix", 14, listOf("rugby", "weights")) + fun testDataClassWithSimpleValues() { + val expected = + """{"char": "c", "byte": 0, "short": 1, "int": 22, "long": {"$numberLong": "42"}, "float": 4.0, + | "double": 4.2, "boolean": true, "string": "String"}""" + .trimMargin() + val dataClass = DataClassWithSimpleValues('c', 0, 1, 22, 42L, 4.0f, 4.2, true, "String") assertRoundTrips(expected, dataClass) } @Test - fun testDataClassWithEmbedded() { - val expected = """{"_id": "myId", "embedded": {"name": "embedded1"}}""" - val dataClass = DataClassWithEmbedded("myId", DataClassEmbedded("embedded1")) + fun testDataClassWithComplexTypes() { + val expected = + """{ + | "listSimple": ["a", "b", "c", "d"], + | "listList": [["a", "b"], [], ["c", "d"]], + | "listMap": [{"a": 1, "b": 2}, {}, {"c": 3, "d": 4}], + | "mapSimple": {"a": 1, "b": 2, "c": 3, "d": 4}, + | "mapList": {"a": ["a", "b"], "b": [], "c": ["c", "d"]}, + | "mapMap" : {"a": {"a": 1, "b": 2}, "b": {}, "c": {"c": 3, "d": 4}} + |}""" + .trimMargin() + + val dataClass = + DataClassWithCollections( + listOf("a", "b", "c", "d"), + listOf(listOf("a", "b"), emptyList(), listOf("c", "d")), + listOf(mapOf("a" to 1, "b" to 2), emptyMap(), mapOf("c" to 3, "d" to 4)), + mapOf("a" to 1, "b" to 2, "c" to 3, "d" to 4), + mapOf("a" to listOf("a", "b"), "b" to emptyList(), "c" to listOf("c", "d")), + mapOf("a" to mapOf("a" to 1, "b" to 2), "b" to emptyMap(), "c" to mapOf("c" to 3, "d" to 4))) assertRoundTrips(expected, dataClass) } @Test - fun testDataClassWithAnnotations() { - val oid = "\$oid" + fun testDataClassWithDefaults() { + val expectedDefault = + """{ + | "boolean": false, + | "string": "String", + | "listSimple": ["a", "b", "c"] + |}""" + .trimMargin() + + val defaultDataClass = DataClassWithDefaults() + assertRoundTrips(expectedDefault, defaultDataClass) + } + + @Test + fun testDataClassWithNulls() { + val dataClass = DataClassWithNulls(null, null, null) + assertRoundTrips(emptyDocument, dataClass) + } + + @Test + fun testDataClassSelfReferential() { val expected = - """{"_id": {"$oid": "111111111111111111111111"}, - |"nom": "Felix", "age": 14, "hobbies": ["rugby", "weights"]}""" + """{"name": "tree", + | "left": {"name": "L", "left": {"name": "LL"}, "right": {"name": "LR"}}, + | "right": {"name": "R", + | "left": {"name": "RL", + | "left": {"name": "RLL"}, + | "right": {"name": "RLR"}}, + | "right": {"name": "RR"}} + |}""" .trimMargin() - val dataClass = DataClassWithAnnotations("111111111111111111111111", "Felix", 14, listOf("rugby", "weights")) + val dataClass = + DataClassSelfReferential( + "tree", + DataClassSelfReferential("L", DataClassSelfReferential("LL"), DataClassSelfReferential("LR")), + DataClassSelfReferential( + "R", + DataClassSelfReferential("RL", DataClassSelfReferential("RLL"), DataClassSelfReferential("RLR")), + DataClassSelfReferential("RR"))) + + assertRoundTrips(expected, dataClass) + } + + @Test + fun testDataClassWithEmbedded() { + val expected = """{"id": "myId", "embedded": {"name": "embedded1"}}""" + val dataClass = DataClassWithEmbedded("myId", DataClassEmbedded("embedded1")) assertRoundTrips(expected, dataClass) } @Test fun testDataClassListOfDataClasses() { - val expected = """{"_id": "myId", "nested": [{"name": "embedded1"}, {"name": "embedded2"}]}""" + val expected = """{"id": "myId", "nested": [{"name": "embedded1"}, {"name": "embedded2"}]}""" val dataClass = DataClassListOfDataClasses("myId", listOf(DataClassEmbedded("embedded1"), DataClassEmbedded("embedded2"))) @@ -98,7 +171,7 @@ class DataClassCodecTest { @Test fun testDataClassListOfListOfDataClasses() { - val expected = """{"_id": "myId", "nested": [[{"name": "embedded1"}], [{"name": "embedded2"}]]}""" + val expected = """{"id": "myId", "nested": [[{"name": "embedded1"}], [{"name": "embedded2"}]]}""" val dataClass = DataClassListOfListOfDataClasses( "myId", listOf(listOf(DataClassEmbedded("embedded1")), listOf(DataClassEmbedded("embedded2")))) @@ -108,8 +181,7 @@ class DataClassCodecTest { @Test fun testDataClassMapOfDataClasses() { - val expected = - """{"_id": "myId", "nested": {"first": {"name": "embedded1"}, "second": {"name": "embedded2"}}}""" + val expected = """{"id": "myId", "nested": {"first": {"name": "embedded1"}, "second": {"name": "embedded2"}}}""" val dataClass = DataClassMapOfDataClasses( "myId", mapOf("first" to DataClassEmbedded("embedded1"), "second" to DataClassEmbedded("embedded2"))) @@ -120,7 +192,7 @@ class DataClassCodecTest { @Test fun testDataClassMapOfListOfDataClasses() { val expected = - """{"_id": "myId", "nested": {"first": [{"name": "embedded1"}], "second": [{"name": "embedded2"}]}}""" + """{"id": "myId", "nested": {"first": [{"name": "embedded1"}], "second": [{"name": "embedded2"}]}}""" val dataClass = DataClassMapOfListOfDataClasses( "myId", @@ -131,62 +203,10 @@ class DataClassCodecTest { assertRoundTrips(expected, dataClass) } - @Test - fun testDataClassWithNulls() { - val expected = """{"name": "Felix", "hobbies": ["rugby", "weights"]}""" - val dataClass = DataClassWithNulls(null, "Felix", null, listOf("rugby", "weights")) - - assertRoundTrips(expected, dataClass) - } - - @Test - fun testDataClassWithDefaults() { - val expected = """{"_id": "myId", "name": "Arthur Dent", "age": 42, "hobbies": ["computers", "databases"]}""" - val dataClass = DataClassWithDefaults("myId", "Arthur Dent") - - assertRoundTrips(expected, dataClass) - } - - @Test - fun testHandlesExtraData() { - val expected = - """{"_id": "myId", "extra1": "extraField", "name": "Felix", "extra2": "extraField", "age": 14, - | "extra3": "extraField", "hobbies": ["rugby", "weights"], "extra4": "extraField"}""" - .trimMargin() - val dataClass = DataClass("myId", "Felix", 14, listOf("rugby", "weights")) - - assertDecodesTo(dataClass, BsonDocument.parse(expected)) - } - - @Test - fun testDataClassSelfReferential() { - val expected = - """{"_id": "myId", "name": "tree", - | "left": {"name": "L", "left": {"name": "LL"}, "right": {"name": "LR"}}, - | "right": {"name": "R", - | "left": {"name": "RL", - | "left": {"name": "RLL"}, - | "right": {"name": "RLR"}}, - | "right": {"name": "RR"}} - |}""" - .trimMargin() - val dataClass = - DataClassSelfReferential( - "tree", - DataClassSelfReferential("L", DataClassSelfReferential("LL"), DataClassSelfReferential("LR")), - DataClassSelfReferential( - "R", - DataClassSelfReferential("RL", DataClassSelfReferential("RLL"), DataClassSelfReferential("RLR")), - DataClassSelfReferential("RR")), - id = "myId") - - assertRoundTrips(expected, dataClass) - } - @Test fun testDataClassWithParameterizedDataClass() { val expected = - """{"_id": "myId", + """{"id": "myId", | "parameterizedDataClass": {"number": 2.0, "string": "myString", | "parameterizedList": [{"name": "embedded1"}]} |}""" @@ -201,7 +221,7 @@ class DataClassCodecTest { @Test fun testDataClassWithNestedParameterizedDataClass() { val expected = - """{"_id": "myId", + """{"id": "myId", |"nestedParameterized": { | "parameterizedDataClass": | {"number": 4.2, "string": "myString", "parameterizedList": [{"name": "embedded1"}]}, @@ -236,7 +256,6 @@ class DataClassCodecTest { @Test fun testDataClassNestedParameterizedTypes() { - val expected = """{ |"triple": { @@ -283,64 +302,177 @@ class DataClassCodecTest { } @Test - fun testDataFailures() { - assertThrows("Missing data") { - val codec: Codec = createDataClassCodec(DataClass::class, registry()) - codec.decode(BsonDocumentReader(BsonDocument()), DecoderContext.builder().build()) + fun testDataClassWithEnum() { + val expected = """{"value": "A"}""" + + val dataClass = DataClassWithEnum(Key.A) + assertRoundTrips(expected, dataClass) + } + + @Test + fun testDataClassWithEnumKeyMap() { + assertThrows("Unsupported map key") { + DataClassCodec.create(DataClassWithEnumMapKey::class, registry()) } + } + @Test + fun testDataClassWithSequence() { assertThrows("Unsupported type Sequence") { - createDataClassCodec(DataClassWithSequence::class, registry()) + DataClassCodec.create(DataClassWithSequence::class, registry()) + } + } + + @Test + fun testDataClassWithBooleanKeyMap() { + assertThrows("Unsupported Map key type") { + DataClassCodec.create(DataClassWithBooleanMapKey::class, registry()) + } + } + + @Test + fun testDataClassWithDataClassKeyMap() { + assertThrows("Unsupported Map key type") { + DataClassCodec.create(DataClassWithDataClassMapKey::class, registry()) + } + } + + @Test + fun testDataClassEmbeddedWithExtraData() { + val expected = + """{ + | "extraA": "extraA", + | "name": "NAME", + | "extraB": "extraB" + |}""" + .trimMargin() + + val dataClass = DataClassEmbedded("NAME") + assertDecodesTo(BsonDocument.parse(expected), dataClass) + } + + @Test + fun testDataClassWithObjectIdAndBsonDocument() { + val subDocument = + """{ + | "_id": 1, + | "arrayEmpty": [], + | "arraySimple": [{"${'$'}numberInt": "1"}, {"${'$'}numberInt": "2"}, {"${'$'}numberInt": "3"}], + | "arrayComplex": [{"a": {"${'$'}numberInt": "1"}}, {"a": {"${'$'}numberInt": "2"}}], + | "arrayMixedTypes": [{"${'$'}numberInt": "1"}, {"${'$'}numberInt": "2"}, true, + | [{"${'$'}numberInt": "1"}, {"${'$'}numberInt": "2"}, {"${'$'}numberInt": "3"}], + | {"a": {"${'$'}numberInt": "2"}}], + | "arrayComplexMixedTypes": [{"a": {"${'$'}numberInt": "1"}}, {"a": "a"}], + | "binary": {"${'$'}binary": {"base64": "S2Fma2Egcm9ja3Mh", "subType": "00"}}, + | "boolean": true, + | "code": {"${'$'}code": "int i = 0;"}, + | "codeWithScope": {"${'$'}code": "int x = y", "${'$'}scope": {"y": {"${'$'}numberInt": "1"}}}, + | "dateTime": {"${'$'}date": {"${'$'}numberLong": "1577836801000"}}, + | "decimal128": {"${'$'}numberDecimal": "1.0"}, + | "documentEmpty": {}, + | "document": {"a": {"${'$'}numberInt": "1"}}, + | "double": {"${'$'}numberDouble": "62.0"}, + | "int32": {"${'$'}numberInt": "42"}, + | "int64": {"${'$'}numberLong": "52"}, + | "maxKey": {"${'$'}maxKey": 1}, + | "minKey": {"${'$'}minKey": 1}, + | "null": null, + | "objectId": {"${'$'}oid": "5f3d1bbde0ca4d2829c91e1d"}, + | "regex": {"${'$'}regularExpression": {"pattern": "^test.*regex.*xyz$", "options": "i"}}, + | "string": "the fox ...", + | "symbol": {"${'$'}symbol": "ruby stuff"}, + | "timestamp": {"${'$'}timestamp": {"t": 305419896, "i": 5}}, + | "undefined": {"${'$'}undefined": true} + | }""" + .trimMargin() + val expected = """{"objectId": {"${'$'}oid": "111111111111111111111111"}, "bsonDocument": $subDocument}""" + + val dataClass = + DataClassWithObjectIdAndBsonDocument(ObjectId("111111111111111111111111"), BsonDocument.parse(subDocument)) + assertRoundTrips(expected, dataClass) + } + + @Test + fun testDataClassSealed() { + val dataClassA = DataClassSealedA("string") + val dataClassB = DataClassSealedB(1) + val dataClassC = DataClassSealedC("String") + + val expectedDataClassSealedA = """{"a": "string"}""" + assertRoundTrips(expectedDataClassSealedA, dataClassA) + + val expectedDataClassSealedB = """{"b": 1}""" + assertRoundTrips(expectedDataClassSealedB, dataClassB) + + val expectedDataClassSealedC = """{"c": "String"}""" + assertRoundTrips(expectedDataClassSealedC, dataClassC) + + assertThrows("No Codec for DataClassSealed") { + DataClassCodec.create(DataClassListOfSealed::class, registry()) + } + } + + @Test + fun testDataFailures() { + assertThrows("Missing data") { + val codec = DataClassCodec.create(DataClassWithSimpleValues::class, registry()) + codec?.decode(BsonDocumentReader(BsonDocument()), DecoderContext.builder().build()) } assertThrows("Invalid types") { val data = - BsonDocument.parse("""{"_id": "myId", "name": "Imogen", "age": "16", "hobbies": ["rugby", "gym"]}""") - val codec: Codec = createDataClassCodec(DataClass::class, registry()) - codec.decode(BsonDocumentReader(data), DecoderContext.builder().build()) + BsonDocument.parse( + """{"char": 123, "short": "2", "int": 22, "long": "ok", "float": true, "double": false, + | "boolean": "true", "string": 99}""" + .trimMargin()) + val codec = DataClassCodec.create(DataClassWithSimpleValues::class, registry()) + codec?.decode(BsonDocumentReader(data), DecoderContext.builder().build()) } assertThrows("Invalid complex types") { val data = BsonDocument.parse("""{"_id": "myId", "embedded": 123}""") - val codec: Codec = createDataClassCodec(DataClassWithEmbedded::class, registry()) - codec.decode(BsonDocumentReader(data), DecoderContext.builder().build()) + val codec = DataClassCodec.create(DataClassWithEmbedded::class, registry()) + codec?.decode(BsonDocumentReader(data), DecoderContext.builder().build()) } assertThrows("Failing init") { - val data = BsonDocument.parse("""{"_id": "myId"}""") - val codec: DataClassCodec = - createDataClassCodec(DataClassWithFailingInit::class, registry()) - codec.decode(BsonDocumentReader(data), DecoderContext.builder().build()) + val data = BsonDocument.parse("""{"id": "myId"}""") + val codec = DataClassCodec.create(DataClassWithFailingInit::class, registry()) + codec?.decode(BsonDocumentReader(data), DecoderContext.builder().build()) } } + @Test + fun testSupportedAnnotations() { + assertRoundTrips("""{"_id": "a"}""", DataClassWithBsonId("a")) + assertRoundTrips("""{"_id": "a"}""", DataClassWithBsonProperty("a")) + } + @Test fun testInvalidAnnotations() { assertThrows { - createDataClassCodec(DataClassWithBsonDiscriminator::class, Bson.DEFAULT_CODEC_REGISTRY) - } - assertThrows { - createDataClassCodec(DataClassWithBsonConstructor::class, Bson.DEFAULT_CODEC_REGISTRY) + DataClassCodec.create(DataClassWithBsonDiscriminator::class, registry()) } assertThrows { - createDataClassCodec(DataClassWithBsonIgnore::class, Bson.DEFAULT_CODEC_REGISTRY) + DataClassCodec.create(DataClassWithBsonConstructor::class, registry()) } + assertThrows { DataClassCodec.create(DataClassWithBsonIgnore::class, registry()) } assertThrows { - createDataClassCodec(DataClassWithBsonExtraElements::class, Bson.DEFAULT_CODEC_REGISTRY) + DataClassCodec.create(DataClassWithBsonExtraElements::class, registry()) } assertThrows { - createDataClassCodec(DataClassWithInvalidRepresentation::class, Bson.DEFAULT_CODEC_REGISTRY) + DataClassCodec.create(DataClassWithInvalidBsonRepresentation::class, registry()) } } private fun assertRoundTrips(expected: String, value: T) { - assertDecodesTo(value, assertEncodesTo(expected, value)) + assertDecodesTo(assertEncodesTo(expected, value), value) } @Suppress("UNCHECKED_CAST") private fun assertEncodesTo(json: String, value: T): BsonDocument { val expected = BsonDocument.parse(json) - val codec: DataClassCodec = createDataClassCodec(value::class, registry()) as DataClassCodec + val codec: DataClassCodec = DataClassCodec.create(value::class, registry()) as DataClassCodec val document = BsonDocument() val writer = BsonDocumentWriter(document) @@ -353,9 +485,9 @@ class DataClassCodecTest { } @Suppress("UNCHECKED_CAST") - private fun assertDecodesTo(expected: T, actual: BsonDocument) { - val codec: DataClassCodec = createDataClassCodec(expected::class, registry()) as DataClassCodec - val decoded: T = codec.decode(BsonDocumentReader(actual), DecoderContext.builder().build()) + private fun assertDecodesTo(value: BsonDocument, expected: T) { + val codec: DataClassCodec = DataClassCodec.create(expected::class, registry()) as DataClassCodec + val decoded: T = codec.decode(BsonDocumentReader(value), DecoderContext.builder().build()) assertEquals(expected, decoded) } diff --git a/bson-kotlin/src/test/kotlin/org/bson/codecs/kotlin/samples/DataClasses.kt b/bson-kotlin/src/test/kotlin/org/bson/codecs/kotlin/samples/DataClasses.kt index a11f069be59..eaa87ca603b 100644 --- a/bson-kotlin/src/test/kotlin/org/bson/codecs/kotlin/samples/DataClasses.kt +++ b/bson-kotlin/src/test/kotlin/org/bson/codecs/kotlin/samples/DataClasses.kt @@ -15,9 +15,9 @@ */ package org.bson.codecs.kotlin.samples +import org.bson.BsonDocument import org.bson.BsonMaxKey import org.bson.BsonType -import org.bson.Document import org.bson.codecs.pojo.annotations.BsonCreator import org.bson.codecs.pojo.annotations.BsonDiscriminator import org.bson.codecs.pojo.annotations.BsonExtraElements @@ -25,54 +25,64 @@ import org.bson.codecs.pojo.annotations.BsonId import org.bson.codecs.pojo.annotations.BsonIgnore import org.bson.codecs.pojo.annotations.BsonProperty import org.bson.codecs.pojo.annotations.BsonRepresentation +import org.bson.types.ObjectId + +data class DataClassWithSimpleValues( + val char: Char, + val byte: Byte, + val short: Short, + val int: Int, + val long: Long, + val float: Float, + val double: Double, + val boolean: Boolean, + val string: String +) -@Suppress("PropertyName", "ConstructorParameterNaming") -data class DataClass(val _id: String, val name: String, val age: Int, val hobbies: List) - -data class DataClassWithAnnotations( - @BsonRepresentation(BsonType.OBJECT_ID) @BsonId val oid: String, - @BsonProperty("nom") val name: String, - val age: Int, - val hobbies: List +data class DataClassWithCollections( + val listSimple: List, + val listList: List>, + val listMap: List>, + val mapSimple: Map, + val mapList: Map>, + val mapMap: Map> ) -data class DataClassEmbedded(val name: String) +data class DataClassWithDefaults( + val boolean: Boolean = false, + val string: String = "String", + val listSimple: List = listOf("a", "b", "c") +) -data class DataClassWithEmbedded(@BsonId val id: String, val embedded: DataClassEmbedded) +data class DataClassWithNulls(val boolean: Boolean?, val string: String?, val listSimple: List?) -data class DataClassListOfDataClasses(@BsonId val id: String, val nested: List) +data class DataClassSelfReferential( + val name: String, + val left: DataClassSelfReferential? = null, + val right: DataClassSelfReferential? = null +) -data class DataClassListOfListOfDataClasses(@BsonId val id: String, val nested: List>) +data class DataClassEmbedded(val name: String) -data class DataClassMapOfDataClasses(@BsonId val id: String, val nested: Map) +data class DataClassWithEmbedded(val id: String, val embedded: DataClassEmbedded) -data class DataClassMapOfListOfDataClasses(@BsonId val id: String, val nested: Map>) +data class DataClassListOfDataClasses(val id: String, val nested: List) -data class DataClassWithNulls(@BsonId val id: String?, val name: String, val age: Int?, val hobbies: List) +data class DataClassListOfListOfDataClasses(val id: String, val nested: List>) -data class DataClassWithDefaults( - @BsonId val id: String, - val name: String, - val age: Int = 42, - val hobbies: List = listOf("computers", "databases") -) +data class DataClassMapOfDataClasses(val id: String, val nested: Map) -data class DataClassSelfReferential( - val name: String, - val left: DataClassSelfReferential? = null, - val right: DataClassSelfReferential? = null, - @BsonId val id: String? = null -) +data class DataClassMapOfListOfDataClasses(val id: String, val nested: Map>) data class DataClassWithParameterizedDataClass( - @BsonId val id: String, + val id: String, val parameterizedDataClass: DataClassParameterized ) data class DataClassParameterized(val number: N, val string: String, val parameterizedList: List) data class DataClassWithNestedParameterizedDataClass( - @BsonId val id: String, + val id: String, val nestedParameterized: DataClassWithNestedParameterized ) @@ -99,19 +109,50 @@ data class DataClassWithMutableSet(val value: MutableSet) data class DataClassWithMutableMap(val value: MutableMap) +data class DataClassWithBooleanMapKey(val map: Map) + +enum class Key { + A, + B +} + +data class DataClassWithEnum(val value: Key) + +data class DataClassWithEnumMapKey(val map: Map) + +data class DataClassKey(val value: String) + +data class DataClassWithDataClassMapKey(val map: Map) + +data class DataClassWithObjectIdAndBsonDocument(val objectId: ObjectId, val bsonDocument: BsonDocument) + +sealed class DataClassSealed + +data class DataClassSealedA(val a: String) : DataClassSealed() + +data class DataClassSealedB(val b: Int) : DataClassSealed() + +data class DataClassSealedC(val c: String) : DataClassSealed() + +data class DataClassListOfSealed(val items: List) + +data class DataClassWithBsonId(@BsonId val id: String) + +data class DataClassWithBsonProperty(@BsonProperty("_id") val id: String) + @BsonDiscriminator data class DataClassWithBsonDiscriminator(val id: String) data class DataClassWithBsonIgnore(val id: String, @BsonIgnore val ignored: String) -data class DataClassWithBsonExtraElements(val id: String, @BsonExtraElements val extraElements: Document) +data class DataClassWithBsonExtraElements(val id: String, @BsonExtraElements val extraElements: Map) data class DataClassWithBsonConstructor(val id: String, val count: Int) { @BsonCreator constructor(id: String) : this(id, -1) } -data class DataClassWithInvalidRepresentation(@BsonRepresentation(BsonType.STRING) val id: BsonMaxKey) +data class DataClassWithInvalidBsonRepresentation(@BsonRepresentation(BsonType.STRING) val id: BsonMaxKey) -data class DataClassWithFailingInit(@BsonId val id: String) { +data class DataClassWithFailingInit(val id: String) { init { require(false) } From e62acfd23860af3faad04ed57f5ab97d0f571bc7 Mon Sep 17 00:00:00 2001 From: Ross Lawley Date: Wed, 22 Mar 2023 12:30:51 +0000 Subject: [PATCH 11/11] Initial driver-kotlin-coroutine implementation Wraps the driver-reactivestreams to provide a kotlin coroutine API In general a faithful wrapping of the reactivestreams driver but with coroutine support instead of using Publishers. Uses reified overloads to provide synaptic sugar instead of passing resultClass. JAVA-4870 --- .evergreen/run-kotlin-tests.sh | 2 +- config/detekt/baseline.xml | 3 + config/spotbugs/exclude.xml | 21 + driver-kotlin-coroutine/build.gradle.kts | 189 ++ .../kotlin/client/coroutine/CrudTest.kt | 49 + .../syncadapter/SyncAggregateIterable.kt | 73 + .../syncadapter/SyncChangeStreamIterable.kt | 63 + .../syncadapter/SyncClientSession.kt | 89 + .../syncadapter/SyncDistinctIterable.kt | 35 + .../coroutine/syncadapter/SyncFindIterable.kt | 95 + .../SyncListCollectionsIterable.kt | 36 + .../syncadapter/SyncListDatabasesIterable.kt | 44 + .../syncadapter/SyncListIndexesIterable.kt | 31 + .../syncadapter/SyncMapReduceIterable.kt | 64 + .../SyncMongoChangeStreamCursor.kt | 25 + .../coroutine/syncadapter/SyncMongoClient.kt | 90 + .../syncadapter/SyncMongoCollection.kt | 605 +++++++ .../coroutine/syncadapter/SyncMongoCursor.kt | 46 + .../syncadapter/SyncMongoDatabase.kt | 212 +++ .../syncadapter/SyncMongoIterable.kt | 49 + .../kotlin/client/coroutine/AggregateFlow.kt | 203 +++ .../client/coroutine/ChangeStreamFlow.kt | 179 ++ .../kotlin/client/coroutine/ClientSession.kt | 226 +++ .../kotlin/client/coroutine/DistinctFlow.kt | 91 + .../kotlin/client/coroutine/FindFlow.kt | 297 ++++ .../client/coroutine/ListCollectionsFlow.kt | 79 + .../client/coroutine/ListDatabasesFlow.kt | 98 ++ .../client/coroutine/ListIndexesFlow.kt | 70 + .../kotlin/client/coroutine/MapReduceFlow.kt | 214 +++ .../kotlin/client/coroutine/MongoClient.kt | 287 ++++ .../client/coroutine/MongoCollection.kt | 1526 +++++++++++++++++ .../kotlin/client/coroutine/MongoDatabase.kt | 548 ++++++ .../client/coroutine/AggregateFlowTest.kt | 111 ++ .../client/coroutine/ChangeStreamFlowTest.kt | 92 + .../client/coroutine/ClientSessionTest.kt | 80 + .../client/coroutine/DistinctFlowTest.kt | 69 + .../client/coroutine/ExtensionMethodsTest.kt | 56 + .../kotlin/client/coroutine/FindFlowTest.kt | 134 ++ .../coroutine/ListCollectionsFlowTest.kt | 67 + .../client/coroutine/ListDatabasesFlowTest.kt | 70 + .../client/coroutine/ListIndexesFlowTest.kt | 62 + .../client/coroutine/MapReduceFlowTest.kt | 101 ++ .../kotlin/client/coroutine/MockitoHelper.kt | 53 + .../client/coroutine/MongoClientTest.kt | 169 ++ .../client/coroutine/MongoCollectionTest.kt | 1011 +++++++++++ .../client/coroutine/MongoDatabaseTest.kt | 408 +++++ settings.gradle | 1 + 47 files changed, 8122 insertions(+), 1 deletion(-) create mode 100644 driver-kotlin-coroutine/build.gradle.kts create mode 100644 driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/CrudTest.kt create mode 100644 driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncAggregateIterable.kt create mode 100644 driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncChangeStreamIterable.kt create mode 100644 driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncClientSession.kt create mode 100644 driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncDistinctIterable.kt create mode 100644 driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncFindIterable.kt create mode 100644 driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncListCollectionsIterable.kt create mode 100644 driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncListDatabasesIterable.kt create mode 100644 driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncListIndexesIterable.kt create mode 100644 driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncMapReduceIterable.kt create mode 100644 driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncMongoChangeStreamCursor.kt create mode 100644 driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncMongoClient.kt create mode 100644 driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncMongoCollection.kt create mode 100644 driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncMongoCursor.kt create mode 100644 driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncMongoDatabase.kt create mode 100644 driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncMongoIterable.kt create mode 100644 driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/AggregateFlow.kt create mode 100644 driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/ChangeStreamFlow.kt create mode 100644 driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/ClientSession.kt create mode 100644 driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/DistinctFlow.kt create mode 100644 driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/FindFlow.kt create mode 100644 driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/ListCollectionsFlow.kt create mode 100644 driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/ListDatabasesFlow.kt create mode 100644 driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/ListIndexesFlow.kt create mode 100644 driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/MapReduceFlow.kt create mode 100644 driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/MongoClient.kt create mode 100644 driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/MongoCollection.kt create mode 100644 driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/MongoDatabase.kt create mode 100644 driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/AggregateFlowTest.kt create mode 100644 driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/ChangeStreamFlowTest.kt create mode 100644 driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/ClientSessionTest.kt create mode 100644 driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/DistinctFlowTest.kt create mode 100644 driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/ExtensionMethodsTest.kt create mode 100644 driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/FindFlowTest.kt create mode 100644 driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/ListCollectionsFlowTest.kt create mode 100644 driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/ListDatabasesFlowTest.kt create mode 100644 driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/ListIndexesFlowTest.kt create mode 100644 driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/MapReduceFlowTest.kt create mode 100644 driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/MockitoHelper.kt create mode 100644 driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/MongoClientTest.kt create mode 100644 driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/MongoCollectionTest.kt create mode 100644 driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/MongoDatabaseTest.kt 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/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..15d372d988b --- /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(@PublishedApi internal 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..0053f1f38e5 --- /dev/null +++ b/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/ChangeStreamFlow.kt @@ -0,0 +1,179 @@ +/* + * 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(@PublishedApi internal 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..1a2685b95c0 --- /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(@PublishedApi internal 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..a89187b74fd --- /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(@PublishedApi internal 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..b248af63573 --- /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(@PublishedApi internal 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..96c296104c5 --- /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(@PublishedApi internal 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..19c1fd27b0d --- /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(@PublishedApi internal 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..5af4136f5e6 --- /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(@PublishedApi internal 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..fe42be646b5 --- /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(@PublishedApi internal 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..1a8c7fe816e --- /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(@PublishedApi internal 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..8f81d1257ac --- /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(@PublishedApi internal 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'