Skip to content

Commit 8584982

Browse files
committed
JAVA-2105: When $explain : true is in find operation modifiers, use explain command instead of find command when server version >= 3.2
1 parent 4aa421f commit 8584982

File tree

2 files changed

+85
-11
lines changed

2 files changed

+85
-11
lines changed

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

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,16 @@
4040
import com.mongodb.operation.OperationHelper.CallableWithConnectionAndSource;
4141
import org.bson.BsonBoolean;
4242
import org.bson.BsonDocument;
43+
import org.bson.BsonDocumentReader;
4344
import org.bson.BsonInt32;
4445
import org.bson.BsonInt64;
4546
import org.bson.BsonString;
4647
import org.bson.BsonValue;
4748
import org.bson.codecs.BsonDocumentCodec;
4849
import org.bson.codecs.Decoder;
50+
import org.bson.codecs.DecoderContext;
4951

52+
import java.util.Collections;
5053
import java.util.HashMap;
5154
import java.util.List;
5255
import java.util.Map;
@@ -65,7 +68,6 @@
6568
import static com.mongodb.operation.OperationHelper.cursorDocumentToQueryResult;
6669
import static com.mongodb.operation.OperationHelper.releasingCallback;
6770
import static com.mongodb.operation.OperationHelper.serverIsAtLeastVersionThreeDotTwo;
68-
import static com.mongodb.operation.OperationHelper.serverIsAtLeastVersionThreeDotZero;
6971
import static com.mongodb.operation.OperationHelper.withConnection;
7072

