diff --git a/driver-kotlin-coroutine/src/integrationTest/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncAggregateIterable.kt b/driver-kotlin-coroutine/src/integrationTest/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncAggregateIterable.kt index 439a0ccbb2..b0966b2200 100644 --- a/driver-kotlin-coroutine/src/integrationTest/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncAggregateIterable.kt +++ b/driver-kotlin-coroutine/src/integrationTest/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncAggregateIterable.kt @@ -74,4 +74,19 @@ data class SyncAggregateIterable(val wrapped: AggregateFlow) : override fun explain(explainResultClass: Class, verbosity: ExplainVerbosity): E = runBlocking { wrapped.explain(explainResultClass, verbosity) } + + override fun explain(timeoutMS: Long): Document = runBlocking { wrapped.explain(timeoutMS) } + + override fun explain(verbosity: ExplainVerbosity, timeoutMS: Long): Document = runBlocking { + wrapped.explain(verbosity, timeoutMS) + } + + override fun explain(explainResultClass: Class, timeoutMS: Long): E = runBlocking { + wrapped.explain(explainResultClass, timeoutMS) + } + + override fun explain(explainResultClass: Class, verbosity: ExplainVerbosity, timeoutMS: Long): E = + runBlocking { + wrapped.explain(explainResultClass, verbosity, timeoutMS) + } } diff --git a/driver-kotlin-coroutine/src/integrationTest/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncFindIterable.kt b/driver-kotlin-coroutine/src/integrationTest/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncFindIterable.kt index 6c500a9cf9..24dd9767d5 100644 --- a/driver-kotlin-coroutine/src/integrationTest/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncFindIterable.kt +++ b/driver-kotlin-coroutine/src/integrationTest/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncFindIterable.kt @@ -90,4 +90,19 @@ data class SyncFindIterable(val wrapped: FindFlow) : JFindIterable explain(explainResultClass: Class, verbosity: ExplainVerbosity): E = runBlocking { wrapped.explain(explainResultClass, verbosity) } + + override fun explain(timeoutMS: Long): Document = runBlocking { wrapped.explain(timeoutMS) } + + override fun explain(verbosity: ExplainVerbosity, timeoutMS: Long): Document = runBlocking { + wrapped.explain(verbosity, timeoutMS) + } + + override fun explain(explainResultClass: Class, timeoutMS: Long): E = runBlocking { + wrapped.explain(explainResultClass, timeoutMS) + } + + override fun explain(explainResultClass: Class, verbosity: ExplainVerbosity, timeoutMS: Long): E = + runBlocking { + wrapped.explain(explainResultClass, verbosity, timeoutMS) + } } 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 index a1debfd812..13fb2ae8c4 100644 --- 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 @@ -182,6 +182,14 @@ public class AggregateFlow(private val wrapped: AggregatePublisher) */ public fun let(variables: Bson?): AggregateFlow = apply { wrapped.let(variables) } + /** + * Requests [AggregateFlow] to start streaming data according to the specified aggregation pipeline. + * - If the aggregation pipeline ends with an `$out` or `$merge` stage, then finds all documents in the affected + * namespace and emits them. You may want to use [toCollection] instead. + * - Otherwise, emits no values. + */ + public override suspend fun collect(collector: FlowCollector): Unit = wrapped.asFlow().collect(collector) + /** * Explain the execution plan for this operation with the given verbosity level * @@ -217,10 +225,47 @@ public class AggregateFlow(private val wrapped: AggregatePublisher) explain(R::class.java, verbosity) /** - * Requests [AggregateFlow] to start streaming data according to the specified aggregation pipeline. - * - If the aggregation pipeline ends with an `$out` or `$merge` stage, then finds all documents in the affected - * namespace and emits them. You may want to use [toCollection] instead. - * - Otherwise, emits no values. + * Explain the execution plan for this operation. + * + * @param timeoutMS the timeout in milliseconds for the explain operation. + * @return the execution plan. + * @see [Explain command](https://www.mongodb.com/docs/manual/reference/command/explain/) */ - public override suspend fun collect(collector: FlowCollector): Unit = wrapped.asFlow().collect(collector) + public suspend inline fun explain(timeoutMS: Long): R = explain(R::class.java, timeoutMS) + + /** + * Explain the execution plan for this operation. + * + * @param verbosity the verbosity of the explanation. + * @param timeoutMS the timeout in milliseconds for the explain operation. + * @return the execution plan. + * @see [Explain command](https://www.mongodb.com/docs/manual/reference/command/explain/) + */ + public suspend inline fun explain(verbosity: ExplainVerbosity, timeoutMS: Long): R = + explain(R::class.java, verbosity, timeoutMS) + + /** + * Explain the execution plan for this operation. + * + * @param R the type of the document class. + * @param resultClass the document class to decode into. + * @param timeoutMS the timeout in milliseconds for the explain operation. + * @return the execution plan. + * @see [Explain command](https://www.mongodb.com/docs/manual/reference/command/explain/) + */ + public suspend fun explain(resultClass: Class, timeoutMS: Long): R = + wrapped.explain(resultClass, timeoutMS).awaitSingle() + + /** + * Explain the execution plan for this operation. + * + * @param R the type of the document class. + * @param resultClass the document class to decode into. + * @param verbosity the verbosity of the explanation. + * @param timeoutMS the timeout in milliseconds for the explain operation. + * @return the execution plan. + * @see [Explain command](https://www.mongodb.com/docs/manual/reference/command/explain/) + */ + public suspend fun explain(resultClass: Class, verbosity: ExplainVerbosity, timeoutMS: Long): R = + wrapped.explain(resultClass, verbosity, timeoutMS).awaitSingle() } 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 index f0afb4e993..7d4eb5a1be 100644 --- 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 @@ -302,5 +302,51 @@ public class FindFlow(private val wrapped: FindPublisher) : Flow public suspend inline fun explain(verbosity: ExplainVerbosity? = null): R = explain(R::class.java, verbosity) + /** + * Explain the execution plan for this operation. + * + * @param R the type of the document class. + * @param timeoutMS the timeout in milliseconds for the explain operation. + * @return the execution plan. + * @see [Explain command](https://www.mongodb.com/docs/manual/reference/command/explain/) + */ + public suspend inline fun explain(timeoutMS: Long): R = explain(R::class.java, timeoutMS) + + /** + * Explain the execution plan for this operation. + * + * @param verbosity the verbosity of the explanation. + * @param timeoutMS the timeout in milliseconds for the explain operation. + * @return the execution plan. + * @see [Explain command](https://www.mongodb.com/docs/manual/reference/command/explain/) + */ + public suspend inline fun explain(verbosity: ExplainVerbosity, timeoutMS: Long): R = + explain(R::class.java, verbosity, timeoutMS) + + /** + * Explain the execution plan for this operation. + * + * @param R the type of the document class. + * @param resultClass the document class to decode into. + * @param timeoutMS the timeout in milliseconds for the explain operation. + * @return the execution plan. + * @see [Explain command](https://www.mongodb.com/docs/manual/reference/command/explain/) + */ + public suspend fun explain(resultClass: Class, timeoutMS: Long): R = + wrapped.explain(resultClass, timeoutMS).awaitSingle() + + /** + * Explain the execution plan for this operation. + * + * @param R the type of the document class. + * @param resultClass the document class to decode into. + * @param verbosity the verbosity of the explanation. + * @param timeoutMS the timeout in milliseconds for the explain operation. + * @return the execution plan. + * @see [Explain command](https://www.mongodb.com/docs/manual/reference/command/explain/) + */ + public suspend fun explain(resultClass: Class, verbosity: ExplainVerbosity, timeoutMS: Long): R = + wrapped.explain(resultClass, verbosity, timeoutMS).awaitSingle() + public override suspend fun collect(collector: FlowCollector): Unit = wrapped.asFlow().collect(collector) } diff --git a/driver-kotlin-sync/src/integrationTest/kotlin/com/mongodb/kotlin/client/syncadapter/SyncAggregateIterable.kt b/driver-kotlin-sync/src/integrationTest/kotlin/com/mongodb/kotlin/client/syncadapter/SyncAggregateIterable.kt index b563c67c36..b6604d2055 100644 --- a/driver-kotlin-sync/src/integrationTest/kotlin/com/mongodb/kotlin/client/syncadapter/SyncAggregateIterable.kt +++ b/driver-kotlin-sync/src/integrationTest/kotlin/com/mongodb/kotlin/client/syncadapter/SyncAggregateIterable.kt @@ -70,4 +70,14 @@ internal class SyncAggregateIterable(val wrapped: AggregateIterable) override fun explain(explainResultClass: Class, verbosity: ExplainVerbosity): E = wrapped.explain(explainResultClass, verbosity) + + override fun explain(timeoutMS: Long): Document = wrapped.explain(timeoutMS) + + override fun explain(verbosity: ExplainVerbosity, timeoutMS: Long): Document = wrapped.explain(verbosity, timeoutMS) + + override fun explain(explainResultClass: Class, timeoutMS: Long): E = + wrapped.explain(explainResultClass, timeoutMS) + + override fun explain(explainResultClass: Class, verbosity: ExplainVerbosity, timeoutMS: Long): E = + wrapped.explain(explainResultClass, verbosity, timeoutMS) } diff --git a/driver-kotlin-sync/src/integrationTest/kotlin/com/mongodb/kotlin/client/syncadapter/SyncFindIterable.kt b/driver-kotlin-sync/src/integrationTest/kotlin/com/mongodb/kotlin/client/syncadapter/SyncFindIterable.kt index 81247aeb2a..f667f7f545 100644 --- a/driver-kotlin-sync/src/integrationTest/kotlin/com/mongodb/kotlin/client/syncadapter/SyncFindIterable.kt +++ b/driver-kotlin-sync/src/integrationTest/kotlin/com/mongodb/kotlin/client/syncadapter/SyncFindIterable.kt @@ -87,4 +87,14 @@ internal class SyncFindIterable(val wrapped: FindIterable) : override fun explain(explainResultClass: Class, verbosity: ExplainVerbosity): E = wrapped.explain(explainResultClass, verbosity) + + override fun explain(timeoutMS: Long): Document = wrapped.explain(timeoutMS) + + override fun explain(verbosity: ExplainVerbosity, timeoutMS: Long): Document = wrapped.explain(verbosity, timeoutMS) + + override fun explain(explainResultClass: Class, timeoutMS: Long): E = + wrapped.explain(explainResultClass, timeoutMS) + + override fun explain(explainResultClass: Class, verbosity: ExplainVerbosity, timeoutMS: Long): E = + wrapped.explain(explainResultClass, verbosity, timeoutMS) } 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 index 49130b82c6..2cf260679e 100644 --- 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 @@ -188,6 +188,71 @@ public class AggregateIterable(private val wrapped: JAggregateIterable< */ public fun let(variables: Bson?): AggregateIterable = apply { wrapped.let(variables) } + /** + * Explain the execution plan for this operation. + * + * @param timeoutMS the timeout in milliseconds for the explain operation + * @return the execution plan + * @see [Explain command](https://www.mongodb.com/docs/manual/reference/command/explain/) + */ + public fun explain(timeoutMS: Long): Document = wrapped.explain(timeoutMS) + + /** + * Explain the execution plan for this operation. + * + * @param verbosity the verbosity of the explanation + * @param timeoutMS the timeout in milliseconds for the explain operation + * @return the execution plan + * @see [Explain command](https://www.mongodb.com/docs/manual/reference/command/explain/) + */ + public fun explain(verbosity: ExplainVerbosity, timeoutMS: Long): Document = wrapped.explain(verbosity, timeoutMS) + + /** + * Explain the execution plan for this operation. + * + * @param R the type of the document class + * @param resultClass the result document type. + * @param timeoutMS the timeout in milliseconds for the explain operation + * @return the execution plan + * @see [Explain command](https://www.mongodb.com/docs/manual/reference/command/explain/) + */ + public fun explain(resultClass: Class, timeoutMS: Long): R = wrapped.explain(resultClass, timeoutMS) + + /** + * Explain the execution plan for this operation. + * + * @param R the type of the document class + * @param resultClass the result document type. + * @param verbosity the verbosity of the explanation + * @param timeoutMS the timeout in milliseconds for the explain operation + * @return the execution plan + * @see [Explain command](https://www.mongodb.com/docs/manual/reference/command/explain/) + */ + public fun explain(resultClass: Class, verbosity: ExplainVerbosity, timeoutMS: Long): R = + wrapped.explain(resultClass, verbosity, timeoutMS) + + /** + * Explain the execution plan for this operation. + * + * @param R the type of the document class + * @param timeoutMS the timeout in milliseconds for the explain operation + * @return the execution plan + * @see [Explain command](https://www.mongodb.com/docs/manual/reference/command/explain/) + */ + public inline fun explain(timeoutMS: Long): R = explain(R::class.java, timeoutMS) + + /** + * Explain the execution plan for this operation. + * + * @param R the type of the document class + * @param verbosity the verbosity of the explanation + * @param timeoutMS the timeout in milliseconds for the explain operation + * @return the execution plan + * @see [Explain command](https://www.mongodb.com/docs/manual/reference/command/explain/) + */ + public inline fun explain(verbosity: ExplainVerbosity, timeoutMS: Long): R = + explain(R::class.java, verbosity, timeoutMS) + /** * Explain the execution plan for this operation with the given verbosity level * 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 index 81e1bb5186..5055b5f163 100644 --- 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 @@ -284,6 +284,49 @@ public class FindIterable(private val wrapped: JFindIterable) : Mong 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. + * + * @param timeoutMS the timeout in milliseconds for the explain operation + * @return the execution plan + * @see [Explain command](https://www.mongodb.com/docs/manual/reference/command/explain/) + */ + public fun explain(timeoutMS: Long): Document = wrapped.explain(timeoutMS) + + /** + * Explain the execution plan for this operation. + * + * @param verbosity the verbosity of the explanation + * @param timeoutMS the timeout in milliseconds for the explain operation + * @return the execution plan + * @see [Explain command](https://www.mongodb.com/docs/manual/reference/command/explain/) + */ + public fun explain(verbosity: ExplainVerbosity, timeoutMS: Long): Document = wrapped.explain(verbosity, timeoutMS) + + /** + * Explain the execution plan for this operation. + * + * @param R the type of the document class + * @param resultClass the result document type. + * @param timeoutMS the timeout in milliseconds for the explain operation + * @return the execution plan + * @see [Explain command](https://www.mongodb.com/docs/manual/reference/command/explain/) + */ + public fun explain(resultClass: Class, timeoutMS: Long): R = wrapped.explain(resultClass, timeoutMS) + + /** + * Explain the execution plan for this operation. + * + * @param R the type of the document class + * @param resultClass the result document type. + * @param verbosity the verbosity of the explanation + * @param timeoutMS the timeout in milliseconds for the explain operation + * @return the execution plan + * @see [Explain command](https://www.mongodb.com/docs/manual/reference/command/explain/) + */ + public fun explain(resultClass: Class, verbosity: ExplainVerbosity, timeoutMS: Long): R = + wrapped.explain(resultClass, verbosity, timeoutMS) + /** * Explain the execution plan for this operation with the given verbosity level * diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/AggregatePublisher.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/AggregatePublisher.java index 4f18fe272b..b6b28b054a 100644 --- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/AggregatePublisher.java +++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/AggregatePublisher.java @@ -272,4 +272,54 @@ public interface AggregatePublisher extends Publisher { * @mongodb.server.release 3.6 */ Publisher explain(Class explainResultClass, ExplainVerbosity verbosity); + + /** + * Explain the execution plan for this operation. + * + * @param timeoutMS the timeout in milliseconds for the explain operation + * @return the execution plan + * @since 5.2 + * @mongodb.driver.manual reference/command/explain/ + * @mongodb.server.release 3.6 + */ + Publisher explain(long timeoutMS); + + /** + * Explain the execution plan for this operation. + * + * @param verbosity the verbosity of the explanation + * @param timeoutMS the timeout in milliseconds for the explain operation + * @return the execution plan + * @since 5.2 + * @mongodb.driver.manual reference/command/explain/ + * @mongodb.server.release 3.6 + */ + Publisher explain(ExplainVerbosity verbosity, long timeoutMS); + + /** + * Explain the execution plan for this operation. + * + * @param the type of the document class + * @param explainResultClass the document class to decode into + * @param timeoutMS the timeout in milliseconds for the explain operation + * @return the execution plan + * @since 5.2 + * @mongodb.driver.manual reference/command/explain/ + * @mongodb.server.release 3.6 + */ + Publisher explain(Class explainResultClass, long timeoutMS); + + /** + * Explain the execution plan for this operation. + * + * @param the type of the document class + * @param explainResultClass the document class to decode into + * @param verbosity the verbosity of the explanation + * @param timeoutMS the timeout in milliseconds for the explain operation + * @return the execution plan + * @since 5.2 + * @mongodb.driver.manual reference/command/explain/ + * @mongodb.server.release 3.6 + */ + Publisher explain(Class explainResultClass, ExplainVerbosity verbosity, long timeoutMS); } diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/FindPublisher.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/FindPublisher.java index 1128c87bd0..33bf455067 100644 --- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/FindPublisher.java +++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/FindPublisher.java @@ -338,4 +338,54 @@ public interface FindPublisher extends Publisher { * @mongodb.server.release 3.2 */ Publisher explain(Class explainResultClass, ExplainVerbosity verbosity); + + /** + * Explain the execution plan for this operation. + * + * @param timeoutMS the timeout in milliseconds for the explain operation + * @return the execution plan + * @since 5.2 + * @mongodb.driver.manual reference/command/explain/ + * @mongodb.server.release 3.2 + */ + Publisher explain(long timeoutMS); + + /** + * Explain the execution plan for this operation. + * + * @param verbosity the verbosity of the explanation + * @param timeoutMS the timeout in milliseconds for the explain operation + * @return the execution plan + * @since 5.2 + * @mongodb.driver.manual reference/command/explain/ + * @mongodb.server.release 3.2 + */ + Publisher explain(ExplainVerbosity verbosity, long timeoutMS); + + /** + * Explain the execution plan for this operation. + * + * @param the type of the document class + * @param explainResultClass the document class to decode into + * @param timeoutMS the timeout in milliseconds for the explain operation + * @return the execution plan + * @since 5.2 + * @mongodb.driver.manual reference/command/explain/ + * @mongodb.server.release 3.2 + */ + Publisher explain(Class explainResultClass, long timeoutMS); + + /** + * Explain the execution plan for this operation. + * + * @param the type of the document class + * @param explainResultClass the document class to decode into + * @param verbosity the verbosity of the explanation + * @param timeoutMS the timeout in milliseconds for the explain operation + * @return the execution plan + * @since 5.2 + * @mongodb.driver.manual reference/command/explain/ + * @mongodb.server.release 3.2 + */ + Publisher explain(Class explainResultClass, ExplainVerbosity verbosity, long timeoutMS); } diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/AggregatePublisherImpl.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/AggregatePublisherImpl.java index d96c0e933d..0d7c3a6454 100644 --- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/AggregatePublisherImpl.java +++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/AggregatePublisherImpl.java @@ -170,6 +170,26 @@ public Publisher explain(final Class explainResultClass, final Explain return publishExplain(explainResultClass, notNull("verbosity", verbosity)); } + @Override + public Publisher explain(final long timeoutMS) { + return publishExplainWithTimeout(Document.class, null, timeoutMS); + } + + @Override + public Publisher explain(final ExplainVerbosity verbosity, final long timeoutMS) { + return publishExplainWithTimeout(Document.class, notNull("verbosity", verbosity), timeoutMS); + } + + @Override + public Publisher explain(final Class explainResultClass, final long timeoutMS) { + return publishExplainWithTimeout(explainResultClass, null, timeoutMS); + } + + @Override + public Publisher explain(final Class explainResultClass, final ExplainVerbosity verbosity, final long timeoutMS) { + return publishExplainWithTimeout(explainResultClass, notNull("verbosity", verbosity), timeoutMS); + } + private Publisher publishExplain(final Class explainResultClass, @Nullable final ExplainVerbosity verbosity) { notNull("explainDocumentClass", explainResultClass); return getMongoOperationPublisher().createReadOperationMono( @@ -178,6 +198,17 @@ private Publisher publishExplain(final Class explainResultClass, @Null getCodecRegistry().get(explainResultClass)), getClientSession()); } + private Publisher publishExplainWithTimeout(final Class explainResultClass, @Nullable final ExplainVerbosity verbosity, final long timeoutMS) { + notNull("explainDocumentClass", explainResultClass); + // Create timeout settings function with the specified timeoutMS + Function, TimeoutSettings> timeoutSettingsFunction = (asyncOperations) -> + asyncOperations.createTimeoutSettings(maxTimeMS, maxAwaitTimeMS).withTimeout(timeoutMS, TimeUnit.MILLISECONDS); + return getMongoOperationPublisher().createReadOperationMono( + timeoutSettingsFunction, + () -> asAggregateOperation(1).asAsyncExplainableOperation(verbosity, + getCodecRegistry().get(explainResultClass)), getClientSession()); + } + @Override AsyncReadOperation> asAsyncReadOperation(final int initialBatchSize) { MongoNamespace outNamespace = getOutNamespace(); diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/FindPublisherImpl.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/FindPublisherImpl.java index ff9fb3a803..d9fb8e320d 100644 --- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/FindPublisherImpl.java +++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/FindPublisherImpl.java @@ -38,6 +38,7 @@ import java.util.function.Function; import static com.mongodb.assertions.Assertions.notNull; +import static java.util.concurrent.TimeUnit.MILLISECONDS; final class FindPublisherImpl extends BatchCursorPublisher implements FindPublisher { private final FindOptions findOptions; @@ -213,6 +214,26 @@ public Publisher explain(final Class explainResultClass, final Explain return publishExplain(explainResultClass, notNull("verbosity", verbosity)); } + @Override + public Publisher explain(final long timeoutMS) { + return publishExplainWithTimeout(Document.class, null, timeoutMS); + } + + @Override + public Publisher explain(final ExplainVerbosity verbosity, final long timeoutMS) { + return publishExplainWithTimeout(Document.class, notNull("verbosity", verbosity), timeoutMS); + } + + @Override + public Publisher explain(final Class explainResultClass, final long timeoutMS) { + return publishExplainWithTimeout(explainResultClass, null, timeoutMS); + } + + @Override + public Publisher explain(final Class explainResultClass, final ExplainVerbosity verbosity, final long timeoutMS) { + return publishExplainWithTimeout(explainResultClass, notNull("verbosity", verbosity), timeoutMS); + } + private Publisher publishExplain(final Class explainResultClass, @Nullable final ExplainVerbosity verbosity) { notNull("explainDocumentClass", explainResultClass); return getMongoOperationPublisher().createReadOperationMono( @@ -221,6 +242,18 @@ private Publisher publishExplain(final Class explainResultClass, @Null .asAsyncExplainableOperation(verbosity, getCodecRegistry().get(explainResultClass)), getClientSession()); } + private Publisher publishExplainWithTimeout(final Class explainResultClass, @Nullable final ExplainVerbosity verbosity, final long timeoutMS) { + notNull("explainDocumentClass", explainResultClass); + // Create timeout settings function with the specified timeoutMS + Function, TimeoutSettings> timeoutSettingsFunction = (asyncOperations) -> + asyncOperations.getTimeoutSettings().withTimeout(timeoutMS, MILLISECONDS); + return getMongoOperationPublisher().createReadOperationMono( + timeoutSettingsFunction, + () -> asAsyncReadOperation(0) + .asAsyncExplainableOperation(verbosity, getCodecRegistry().get(explainResultClass)), + getClientSession()); + } + @Override AsyncExplainableReadOperation> asAsyncReadOperation(final int initialBatchSize) { return getOperations().find(filter, getDocumentClass(), findOptions.withBatchSize(initialBatchSize)); diff --git a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/syncadapter/SyncAggregateIterable.java b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/syncadapter/SyncAggregateIterable.java index 6b81b1f42a..bf6cdaee04 100644 --- a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/syncadapter/SyncAggregateIterable.java +++ b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/syncadapter/SyncAggregateIterable.java @@ -137,4 +137,24 @@ public E explain(final Class explainResultClass) { public E explain(final Class explainResultClass, final ExplainVerbosity verbosity) { return requireNonNull(Mono.from(wrapped.explain(explainResultClass, verbosity)).contextWrite(CONTEXT).block(TIMEOUT_DURATION)); } + + @Override + public Document explain(final long timeoutMS) { + return requireNonNull(Mono.from(wrapped.explain(timeoutMS)).contextWrite(CONTEXT).block(TIMEOUT_DURATION)); + } + + @Override + public Document explain(final ExplainVerbosity verbosity, final long timeoutMS) { + return requireNonNull(Mono.from(wrapped.explain(verbosity, timeoutMS)).contextWrite(CONTEXT).block(TIMEOUT_DURATION)); + } + + @Override + public E explain(final Class explainResultClass, final long timeoutMS) { + return requireNonNull(Mono.from(wrapped.explain(explainResultClass, timeoutMS)).contextWrite(CONTEXT).block(TIMEOUT_DURATION)); + } + + @Override + public E explain(final Class explainResultClass, final ExplainVerbosity verbosity, final long timeoutMS) { + return requireNonNull(Mono.from(wrapped.explain(explainResultClass, verbosity, timeoutMS)).contextWrite(CONTEXT).block(TIMEOUT_DURATION)); + } } diff --git a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/syncadapter/SyncFindIterable.java b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/syncadapter/SyncFindIterable.java index 3cf93b9ffb..65ae7b1eb3 100644 --- a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/syncadapter/SyncFindIterable.java +++ b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/syncadapter/SyncFindIterable.java @@ -200,4 +200,24 @@ public E explain(final Class explainResultClass) { public E explain(final Class explainResultClass, final ExplainVerbosity verbosity) { return requireNonNull(Mono.from(wrapped.explain(explainResultClass, verbosity)).contextWrite(CONTEXT).block(TIMEOUT_DURATION)); } + + @Override + public Document explain(final long timeoutMS) { + return requireNonNull(Mono.from(wrapped.explain(timeoutMS)).contextWrite(CONTEXT).block(TIMEOUT_DURATION)); + } + + @Override + public Document explain(final ExplainVerbosity verbosity, final long timeoutMS) { + return requireNonNull(Mono.from(wrapped.explain(verbosity, timeoutMS)).contextWrite(CONTEXT).block(TIMEOUT_DURATION)); + } + + @Override + public E explain(final Class explainResultClass, final long timeoutMS) { + return requireNonNull(Mono.from(wrapped.explain(explainResultClass, timeoutMS)).contextWrite(CONTEXT).block(TIMEOUT_DURATION)); + } + + @Override + public E explain(final Class explainResultClass, final ExplainVerbosity verbosity, final long timeoutMS) { + return requireNonNull(Mono.from(wrapped.explain(explainResultClass, verbosity, timeoutMS)).contextWrite(CONTEXT).block(TIMEOUT_DURATION)); + } } diff --git a/driver-scala/src/integrationTest/scala/org/mongodb/scala/syncadapter/SyncAggregateIterable.scala b/driver-scala/src/integrationTest/scala/org/mongodb/scala/syncadapter/SyncAggregateIterable.scala index d9cec1ede3..9246624838 100644 --- a/driver-scala/src/integrationTest/scala/org/mongodb/scala/syncadapter/SyncAggregateIterable.scala +++ b/driver-scala/src/integrationTest/scala/org/mongodb/scala/syncadapter/SyncAggregateIterable.scala @@ -17,12 +17,11 @@ package org.mongodb.scala.syncadapter import com.mongodb.ExplainVerbosity import com.mongodb.client.AggregateIterable -import org.mongodb.scala.TimeoutMode import com.mongodb.client.model.Collation import org.bson.conversions.Bson import org.bson.{ BsonValue, Document } -import org.mongodb.scala.AggregateObservable import org.mongodb.scala.bson.DefaultHelper.DefaultsTo +import org.mongodb.scala.{ AggregateObservable, TimeoutMode } import java.util.concurrent.TimeUnit import scala.concurrent.duration.Duration @@ -108,4 +107,21 @@ case class SyncAggregateIterable[T](wrapped: AggregateObservable[T]) .explain[E](verbosity)(DefaultsTo.overrideDefault[E, org.mongodb.scala.Document], ClassTag(explainResultClass)) .toFuture() .get() + + override def explain(timeoutMS: Long): Document = wrapped.explain(timeoutMS).toFuture().get() + + override def explain(verbosity: ExplainVerbosity, timeoutMS: Long): Document = + wrapped.explain(verbosity, timeoutMS).toFuture().get() + + override def explain[E](explainResultClass: Class[E], timeoutMS: Long): E = + wrapped.explain[E](timeoutMS)( + DefaultsTo.overrideDefault[E, org.mongodb.scala.Document], + ClassTag(explainResultClass) + ).toFuture().get() + + override def explain[E](explainResultClass: Class[E], verbosity: ExplainVerbosity, timeoutMS: Long): E = + wrapped.explain[E](verbosity, timeoutMS)( + DefaultsTo.overrideDefault[E, org.mongodb.scala.Document], + ClassTag(explainResultClass) + ).toFuture().get() } diff --git a/driver-scala/src/integrationTest/scala/org/mongodb/scala/syncadapter/SyncFindIterable.scala b/driver-scala/src/integrationTest/scala/org/mongodb/scala/syncadapter/SyncFindIterable.scala index 505241ab39..749715e10c 100644 --- a/driver-scala/src/integrationTest/scala/org/mongodb/scala/syncadapter/SyncFindIterable.scala +++ b/driver-scala/src/integrationTest/scala/org/mongodb/scala/syncadapter/SyncFindIterable.scala @@ -159,4 +159,27 @@ case class SyncFindIterable[T](wrapped: FindObservable[T]) extends SyncMongoIter .explain[E](verbosity)(DefaultsTo.overrideDefault[E, org.mongodb.scala.Document], ClassTag(explainResultClass)) .toFuture() .get() + + override def explain(timeoutMS: Long): Document = wrapped.explain(timeoutMS).toFuture().get() + + override def explain(verbosity: ExplainVerbosity, timeoutMS: Long): Document = + wrapped.explain(verbosity, timeoutMS).toFuture().get() + + override def explain[E](explainResultClass: Class[E], timeoutMS: Long): E = { + wrapped.explain[E](timeoutMS)( + DefaultsTo.overrideDefault[E, org.mongodb.scala.Document], + ClassTag(explainResultClass) + ) + .toFuture() + .get() + } + + override def explain[E](explainResultClass: Class[E], verbosity: ExplainVerbosity, timeoutMS: Long): E = { + wrapped.explain[E](verbosity, timeoutMS)( + DefaultsTo.overrideDefault[E, org.mongodb.scala.Document], + ClassTag(explainResultClass) + ) + .toFuture() + .get() + } } diff --git a/driver-scala/src/main/scala/org/mongodb/scala/AggregateObservable.scala b/driver-scala/src/main/scala/org/mongodb/scala/AggregateObservable.scala index a363ee2166..79a267ba84 100644 --- a/driver-scala/src/main/scala/org/mongodb/scala/AggregateObservable.scala +++ b/driver-scala/src/main/scala/org/mongodb/scala/AggregateObservable.scala @@ -263,6 +263,37 @@ case class AggregateObservable[TResult](private val wrapped: AggregatePublisher[ )(implicit e: ExplainResult DefaultsTo Document, ct: ClassTag[ExplainResult]): SingleObservable[ExplainResult] = wrapped.explain[ExplainResult](ct, verbosity) + /** + * Explain the execution plan for this operation with the server's default verbosity level + * + * @tparam ExplainResult The type of the result + * @param timeoutMS the timeout in milliseconds + * @return the execution plan + * @since 5.6 + * @note Requires MongoDB 3.6 or greater + */ + def explain[ExplainResult](timeoutMS: Long)( + implicit e: ExplainResult DefaultsTo Document, + ct: ClassTag[ExplainResult] + ): SingleObservable[ExplainResult] = + wrapped.explain[ExplainResult](ct, timeoutMS) + + /** + * Explain the execution plan for this operation with the given verbosity level + * + * @tparam ExplainResult The type of the result + * @param verbosity the verbosity of the explanation + * @param timeoutMS the timeout in milliseconds + * @return the execution plan + * @since 5.6 + * @note Requires MongoDB 3.6 or greater + */ + def explain[ExplainResult]( + verbosity: ExplainVerbosity, + timeoutMS: Long + )(implicit e: ExplainResult DefaultsTo Document, ct: ClassTag[ExplainResult]): SingleObservable[ExplainResult] = + wrapped.explain[ExplainResult](ct, verbosity, timeoutMS) + /** * Requests [[AggregateObservable]] to start streaming data according to the specified aggregation pipeline. * diff --git a/driver-scala/src/main/scala/org/mongodb/scala/FindObservable.scala b/driver-scala/src/main/scala/org/mongodb/scala/FindObservable.scala index 57a964b831..4546a76e52 100644 --- a/driver-scala/src/main/scala/org/mongodb/scala/FindObservable.scala +++ b/driver-scala/src/main/scala/org/mongodb/scala/FindObservable.scala @@ -383,5 +383,36 @@ case class FindObservable[TResult](private val wrapped: FindPublisher[TResult]) )(implicit e: ExplainResult DefaultsTo Document, ct: ClassTag[ExplainResult]): SingleObservable[ExplainResult] = wrapped.explain[ExplainResult](ct, verbosity) + /** + * Explain the execution plan for this operation with the server's default verbosity level + * + * @tparam ExplainResult The type of the result + * @param timeoutMS the timeout in milliseconds + * @return the execution plan + * @since 5.6 + * @note Requires MongoDB 3.2 or greater + */ + def explain[ExplainResult](timeoutMS: Long)( + implicit e: ExplainResult DefaultsTo Document, + ct: ClassTag[ExplainResult] + ): SingleObservable[ExplainResult] = + wrapped.explain[ExplainResult](ct, timeoutMS) + + /** + * Explain the execution plan for this operation with the given verbosity level + * + * @tparam ExplainResult The type of the result + * @param verbosity the verbosity of the explanation + * @param timeoutMS the timeout in milliseconds + * @return the execution plan + * @since 5.6 + * @note Requires MongoDB 3.2 or greater + */ + def explain[ExplainResult]( + verbosity: ExplainVerbosity, + timeoutMS: Long + )(implicit e: ExplainResult DefaultsTo Document, ct: ClassTag[ExplainResult]): SingleObservable[ExplainResult] = + wrapped.explain[ExplainResult](ct, verbosity, timeoutMS) + override def subscribe(observer: Observer[_ >: TResult]): Unit = wrapped.subscribe(observer) } diff --git a/driver-sync/src/main/com/mongodb/client/AggregateIterable.java b/driver-sync/src/main/com/mongodb/client/AggregateIterable.java index 032e186024..3910dfee37 100644 --- a/driver-sync/src/main/com/mongodb/client/AggregateIterable.java +++ b/driver-sync/src/main/com/mongodb/client/AggregateIterable.java @@ -279,4 +279,50 @@ public interface AggregateIterable extends MongoIterable { * @mongodb.server.release 3.6 */ E explain(Class explainResultClass, ExplainVerbosity verbosity); + + /** + * Explain the execution plan for this operation. + * + * @param timeoutMS the timeout in milliseconds for the explain operation + * @return the execution plan + * @mongodb.driver.manual reference/command/explain/ + * @mongodb.server.release 3.6 + */ + Document explain(long timeoutMS); + + /** + * Explain the execution plan for this operation. + * + * @param verbosity the verbosity of the explanation + * @param timeoutMS the timeout in milliseconds for the explain operation + * @return the execution plan + * @mongodb.driver.manual reference/command/explain/ + * @mongodb.server.release 3.6 + */ + Document explain(ExplainVerbosity verbosity, long timeoutMS); + + /** + * Explain the execution plan for this operation. + * + * @param the type of the document class + * @param explainResultClass the document class to decode into + * @param timeoutMS the timeout in milliseconds for the explain operation + * @return the execution plan + * @mongodb.driver.manual reference/command/explain/ + * @mongodb.server.release 3.6 + */ + E explain(Class explainResultClass, long timeoutMS); + + /** + * Explain the execution plan for this operation. + * + * @param the type of the document class + * @param explainResultClass the document class to decode into + * @param verbosity the verbosity of the explanation + * @param timeoutMS the timeout in milliseconds for the explain operation + * @return the execution plan + * @mongodb.driver.manual reference/command/explain/ + * @mongodb.server.release 3.6 + */ + E explain(Class explainResultClass, ExplainVerbosity verbosity, long timeoutMS); } diff --git a/driver-sync/src/main/com/mongodb/client/FindIterable.java b/driver-sync/src/main/com/mongodb/client/FindIterable.java index d610ed73ff..698e7f6293 100644 --- a/driver-sync/src/main/com/mongodb/client/FindIterable.java +++ b/driver-sync/src/main/com/mongodb/client/FindIterable.java @@ -331,4 +331,50 @@ public interface FindIterable extends MongoIterable { * @mongodb.server.release 3.2 */ E explain(Class explainResultClass, ExplainVerbosity verbosity); + + /** + * Explain the execution plan for this operation. + * + * @param timeoutMS the timeout in milliseconds for the explain operation + * @return the execution plan + * @mongodb.driver.manual reference/command/explain/ + * @mongodb.server.release 3.2 + */ + Document explain(long timeoutMS); + + /** + * Explain the execution plan for this operation. + * + * @param verbosity the verbosity of the explanation + * @param timeoutMS the timeout in milliseconds for the explain operation + * @return the execution plan + * @mongodb.driver.manual reference/command/explain/ + * @mongodb.server.release 3.2 + */ + Document explain(ExplainVerbosity verbosity, long timeoutMS); + + /** + * Explain the execution plan for this operation. + * + * @param the type of the document class + * @param explainResultClass the document class to decode into + * @param timeoutMS the timeout in milliseconds for the explain operation + * @return the execution plan + * @mongodb.driver.manual reference/command/explain/ + * @mongodb.server.release 3.2 + */ + E explain(Class explainResultClass, long timeoutMS); + + /** + * Explain the execution plan for this operation. + * + * @param the type of the document class + * @param explainResultClass the document class to decode into + * @param verbosity the verbosity of the explanation + * @param timeoutMS the timeout in milliseconds for the explain operation + * @return the execution plan + * @mongodb.driver.manual reference/command/explain/ + * @mongodb.server.release 3.2 + */ + E explain(Class explainResultClass, ExplainVerbosity verbosity, long timeoutMS); } diff --git a/driver-sync/src/main/com/mongodb/client/internal/AggregateIterableImpl.java b/driver-sync/src/main/com/mongodb/client/internal/AggregateIterableImpl.java index 23c8fb3528..126da2a420 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/AggregateIterableImpl.java +++ b/driver-sync/src/main/com/mongodb/client/internal/AggregateIterableImpl.java @@ -197,6 +197,26 @@ public E explain(final Class explainResultClass, final ExplainVerbosity v return executeExplain(explainResultClass, notNull("verbosity", verbosity)); } + @Override + public Document explain(final long timeoutMS) { + return executeExplainWithTimeout(Document.class, null, timeoutMS); + } + + @Override + public Document explain(final ExplainVerbosity verbosity, final long timeoutMS) { + return executeExplainWithTimeout(Document.class, notNull("verbosity", verbosity), timeoutMS); + } + + @Override + public E explain(final Class explainResultClass, final long timeoutMS) { + return executeExplainWithTimeout(explainResultClass, null, timeoutMS); + } + + @Override + public E explain(final Class explainResultClass, final ExplainVerbosity verbosity, final long timeoutMS) { + return executeExplainWithTimeout(explainResultClass, notNull("verbosity", verbosity), timeoutMS); + } + private E executeExplain(final Class explainResultClass, @Nullable final ExplainVerbosity verbosity) { notNull("explainDocumentClass", explainResultClass); return getExecutor().execute( @@ -204,6 +224,14 @@ private E executeExplain(final Class explainResultClass, @Nullable final getReadConcern(), getClientSession()); } + private E executeExplainWithTimeout(final Class explainResultClass, @Nullable final ExplainVerbosity verbosity, final long timeoutMS) { + notNull("explainDocumentClass", explainResultClass); + OperationExecutor timeoutExecutor = getExecutor(operations.createTimeoutSettings(maxTimeMS, maxAwaitTimeMS).withTimeout(timeoutMS, TimeUnit.MILLISECONDS)); + return timeoutExecutor.execute( + asAggregateOperation().asExplainableOperation(verbosity, codecRegistry.get(explainResultClass)), getReadPreference(), + getReadConcern(), getClientSession()); + } + @Override public ReadOperation> asReadOperation() { MongoNamespace outNamespace = getOutNamespace(); diff --git a/driver-sync/src/main/com/mongodb/client/internal/FindIterableImpl.java b/driver-sync/src/main/com/mongodb/client/internal/FindIterableImpl.java index fbead0d791..1bb8418c5b 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/FindIterableImpl.java +++ b/driver-sync/src/main/com/mongodb/client/internal/FindIterableImpl.java @@ -224,8 +224,8 @@ public Document explain(final ExplainVerbosity verbosity) { } @Override - public E explain(final Class explainDocumentClass) { - return executeExplain(explainDocumentClass, null); + public E explain(final Class explainResultClass) { + return executeExplain(explainResultClass, null); } @Override @@ -233,6 +233,25 @@ public E explain(final Class explainResultClass, final ExplainVerbosity v return executeExplain(explainResultClass, notNull("verbosity", verbosity)); } + @Override + public Document explain(final long timeoutMS) { + return executeExplainWithTimeout(Document.class, null, timeoutMS); + } + + @Override + public Document explain(final ExplainVerbosity verbosity, final long timeoutMS) { + return executeExplainWithTimeout(Document.class, notNull("verbosity", verbosity), timeoutMS); + } + + @Override + public E explain(final Class explainResultClass, final long timeoutMS) { + return executeExplainWithTimeout(explainResultClass, null, timeoutMS); + } + + @Override + public E explain(final Class explainResultClass, final ExplainVerbosity verbosity, final long timeoutMS) { + return executeExplainWithTimeout(explainResultClass, notNull("verbosity", verbosity), timeoutMS); + } protected OperationExecutor getExecutor() { return getExecutor(operations.createTimeoutSettings(findOptions)); @@ -244,8 +263,14 @@ private E executeExplain(final Class explainResultClass, @Nullable final asReadOperation().asExplainableOperation(verbosity, codecRegistry.get(explainResultClass)), getReadPreference(), getReadConcern(), getClientSession()); } + private E executeExplainWithTimeout(final Class explainResultClass, @Nullable final ExplainVerbosity verbosity, final long timeoutMS) { + notNull("explainDocumentClass", explainResultClass); + OperationExecutor timeoutExecutor = getExecutor(operations.createTimeoutSettings(findOptions).withTimeout(timeoutMS, TimeUnit.MILLISECONDS)); + return timeoutExecutor.execute( + asReadOperation().asExplainableOperation(verbosity, codecRegistry.get(explainResultClass)), getReadPreference(), getReadConcern(), getClientSession()); + } + public ExplainableReadOperation> asReadOperation() { return operations.find(filter, resultClass, findOptions); } - } diff --git a/driver-sync/src/test/functional/com/mongodb/client/AbstractExplainTest.java b/driver-sync/src/test/functional/com/mongodb/client/AbstractExplainTest.java index 920dff0396..82560ed1ea 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/AbstractExplainTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/AbstractExplainTest.java @@ -19,6 +19,7 @@ import com.mongodb.ExplainVerbosity; import com.mongodb.MongoClientSettings; import com.mongodb.MongoCommandException; +import com.mongodb.MongoOperationTimeoutException; import com.mongodb.client.model.Aggregates; import com.mongodb.client.model.Filters; import com.mongodb.event.CommandStartedEvent; @@ -39,10 +40,10 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.junit.Assume.assumeTrue; public abstract class AbstractExplainTest { - private MongoClient client; private TestCommandListener commandListener; @@ -175,6 +176,79 @@ public void testExplainOfAggregateWithNewResponseStructure() { assertFalse(explainBsonDocument.containsKey("executionStats")); } + @Test + public void testExplainWithMaxTimeMS() { + // Create a collection with the namespace explain-test.collection + MongoCollection collection = client.getDatabase("explain-test") + .getCollection("collection"); + collection.drop(); + + // Insert a test document + collection.insertOne(new Document("name", "john doe")); + + // Reset command listener to capture only the explain command + commandListener.reset(); + + client.getDatabase("admin") + .runCommand(BsonDocument.parse("{" + + " configureFailPoint: \"failCommand\"," + + " mode: {" + + " times: 1" + + " }," + + " data: {" + + " failCommands: [\"explain\"]," + + " blockConnection: true," + + " blockTimeMS: 100" + + " }" + + "}")); + + FindIterable findIterable = collection.find( + Filters.and( + Filters.eq("name", "john doe") + ) + ); + + // Execute the explain with short timeoutMS - this should trigger a MongoOperationTimeoutException + try { + findIterable.explain(50L); // Use 50ms timeout to trigger timeout + fail("Expected MongoOperationTimeoutException but explain completed successfully"); + } catch (MongoOperationTimeoutException e) { + // This is expected - the operation should timeout + assertTrue("Exception message should mention timeout", + e.getMessage().toLowerCase().contains("operation exceeded the timeout limit") || e.getMessage().toLowerCase().contains("timeout")); + + // Verify that the explain command was sent with maxTimeMS before timing out + // The command should have been started even though it timed out + assertFalse("At least one command should have been started", commandListener.getCommandStartedEvents().isEmpty()); + + CommandStartedEvent explainEvent = commandListener.getCommandStartedEvent("explain"); + // Confirm that the explain command had maxTimeMS set + BsonDocument explainCommand = explainEvent.getCommand(); + assertTrue("Explain command should contain maxTimeMS when timeoutMS is specified", + explainCommand.containsKey("maxTimeMS")); + + // The maxTimeMS value should be derived from the timeoutMS (may be less due to RTT calculation) + // Handle both INT32 and INT64 types + long maxTimeMSValue; + if (explainCommand.get("maxTimeMS").isInt32()) { + maxTimeMSValue = explainCommand.getInt32("maxTimeMS").getValue(); + } else { + maxTimeMSValue = explainCommand.getInt64("maxTimeMS").getValue(); + } + assertTrue("maxTimeMS should be positive", maxTimeMSValue > 0); + assertTrue("maxTimeMS should be <= timeoutMS", maxTimeMSValue <= 50); + + // Verify the explain command structure + assertTrue("Explain command should contain explain field", explainCommand.containsKey("explain")); + + // The inner find command should contain the correct filter + BsonDocument findCommand = explainCommand.getDocument("explain"); + assertTrue("Find command should contain filter", findCommand.containsKey("filter")); + BsonDocument filter = findCommand.getDocument("filter"); + assertTrue("Filter should contain $and", filter.containsKey("$and")); + } + } + // Post-MongoDB 7.0, sharded cluster responses move the explain plan document into a "shards" document, which a plan for each shard. // This method grabs the explain plan document from the first shard when this new structure is present. private static Document getAggregateExplainDocument(final Document rootAggregateExplainDocument) {