diff --git a/driver-core/src/main/com/mongodb/internal/operation/AsyncOperations.java b/driver-core/src/main/com/mongodb/internal/operation/AsyncOperations.java index 77434bd9781..70c847668ab 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/AsyncOperations.java +++ b/driver-core/src/main/com/mongodb/internal/operation/AsyncOperations.java @@ -181,8 +181,9 @@ public AsyncReadOperation> find(final MongoN } public AsyncReadOperation> distinct(final String fieldName, final Bson filter, - final Class resultClass, final Collation collation, final BsonValue comment) { - return operations.distinct(fieldName, filter, resultClass, collation, comment); + final Class resultClass, final Collation collation, final BsonValue comment, final Bson hint, + final String hintString) { + return operations.distinct(fieldName, filter, resultClass, collation, comment, hint, hintString); } public AsyncExplainableReadOperation> aggregate( diff --git a/driver-core/src/main/com/mongodb/internal/operation/DistinctOperation.java b/driver-core/src/main/com/mongodb/internal/operation/DistinctOperation.java index ee11ee5cce1..547e5f0dfc1 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/DistinctOperation.java +++ b/driver-core/src/main/com/mongodb/internal/operation/DistinctOperation.java @@ -54,6 +54,7 @@ public class DistinctOperation implements AsyncReadOperation decoder) { this.namespace = notNull("namespace", namespace); @@ -97,6 +98,15 @@ public DistinctOperation comment(final BsonValue comment) { return this; } + public BsonValue getHint() { + return hint; + } + + public DistinctOperation hint(@Nullable final BsonValue hint) { + this.hint = hint; + return this; + } + @Override public BatchCursor execute(final ReadBinding binding) { return executeRetryableRead(binding, namespace.getDatabaseName(), getCommandCreator(), createCommandDecoder(), @@ -124,6 +134,7 @@ private CommandCreator getCommandCreator() { commandDocument.put("collation", collation.asDocument()); } putIfNotNull(commandDocument, "comment", comment); + putIfNotNull(commandDocument, "hint", hint); return commandDocument; }; } diff --git a/driver-core/src/main/com/mongodb/internal/operation/Operations.java b/driver-core/src/main/com/mongodb/internal/operation/Operations.java index 5ec696b61ce..d00c5a446c9 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/Operations.java +++ b/driver-core/src/main/com/mongodb/internal/operation/Operations.java @@ -217,13 +217,20 @@ private FindOperation createFindOperation(final MongoNamespac } DistinctOperation distinct(final String fieldName, @Nullable final Bson filter, final Class resultClass, - final Collation collation, final BsonValue comment) { - return new DistinctOperation<>(assertNotNull(namespace), + final Collation collation, final BsonValue comment, @Nullable final Bson hint, @Nullable final String hintString) { + DistinctOperation operation = new DistinctOperation<>(assertNotNull(namespace), fieldName, codecRegistry.get(resultClass)) .retryReads(retryReads) .filter(filter == null ? null : filter.toBsonDocument(documentClass, codecRegistry)) .collation(collation) .comment(comment); + + if (hint != null) { + operation.hint(toBsonDocument(hint)); + } else if (hintString != null) { + operation.hint(new BsonString(hintString)); + } + return operation; } AggregateOperation aggregate(final List pipeline, final Class resultClass, diff --git a/driver-core/src/main/com/mongodb/internal/operation/SyncOperations.java b/driver-core/src/main/com/mongodb/internal/operation/SyncOperations.java index 73a83310d65..952a35fe7fe 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/SyncOperations.java +++ b/driver-core/src/main/com/mongodb/internal/operation/SyncOperations.java @@ -165,8 +165,9 @@ public ReadOperation> find(final MongoNamespace f public ReadOperation> distinct(final String fieldName, final Bson filter, final Class resultClass, - final Collation collation, final BsonValue comment) { - return operations.distinct(fieldName, filter, resultClass, collation, comment); + final Collation collation, final BsonValue comment, + final Bson hint, final String hintString) { + return operations.distinct(fieldName, filter, resultClass, collation, comment, hint, hintString); } public ExplainableReadOperation> aggregate(final List pipeline, diff --git a/driver-core/src/test/resources/unified-test-format/crud/distinct-hint.json b/driver-core/src/test/resources/unified-test-format/crud/distinct-hint.json new file mode 100644 index 00000000000..2a6869cbe08 --- /dev/null +++ b/driver-core/src/test/resources/unified-test-format/crud/distinct-hint.json @@ -0,0 +1,139 @@ +{ + "description": "distinct-hint", + "schemaVersion": "1.0", + "runOnRequirements": [ + { + "minServerVersion": "7.1.0" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "distinct-hint-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "distinct-hint-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "tests": [ + { + "description": "distinct with hint string", + "operations": [ + { + "name": "distinct", + "object": "collection0", + "arguments": { + "fieldName": "x", + "filter": { + "_id": 1 + }, + "hint": "_id_" + }, + "expectResult": [ + 11 + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "distinct": "coll0", + "key": "x", + "query": { + "_id": 1 + }, + "hint": "_id_" + }, + "commandName": "distinct", + "databaseName": "distinct-hint-tests" + } + } + ] + } + ] + }, + { + "description": "distinct with hint document", + "operations": [ + { + "name": "distinct", + "object": "collection0", + "arguments": { + "fieldName": "x", + "filter": { + "_id": 1 + }, + "hint": { + "_id": 1 + } + }, + "expectResult": [ + 11 + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "distinct": "coll0", + "key": "x", + "query": { + "_id": 1 + }, + "hint": { + "_id": 1 + } + }, + "commandName": "distinct", + "databaseName": "distinct-hint-tests" + } + } + ] + } + ] + } + ] +} 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 index 0fdc879d610..1c5a382c8da 100644 --- 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 @@ -33,6 +33,8 @@ data class SyncDistinctIterable(val wrapped: DistinctFlow) : 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) } + override fun hint(hint: Bson?): SyncDistinctIterable = apply { wrapped.hint(hint) } + override fun hintString(hint: String?): SyncDistinctIterable = apply { wrapped.hintString(hint) } override fun timeoutMode(timeoutMode: TimeoutMode): SyncDistinctIterable = apply { wrapped.timeoutMode(timeoutMode) } 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 index c65f7f6301c..10eef030429 100644 --- 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 @@ -19,6 +19,7 @@ import com.mongodb.annotations.Alpha import com.mongodb.annotations.Reason import com.mongodb.client.cursor.TimeoutMode import com.mongodb.client.model.Collation +import com.mongodb.lang.Nullable import com.mongodb.reactivestreams.client.DistinctPublisher import java.util.concurrent.TimeUnit import kotlinx.coroutines.flow.Flow @@ -103,5 +104,23 @@ public class DistinctFlow(private val wrapped: DistinctPublisher) : */ public fun comment(comment: BsonValue?): DistinctFlow = 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(@Nullable hint: Bson?): DistinctFlow = apply { wrapped.hint(hint) } + + /** + * Sets the hint for which index to use. A null value means no hint is set. + * + * Note: If [DistinctFlow.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(@Nullable hint: String?): DistinctFlow = apply { wrapped.hintString(hint) } + public override suspend fun collect(collector: FlowCollector): Unit = wrapped.asFlow().collect(collector) } 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 index 91cf8165a3a..92e52ce39da 100644 --- 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 @@ -36,4 +36,6 @@ internal class SyncDistinctIterable(val wrapped: DistinctIterable) : 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) } + override fun hint(hint: Bson?): SyncDistinctIterable = apply { wrapped.hint(hint) } + override fun hintString(hint: String?): SyncDistinctIterable = apply { wrapped.hintString(hint) } } 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 index f785eeca7e4..98cfd590104 100644 --- 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 @@ -20,6 +20,7 @@ import com.mongodb.annotations.Reason import com.mongodb.client.DistinctIterable as JDistinctIterable import com.mongodb.client.cursor.TimeoutMode import com.mongodb.client.model.Collation +import com.mongodb.lang.Nullable import java.util.concurrent.TimeUnit import org.bson.BsonValue import org.bson.conversions.Bson @@ -99,4 +100,22 @@ public class DistinctIterable(private val wrapped: JDistinctIterable = 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(@Nullable hint: Bson?): DistinctIterable = apply { wrapped.hint(hint) } + + /** + * Sets the hint for which index to use. A null value means no hint is set. + * + * Note: If [DistinctFlow.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(@Nullable hint: String?): DistinctIterable = apply { wrapped.hintString(hint) } } diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/DistinctPublisher.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/DistinctPublisher.java index 2b695621dc3..f91b2a50722 100644 --- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/DistinctPublisher.java +++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/DistinctPublisher.java @@ -97,6 +97,24 @@ public interface DistinctPublisher extends Publisher { */ DistinctPublisher comment(@Nullable BsonValue comment); + /** + * Sets the hint for which index to use. A null value means no hint is set. + * + * @param hint the hint + * @return this + * @since 5.3 + */ + DistinctPublisher hint(@Nullable Bson hint); + + /** + * Sets the hint for which index to use. A null value means no hint is set. + * + * @param hint the name of the index which should be used for the operation + * @return this + * @since 5.3 + */ + DistinctPublisher hintString(@Nullable String hint); + /** * Sets the timeoutMode for the cursor. * diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/DistinctPublisherImpl.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/DistinctPublisherImpl.java index 84c0df234c5..30e85189e8d 100644 --- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/DistinctPublisherImpl.java +++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/DistinctPublisherImpl.java @@ -41,6 +41,8 @@ final class DistinctPublisherImpl extends BatchCursorPublisher implements private long maxTimeMS; private Collation collation; private BsonValue comment; + private Bson hint; + private String hintString; DistinctPublisherImpl( @Nullable final ClientSession clientSession, @@ -88,6 +90,18 @@ public DistinctPublisher comment(@Nullable final BsonValue comment) { return this; } + @Override + public DistinctPublisher hint(@Nullable final Bson hint) { + this.hint = hint; + return this; + } + + @Override + public DistinctPublisher hintString(@Nullable final String hint) { + this.hintString = hint; + return this; + } + @Override public DistinctPublisher timeoutMode(final TimeoutMode timeoutMode) { super.timeoutMode(timeoutMode); @@ -97,7 +111,7 @@ public DistinctPublisher timeoutMode(final TimeoutMode timeoutMode) { @Override AsyncReadOperation> asAsyncReadOperation(final int initialBatchSize) { // initialBatchSize is ignored for distinct operations. - return getOperations().distinct(fieldName, filter, getDocumentClass(), collation, comment); + return getOperations().distinct(fieldName, filter, getDocumentClass(), collation, comment, hint, hintString); } @Override diff --git a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/syncadapter/SyncDistinctIterable.java b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/syncadapter/SyncDistinctIterable.java index 7f50727621d..5b3e3c71fe1 100644 --- a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/syncadapter/SyncDistinctIterable.java +++ b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/syncadapter/SyncDistinctIterable.java @@ -71,6 +71,18 @@ public DistinctIterable comment(@Nullable final BsonValue comment) { return this; } + @Override + public DistinctIterable hint(@Nullable final Bson hint) { + wrapped.hint(hint); + return this; + } + + @Override + public DistinctIterable hintString(@Nullable final String hint) { + wrapped.hintString(hint); + return this; + } + @Override public DistinctIterable timeoutMode(final TimeoutMode timeoutMode) { wrapped.timeoutMode(timeoutMode); diff --git a/driver-scala/src/integration/scala/org/mongodb/scala/syncadapter/SyncDistinctIterable.scala b/driver-scala/src/integration/scala/org/mongodb/scala/syncadapter/SyncDistinctIterable.scala index b105ac0897c..acb8de040cc 100644 --- a/driver-scala/src/integration/scala/org/mongodb/scala/syncadapter/SyncDistinctIterable.scala +++ b/driver-scala/src/integration/scala/org/mongodb/scala/syncadapter/SyncDistinctIterable.scala @@ -61,4 +61,15 @@ case class SyncDistinctIterable[T](wrapped: DistinctObservable[T]) wrapped.comment(comment) this } + + override def hint(hint: Bson): DistinctIterable[T] = { + wrapped.hint(hint) + this + } + + override def hintString(hint: String): DistinctIterable[T] = { + wrapped.hintString(hint) + this + } + } diff --git a/driver-scala/src/main/scala/org/mongodb/scala/DistinctObservable.scala b/driver-scala/src/main/scala/org/mongodb/scala/DistinctObservable.scala index 252758f8f99..84e0905cdf9 100644 --- a/driver-scala/src/main/scala/org/mongodb/scala/DistinctObservable.scala +++ b/driver-scala/src/main/scala/org/mongodb/scala/DistinctObservable.scala @@ -111,6 +111,31 @@ case class DistinctObservable[TResult](private val wrapped: DistinctPublisher[TR this } + /** + * Sets the hint for this operation. A null value means no hint is set. + * + * @param hint the hint + * @return this + * @note if [[hint]] is set that will be used instead of any hint string. + * @since 5.3 + */ + def hint(hint: Bson): DistinctObservable[TResult] = { + wrapped.hint(hint) + this + } + + /** + * Sets the hint for this operation. A null value means no hint is set. + * + * @param hint the name of the index which should be used for the operation + * @return this + * @since 5.3 + */ + def hintString(hint: String): DistinctObservable[TResult] = { + wrapped.hintString(hint) + this + } + /** * Sets the timeoutMode for the cursor. * diff --git a/driver-sync/src/main/com/mongodb/client/DistinctIterable.java b/driver-sync/src/main/com/mongodb/client/DistinctIterable.java index 9206b7d3094..9488c7fb49e 100644 --- a/driver-sync/src/main/com/mongodb/client/DistinctIterable.java +++ b/driver-sync/src/main/com/mongodb/client/DistinctIterable.java @@ -92,6 +92,26 @@ public interface DistinctIterable extends MongoIterable { */ DistinctIterable comment(@Nullable BsonValue comment); + /** + * Sets the hint for which index to use. A null value means no hint is set. + * + * @param hint the hint + * @return this + * @since 5.3 + */ + DistinctIterable hint(@Nullable Bson hint); + + /** + * Sets the hint to apply. + * + *

Note: If {@link DistinctIterable#hint(Bson)} 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 + * @since 5.3 + */ + DistinctIterable hintString(@Nullable String hint); + /** * Sets the timeoutMode for the cursor. * diff --git a/driver-sync/src/main/com/mongodb/client/internal/DistinctIterableImpl.java b/driver-sync/src/main/com/mongodb/client/internal/DistinctIterableImpl.java index b37931c52cb..fdda7777fe5 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/DistinctIterableImpl.java +++ b/driver-sync/src/main/com/mongodb/client/internal/DistinctIterableImpl.java @@ -47,6 +47,8 @@ class DistinctIterableImpl extends MongoIterableImpl documentClass, final Class resultClass, final CodecRegistry codecRegistry, final ReadPreference readPreference, @@ -103,10 +105,21 @@ public DistinctIterable comment(@Nullable final BsonValue comment) { } @Override - public ReadOperation> asReadOperation() { - return operations.distinct(fieldName, filter, resultClass, collation, comment); + public DistinctIterable hint(@Nullable final Bson hint) { + this.hint = hint; + return this; } + @Override + public DistinctIterable hintString(@Nullable final String hint) { + this.hintString = hint; + return this; + } + + @Override + public ReadOperation> asReadOperation() { + return operations.distinct(fieldName, filter, resultClass, collation, comment, hint, hintString); + } protected OperationExecutor getExecutor() { return getExecutor(operations.createTimeoutSettings(maxTimeMS)); diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedCrudHelper.java b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedCrudHelper.java index 041f016510f..64cf204e565 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedCrudHelper.java +++ b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedCrudHelper.java @@ -492,6 +492,13 @@ OperationResult executeDistinct(final BsonDocument operation) { case "comment": iterable.comment(cur.getValue()); break; + case "hint": + if (cur.getValue().isString()) { + iterable.hintString(cur.getValue().asString().getValue()); + } else { + iterable.hint(cur.getValue().asDocument()); + } + break; case "filter": iterable.filter(cur.getValue().asDocument()); break;