7173
/**
@@ -482,7 +484,8 @@ public BatchCursor<T> execute(final ReadBinding binding) {
482484
public BatchCursor<T> call(final ConnectionSource source, final Connection connection) {
483485
if (serverIsAtLeastVersionThreeDotTwo(connection.getDescription())) {
484486
try {
485-
return executeWrappedCommandProtocol(binding, namespace.getDatabaseName(), asCommandDocument(),
487+
return executeWrappedCommandProtocol(binding, namespace.getDatabaseName(),
488+
wrapInExplainIfNecessary(asCommandDocument()),
486489
CommandResultDocumentCodec.create(decoder, FIRST_BATCH),
487490
connection, transformer(source, connection));
488491
} catch (MongoCommandException e) {
@@ -518,7 +521,8 @@ public void call(final AsyncConnectionSource source, final AsyncConnection conne
518521
errorHandlingCallback(callback).onResult(null, t);
519522
} else {
520523
if (serverIsAtLeastVersionThreeDotTwo(connection.getDescription())) {
521-
executeWrappedCommandProtocolAsync(binding, namespace.getDatabaseName(), asCommandDocument(),
524+
executeWrappedCommandProtocolAsync(binding, namespace.getDatabaseName(),
525+
wrapInExplainIfNecessary(asCommandDocument()),
522526
CommandResultDocumentCodec.create(decoder, FIRST_BATCH),
523527
connection, asyncTransformer(source, connection),
524528
releasingCallback(exceptionTransformingCallback(errorHandlingCallback(callback)),
@@ -595,7 +599,7 @@ public BsonDocument call(final ConnectionSource connectionSource, final Connecti
595599
connectionSource.getServerDescription(),
596600
connection);
597601
try {
598-
if (serverIsAtLeastVersionThreeDotZero(connection.getDescription())) {
602+
if (serverIsAtLeastVersionThreeDotTwo(connection.getDescription())) {
599603
try {
600604
return new CommandReadOperation<BsonDocument>(getNamespace().getDatabaseName(),
601605
new BsonDocument("explain", asCommandDocument()),
@@ -800,6 +804,18 @@ private BsonDocument asCommandDocument() {
800804
return document;
801805
}
802806

807+
private BsonDocument wrapInExplainIfNecessary(final BsonDocument findCommandDocument) {
808+
if (isExplain()) {
809+
return new BsonDocument("explain", findCommandDocument);
810+
} else {
811+
return findCommandDocument;
812+
}
813+
}
814+
815+
private boolean isExplain() {
816+
return modifiers != null && modifiers.get("$explain", BsonBoolean.FALSE).equals(BsonBoolean.TRUE);
817+
}
818+
803819
private boolean isTailableCursor() {
804820
return cursorType.isTailable();
805821
}
@@ -808,13 +824,12 @@ private boolean isAwaitData() {
808824
return cursorType == CursorType.TailableAwait;
809825
}
810826

811-
private CommandTransformer<BsonDocument, BatchCursor<T>> transformer(final ConnectionSource source, final Connection
812-
connection) {
827+
private CommandTransformer<BsonDocument, BatchCursor<T>> transformer(final ConnectionSource source,
828+
final Connection connection) {
813829
return new CommandTransformer<BsonDocument, BatchCursor<T>>() {
814830
@Override
815831
public BatchCursor<T> apply(final BsonDocument result, final ServerAddress serverAddress) {
816-
QueryResult<T> queryResult = cursorDocumentToQueryResult(result.getDocument("cursor"),
817-
connection.getDescription().getServerAddress());
832+
QueryResult<T> queryResult = documentToQueryResult(result, serverAddress);
818833
return new QueryBatchCursor<T>(queryResult, limit, batchSize, getMaxTimeForCursor(), decoder, source, connection);
819834
}
820835
};
@@ -825,17 +840,27 @@ private long getMaxTimeForCursor() {
825840
}
826841

827842
private CommandTransformer<BsonDocument, AsyncBatchCursor<T>> asyncTransformer(final AsyncConnectionSource source,
828-
final AsyncConnection connection) {
843+
final AsyncConnection connection) {
829844
return new CommandTransformer<BsonDocument, AsyncBatchCursor<T>>() {
830845
@Override
831846
public AsyncBatchCursor<T> apply(final BsonDocument result, final ServerAddress serverAddress) {
832-
QueryResult<T> queryResult = cursorDocumentToQueryResult(result.getDocument("cursor"),
833-
connection.getDescription().getServerAddress());
847+
QueryResult<T> queryResult = documentToQueryResult(result, serverAddress);
834848
return new AsyncQueryBatchCursor<T>(queryResult, limit, batchSize, getMaxTimeForCursor(), decoder, source, connection);
835849
}
836850
};
837851
}
838852

853+
private QueryResult<T> documentToQueryResult(final BsonDocument result, final ServerAddress serverAddress) {
854+
QueryResult<T> queryResult;
855+
if (isExplain()) {
856+
T decodedDocument = decoder.decode(new BsonDocumentReader(result), DecoderContext.builder().build());
857+
queryResult = new QueryResult<T>(getNamespace(), Collections.singletonList(decodedDocument), 0, serverAddress);
858+
} else {
859+
queryResult = cursorDocumentToQueryResult(result.getDocument("cursor"), serverAddress);
860+
}
861+
return queryResult;
862+
}
863+
839864
private static class ExplainResultCallback implements SingleResultCallback<AsyncBatchCursor<BsonDocument>> {
840865
private final SingleResultCallback<BsonDocument> callback;
841866

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

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -599,24 +599,67 @@ class FindOperationSpecification extends OperationFunctionalSpecification {
599599
def 'should explain'() {
600600
given:
601601
def findOperation = new FindOperation<Document>(getNamespace(), new DocumentCodec())
602+
.modifiers(modifiers)
602603

603604
when:
604605
BsonDocument result = findOperation.asExplainableOperation(QUERY_PLANNER).execute(getBinding())
605606

606607
then:
607608
result
609+
610+
where:
611+
modifiers << [null, new BsonDocument('$explain', BsonBoolean.TRUE), new BsonDocument('$explain', BsonBoolean.FALSE)]
608612
}
609613

610614
@Category(Async)
611615
def 'should explain asynchronously'() {
612616
given:
613617
def findOperation = new FindOperation<Document>(getNamespace(), new DocumentCodec())
618+
.modifiers(modifiers)
614619

615620
when:
616621
BsonDocument result = executeAsync(findOperation.asExplainableOperationAsync(QUERY_PLANNER))
617622

618623
then:
619624
result
625+
626+
where:
627+
modifiers << [null, new BsonDocument('$explain', BsonBoolean.TRUE), new BsonDocument('$explain', BsonBoolean.FALSE)]
628+
}
629+
630+
def 'should explain with $explain modifier'() {
631+
given:
632+
def findOperation = new FindOperation<BsonDocument>(getNamespace(), new BsonDocumentCodec())
633+
.modifiers(new BsonDocument('$explain', BsonBoolean.TRUE))
634+
635+
when:
636+
def explainResult = findOperation.execute(getBinding()).next().get(0)
637+
638+
then:
639+
sanitizeExplainResult(explainResult) ==
640+
sanitizeExplainResult(new FindOperation<BsonDocument>(getNamespace(), new BsonDocumentCodec())
641+
.asExplainableOperation(QUERY_PLANNER).execute(getBinding()))
642+
}
643+
644+
@Category(Async)
645+
def 'should explain asynchronously with $explain modifier'() {
646+
given:
647+
def findOperation = new FindOperation<BsonDocument>(getNamespace(), new BsonDocumentCodec())
648+
.modifiers(new BsonDocument('$explain', BsonBoolean.TRUE))
649+
650+
when:
651+
def explainResult
652+
loopCursor(findOperation, new Block<BsonDocument>() {
653+
@Override
654+
void apply(final BsonDocument document) {
655+
explainResult = document
656+
}
657+
})
658+
659+
then:
660+
sanitizeExplainResult(explainResult) ==
661+
sanitizeExplainResult(new FindOperation<BsonDocument>(getNamespace(), new BsonDocumentCodec())
662+
.asExplainableOperation(QUERY_PLANNER).execute(getBinding()))
620663
}
621664

622665
// sanity check that the server accepts tailable and await data flags
@@ -902,6 +945,12 @@ class FindOperationSpecification extends OperationFunctionalSpecification {
902945
connectionDescription: Stub(ConnectionDescription)
903946
]
904947

948+
static BsonDocument sanitizeExplainResult(BsonDocument document) {
949+
document.remove('ok')
950+
document.remove('millis')
951+
document
952+
}
953+
905954
static BsonDocument getKeyPattern(BsonDocument explainPlan) {
906955
def winningPlan = explainPlan.getDocument('queryPlanner').getDocument('winningPlan')
907956
if (winningPlan.containsKey('inputStage')) {

0 commit comments

Comments
 (0)