Skip to content

Commit e12c8e2

Browse files
authored
Support explain for find operations (#620)
Support is only for server versions 3.2+ JAVA-3909
1 parent 1a56895 commit e12c8e2

File tree

18 files changed

+458
-31
lines changed

18 files changed

+458
-31
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright 2008-present MongoDB, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.mongodb.internal.operation;
18+
19+
import com.mongodb.ExplainVerbosity;
20+
import com.mongodb.lang.Nullable;
21+
import org.bson.codecs.Decoder;
22+
23+
public interface AsyncExplainableReadOperation<T> extends AsyncReadOperation<T> {
24+
<R> AsyncReadOperation<R> asAsyncExplainableOperation(@Nullable ExplainVerbosity verbosity, Decoder<R> resultDecoder);
25+
}

driver-core/src/main/com/mongodb/internal/operation/ExplainHelper.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,18 @@
1818

1919
import com.mongodb.ExplainVerbosity;
2020
import com.mongodb.MongoInternalException;
21+
import com.mongodb.lang.Nullable;
2122
import org.bson.BsonDocument;
2223
import org.bson.BsonString;
2324

2425
final class ExplainHelper {
2526

26-
static BsonDocument asExplainCommand(final BsonDocument command, final ExplainVerbosity explainVerbosity) {
27-
return new BsonDocument("explain", command)
28-
.append("verbosity", getVerbosityAsString(explainVerbosity));
27+
static BsonDocument asExplainCommand(final BsonDocument command, @Nullable final ExplainVerbosity explainVerbosity) {
28+
BsonDocument explainCommand = new BsonDocument("explain", command);
29+
if (explainVerbosity != null) {
30+
explainCommand.append("verbosity", getVerbosityAsString(explainVerbosity));
31+
}
32+
return explainCommand;
2933
}
3034

3135
private static BsonString getVerbosityAsString(final ExplainVerbosity explainVerbosity) {
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright 2008-present MongoDB, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.mongodb.internal.operation;
18+
19+
import com.mongodb.ExplainVerbosity;
20+
import com.mongodb.lang.Nullable;
21+
import org.bson.codecs.Decoder;
22+
23+
public interface ExplainableReadOperation<T> extends ReadOperation<T> {
24+
<R> ReadOperation<R> asExplainableOperation(@Nullable ExplainVerbosity verbosity, Decoder<R> resultDecoder);
25+
}

driver-core/src/main/com/mongodb/internal/operation/FindOperation.java

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package com.mongodb.internal.operation;
1818

1919
import com.mongodb.CursorType;
20+
import com.mongodb.ExplainVerbosity;
2021
import com.mongodb.MongoCommandException;
2122
import com.mongodb.MongoNamespace;
2223
import com.mongodb.MongoQueryException;
@@ -43,7 +44,6 @@
4344
import org.bson.BsonInt64;
4445
import org.bson.BsonString;
4546
import org.bson.BsonValue;
46-
import org.bson.codecs.BsonDocumentCodec;
4747
import org.bson.codecs.Decoder;
4848

4949
import java.util.concurrent.TimeUnit;
@@ -60,6 +60,7 @@
6060
import static com.mongodb.internal.operation.CommandOperationHelper.executeCommandWithConnection;
6161
import static com.mongodb.internal.operation.DocumentHelper.putIfNotNull;
6262
import static com.mongodb.internal.operation.DocumentHelper.putIfNotNullOrEmpty;
63+
import static com.mongodb.internal.operation.ExplainHelper.asExplainCommand;
6364
import static com.mongodb.internal.operation.OperationHelper.AsyncCallableWithConnectionAndSource;
6465
import static com.mongodb.internal.operation.OperationHelper.LOGGER;
6566
import static com.mongodb.internal.operation.OperationHelper.cursorDocumentToQueryResult;
@@ -76,7 +77,7 @@
7677
* @param <T> the operations result type.
7778
* @since 3.0
7879
*/
79-
public class FindOperation<T> implements AsyncReadOperation<AsyncBatchCursor<T>>, ReadOperation<BatchCursor<T>> {
80+
public class FindOperation<T> implements AsyncExplainableReadOperation<AsyncBatchCursor<T>>, ExplainableReadOperation<BatchCursor<T>> {
8081
private static final String FIRST_BATCH = "firstBatch";
8182

8283
private final MongoNamespace namespace;
@@ -757,16 +758,20 @@ public void onResult(final T result, final Throwable t) {
757758
};
758759
}
759760

761+
@Override
762+
public <R> ReadOperation<R> asExplainableOperation(@Nullable final ExplainVerbosity verbosity,
763+
final Decoder<R> resultDecoder) {
764+
return new CommandReadOperation<R>(getNamespace().getDatabaseName(),
765+
asExplainCommand(getCommand(NoOpSessionContext.INSTANCE), verbosity),
766+
resultDecoder);
767+
}
760768

761-
/**
762-
* Gets an operation whose execution explains this operation.
763-
*
764-
* @return a read operation that when executed will explain this operation
765-
*/
766-
public ReadOperation<BsonDocument> asExplainableOperation() {
767-
return new CommandReadOperation<>(getNamespace().getDatabaseName(),
768-
new BsonDocument("explain", getCommand(NoOpSessionContext.INSTANCE, null)),
769-
new BsonDocumentCodec());
769+
@Override
770+
public <R> AsyncReadOperation<R> asAsyncExplainableOperation(@Nullable final ExplainVerbosity verbosity,
771+
final Decoder<R> resultDecoder) {
772+
return new CommandReadOperation<R>(getNamespace().getDatabaseName(),
773+
asExplainCommand(getCommand(NoOpSessionContext.INSTANCE), verbosity),
774+
resultDecoder);
770775
}
771776

772777
private BsonDocument asDocument(final ConnectionDescription connectionDescription, final ReadPreference readPreference) {
@@ -811,7 +816,7 @@ private BsonDocument asDocument(final ConnectionDescription connectionDescriptio
811816
return document;
812817
}
813818

814-
private BsonDocument getCommand(final SessionContext sessionContext, final ConnectionDescription description) {
819+
private BsonDocument getCommand(final SessionContext sessionContext) {
815820
BsonDocument commandDocument = new BsonDocument("find", new BsonString(namespace.getCollectionName()));
816821

817822
appendReadConcernToCommand(sessionContext, commandDocument);
@@ -885,7 +890,7 @@ private CommandCreator getCommandCreator(final SessionContext sessionContext) {
885890
@Override
886891
public BsonDocument create(final ServerDescription serverDescription, final ConnectionDescription connectionDescription) {
887892
validateFindOptions(connectionDescription, sessionContext.getReadConcern(), collation, allowDiskUse);
888-
return getCommand(sessionContext, connectionDescription);
893+
return getCommand(sessionContext);
889894
}
890895
};
891896
}

driver-core/src/main/com/mongodb/internal/operation/SyncOperations.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ public <TResult> ReadOperation<BatchCursor<TResult>> findFirst(final Bson filter
8888
return operations.findFirst(filter, resultClass, options);
8989
}
9090

91-
public <TResult> ReadOperation<BatchCursor<TResult>> find(final Bson filter, final Class<TResult> resultClass,
91+
public <TResult> ExplainableReadOperation<BatchCursor<TResult>> find(final Bson filter, final Class<TResult> resultClass,
9292
final FindOptions options) {
9393
return operations.find(filter, resultClass, options);
9494
}

driver-core/src/test/functional/com/mongodb/internal/operation/FindOperationSpecification.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ class FindOperationSpecification extends OperationFunctionalSpecification {
404404

405405
def operation = new FindOperation<Document>(getNamespace(), new DocumentCodec())
406406
.hint((BsonValue) hint)
407-
.asExplainableOperation()
407+
.asExplainableOperation(null, new BsonDocumentCodec())
408408

409409
when:
410410
def explainPlan = execute(operation, async)

driver-legacy/src/main/com/mongodb/DBCursor.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
import java.util.NoSuchElementException;
3434
import java.util.concurrent.TimeUnit;
3535

36-
import static com.mongodb.DBObjects.toDBObject;
36+
import static com.mongodb.MongoClient.getDefaultCodecRegistry;
3737
import static com.mongodb.assertions.Assertions.notNull;
3838
import static java.util.concurrent.TimeUnit.MILLISECONDS;
3939

@@ -339,9 +339,9 @@ public DBCursor maxTime(final long maxTime, final TimeUnit timeUnit) {
339339
*/
340340
@Deprecated
341341
public DBObject explain() {
342-
return toDBObject(executor.execute(getQueryOperation(collection.getObjectCodec())
343-
.asExplainableOperation(),
344-
getReadPreference(), getReadConcern()));
342+
return executor.execute(getQueryOperation(collection.getObjectCodec())
343+
.asExplainableOperation(null, getDefaultCodecRegistry().get(DBObject.class)),
344+
getReadPreference(), getReadConcern());
345345
}
346346

347347
/**

driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/FindPublisher.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717
package com.mongodb.reactivestreams.client;
1818

1919
import com.mongodb.CursorType;
20+
import com.mongodb.ExplainVerbosity;
2021
import com.mongodb.client.model.Collation;
2122
import com.mongodb.lang.Nullable;
23+
import org.bson.Document;
2224
import org.bson.conversions.Bson;
2325
import org.reactivestreams.Publisher;
2426

@@ -246,4 +248,50 @@ public interface FindPublisher<TResult> extends Publisher<TResult> {
246248
* @mongodb.server.release 4.4
247249
*/
248250
FindPublisher<TResult> allowDiskUse(@Nullable Boolean allowDiskUse);
251+
252+
/**
253+
* Explain the execution plan for this operation with the server's default verbosity level
254+
*
255+
* @return the execution plan
256+
* @since 4.2
257+
* @mongodb.driver.manual reference/command/explain/
258+
* @mongodb.server.release 3.2
259+
*/
260+
Publisher<Document> explain();
261+
262+
/**
263+
* Explain the execution plan for this operation with the given verbosity level
264+
*
265+
* @param verbosity the verbosity of the explanation
266+
* @return the execution plan
267+
* @since 4.2
268+
* @mongodb.driver.manual reference/command/explain/
269+
* @mongodb.server.release 3.2
270+
*/
271+
Publisher<Document> explain(ExplainVerbosity verbosity);
272+
273+
/**
274+
* Explain the execution plan for this operation with the server's default verbosity level
275+
*
276+
* @param <E> the type of the document class
277+
* @param explainResultClass the document class to decode into
278+
* @return the execution plan
279+
* @since 4.2
280+
* @mongodb.driver.manual reference/command/explain/
281+
* @mongodb.server.release 3.2
282+
*/
283+
<E> Publisher<E> explain(Class<E> explainResultClass);
284+
285+
/**
286+
* Explain the execution plan for this operation with the given verbosity level
287+
*
288+
* @param <E> the type of the document class
289+
* @param explainResultClass the document class to decode into
290+
* @param verbosity the verbosity of the explanation
291+
* @return the execution plan
292+
* @since 4.2
293+
* @mongodb.driver.manual reference/command/explain/
294+
* @mongodb.server.release 3.2
295+
*/
296+
<E> Publisher<E> explain(Class<E> explainResultClass, ExplainVerbosity verbosity);
249297
}

driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/FindPublisherImpl.java

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,18 @@
1717
package com.mongodb.reactivestreams.client.internal;
1818

1919
import com.mongodb.CursorType;
20+
import com.mongodb.ExplainVerbosity;
2021
import com.mongodb.client.model.Collation;
2122
import com.mongodb.internal.async.AsyncBatchCursor;
2223
import com.mongodb.internal.client.model.FindOptions;
24+
import com.mongodb.internal.operation.AsyncExplainableReadOperation;
2325
import com.mongodb.internal.operation.AsyncReadOperation;
2426
import com.mongodb.lang.Nullable;
2527
import com.mongodb.reactivestreams.client.ClientSession;
2628
import com.mongodb.reactivestreams.client.FindPublisher;
29+
import org.bson.Document;
2730
import org.bson.conversions.Bson;
31+
import org.reactivestreams.Publisher;
2832

2933
import java.util.concurrent.TimeUnit;
3034

@@ -172,9 +176,36 @@ public FindPublisher<T> allowDiskUse(@Nullable final Boolean allowDiskUse) {
172176
return this;
173177
}
174178

179+
@Override
180+
public Publisher<Document> explain() {
181+
return publishExplain(Document.class, null);
182+
}
183+
184+
@Override
185+
public Publisher<Document> explain(final ExplainVerbosity verbosity) {
186+
return publishExplain(Document.class, notNull("verbosity", verbosity));
187+
}
188+
189+
@Override
190+
public <E> Publisher<E> explain(final Class<E> explainResultClass) {
191+
return publishExplain(explainResultClass, null);
192+
}
193+
194+
@Override
195+
public <E> Publisher<E> explain(final Class<E> explainResultClass, final ExplainVerbosity verbosity) {
196+
return publishExplain(explainResultClass, notNull("verbosity", verbosity));
197+
}
198+
199+
private <E> Publisher<E> publishExplain(final Class<E> explainResultClass, @Nullable final ExplainVerbosity verbosity) {
200+
notNull("explainDocumentClass", explainResultClass);
201+
return getMongoOperationPublisher().createReadOperationMono(() ->
202+
asAsyncReadOperation().asAsyncExplainableOperation(verbosity,
203+
getCodecRegistry().get(explainResultClass)),
204+
getClientSession());
205+
}
175206

176207
@Override
177-
AsyncReadOperation<AsyncBatchCursor<T>> asAsyncReadOperation() {
208+
AsyncExplainableReadOperation<AsyncBatchCursor<T>> asAsyncReadOperation() {
178209
return getOperations().find(filter, getDocumentClass(), findOptions);
179210
}
180211

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright 2008-present MongoDB, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.mongodb.reactivestreams.client;
18+
19+
import com.mongodb.MongoClientSettings;
20+
import com.mongodb.client.AbstractExplainTest;
21+
import com.mongodb.client.MongoClient;
22+
import com.mongodb.lang.NonNull;
23+
import com.mongodb.reactivestreams.client.syncadapter.SyncMongoClient;
24+
25+
public class ExplainTest extends AbstractExplainTest {
26+
@Override
27+
@NonNull
28+
protected MongoClient createMongoClient(@NonNull final MongoClientSettings settings) {
29+
return new SyncMongoClient(MongoClients.create(settings));
30+
}
31+
}

0 commit comments

Comments
 (0)