summaryrefslogtreecommitdiff
path: root/src/mongo/db/pipeline
diff options
context:
space:
mode:
authorDavid Storch <david.storch@10gen.com>2017-02-10 11:52:18 -0500
committerDavid Storch <david.storch@10gen.com>2017-03-13 09:46:14 -0400
commit82b16740f8a66093b453a73a04b3b9bd00e5d7a0 (patch)
tree62d156fc9676526ecbea19cd03ef7a293579c4df /src/mongo/db/pipeline
parent73f9e8b8a8422becf8694fe3d82c0e647dc71189 (diff)
downloadmongo-82b16740f8a66093b453a73a04b3b9bd00e5d7a0.tar.gz
SERVER-19758 add support for "executionStats" and "allPlansExecution" to agg explain
Like other explainable commands, aggregate can now be explained using the explain command, e.g. db.runCommand({explain: {aggregate: ...}, verbosity: "executionStats"}). The existing explain:true flag corresponds to "queryPlanner" mode and is still supported. However, explain:true cannot be specified when explaining aggregate via the explain command. Additional execution information is provided only in the $cursor section of the aggregation explain output. Having aggregation stages themselves track and report execution info is further work.
Diffstat (limited to 'src/mongo/db/pipeline')
-rw-r--r--src/mongo/db/pipeline/SConscript6
-rw-r--r--src/mongo/db/pipeline/aggregation_request.cpp51
-rw-r--r--src/mongo/db/pipeline/aggregation_request.h30
-rw-r--r--src/mongo/db/pipeline/aggregation_request_test.cpp105
-rw-r--r--src/mongo/db/pipeline/document_source.cpp3
-rw-r--r--src/mongo/db/pipeline/document_source.h19
-rw-r--r--src/mongo/db/pipeline/document_source_bucket_auto.cpp9
-rw-r--r--src/mongo/db/pipeline/document_source_bucket_auto.h2
-rw-r--r--src/mongo/db/pipeline/document_source_bucket_auto_test.cpp4
-rw-r--r--src/mongo/db/pipeline/document_source_bucket_test.cpp2
-rw-r--r--src/mongo/db/pipeline/document_source_coll_stats.cpp2
-rw-r--r--src/mongo/db/pipeline/document_source_coll_stats.h2
-rw-r--r--src/mongo/db/pipeline/document_source_count_test.cpp2
-rw-r--r--src/mongo/db/pipeline/document_source_cursor.cpp9
-rw-r--r--src/mongo/db/pipeline/document_source_cursor.h2
-rw-r--r--src/mongo/db/pipeline/document_source_facet.cpp6
-rw-r--r--src/mongo/db/pipeline/document_source_facet.h2
-rw-r--r--src/mongo/db/pipeline/document_source_geo_near.cpp2
-rw-r--r--src/mongo/db/pipeline/document_source_geo_near.h2
-rw-r--r--src/mongo/db/pipeline/document_source_graph_lookup.cpp3
-rw-r--r--src/mongo/db/pipeline/document_source_graph_lookup.h6
-rw-r--r--src/mongo/db/pipeline/document_source_group.cpp10
-rw-r--r--src/mongo/db/pipeline/document_source_group.h2
-rw-r--r--src/mongo/db/pipeline/document_source_index_stats.cpp3
-rw-r--r--src/mongo/db/pipeline/document_source_index_stats.h2
-rw-r--r--src/mongo/db/pipeline/document_source_limit.cpp2
-rw-r--r--src/mongo/db/pipeline/document_source_limit.h2
-rw-r--r--src/mongo/db/pipeline/document_source_lookup.cpp3
-rw-r--r--src/mongo/db/pipeline/document_source_lookup.h6
-rw-r--r--src/mongo/db/pipeline/document_source_match.cpp2
-rw-r--r--src/mongo/db/pipeline/document_source_match.h2
-rw-r--r--src/mongo/db/pipeline/document_source_merge_cursors.cpp3
-rw-r--r--src/mongo/db/pipeline/document_source_merge_cursors.h2
-rw-r--r--src/mongo/db/pipeline/document_source_mock.cpp2
-rw-r--r--src/mongo/db/pipeline/document_source_mock.h3
-rw-r--r--src/mongo/db/pipeline/document_source_out.cpp2
-rw-r--r--src/mongo/db/pipeline/document_source_out.h2
-rw-r--r--src/mongo/db/pipeline/document_source_redact.cpp4
-rw-r--r--src/mongo/db/pipeline/document_source_redact.h2
-rw-r--r--src/mongo/db/pipeline/document_source_replace_root.cpp4
-rw-r--r--src/mongo/db/pipeline/document_source_sample.cpp2
-rw-r--r--src/mongo/db/pipeline/document_source_sample.h2
-rw-r--r--src/mongo/db/pipeline/document_source_sample_from_random_cursor.cpp3
-rw-r--r--src/mongo/db/pipeline/document_source_sample_from_random_cursor.h2
-rw-r--r--src/mongo/db/pipeline/document_source_sample_test.cpp2
-rw-r--r--src/mongo/db/pipeline/document_source_single_document_transformation.cpp3
-rw-r--r--src/mongo/db/pipeline/document_source_single_document_transformation.h4
-rw-r--r--src/mongo/db/pipeline/document_source_skip.cpp2
-rw-r--r--src/mongo/db/pipeline/document_source_skip.h2
-rw-r--r--src/mongo/db/pipeline/document_source_sort.cpp17
-rw-r--r--src/mongo/db/pipeline/document_source_sort.h6
-rw-r--r--src/mongo/db/pipeline/document_source_sort_by_count_test.cpp2
-rw-r--r--src/mongo/db/pipeline/document_source_tee_consumer.cpp3
-rw-r--r--src/mongo/db/pipeline/document_source_tee_consumer.h2
-rw-r--r--src/mongo/db/pipeline/document_source_unwind.cpp2
-rw-r--r--src/mongo/db/pipeline/document_source_unwind.h2
-rw-r--r--src/mongo/db/pipeline/expression_context.cpp4
-rw-r--r--src/mongo/db/pipeline/expression_context.h6
-rw-r--r--src/mongo/db/pipeline/parsed_add_fields.h2
-rw-r--r--src/mongo/db/pipeline/parsed_add_fields_test.cpp45
-rw-r--r--src/mongo/db/pipeline/parsed_exclusion_projection.cpp3
-rw-r--r--src/mongo/db/pipeline/parsed_exclusion_projection.h2
-rw-r--r--src/mongo/db/pipeline/parsed_exclusion_projection_test.cpp2
-rw-r--r--src/mongo/db/pipeline/parsed_inclusion_projection.cpp5
-rw-r--r--src/mongo/db/pipeline/parsed_inclusion_projection.h5
-rw-r--r--src/mongo/db/pipeline/parsed_inclusion_projection_test.cpp36
-rw-r--r--src/mongo/db/pipeline/pipeline.cpp4
-rw-r--r--src/mongo/db/pipeline/pipeline.h7
-rw-r--r--src/mongo/db/pipeline/pipeline_test.cpp11
69 files changed, 369 insertions, 146 deletions
diff --git a/src/mongo/db/pipeline/SConscript b/src/mongo/db/pipeline/SConscript
index 49783bc5def..e243adda24a 100644
--- a/src/mongo/db/pipeline/SConscript
+++ b/src/mongo/db/pipeline/SConscript
@@ -85,13 +85,15 @@ env.Library(
'aggregation_request.cpp',
],
LIBDEPS=[
- 'document_value',
'$BUILD_DIR/mongo/base',
- '$BUILD_DIR/mongo/db/query/command_request_response',
'$BUILD_DIR/mongo/db/namespace_string',
+ '$BUILD_DIR/mongo/db/query/command_request_response',
+ '$BUILD_DIR/mongo/db/query/explain_options',
'$BUILD_DIR/mongo/db/query/query_request',
'$BUILD_DIR/mongo/db/repl/read_concern_args',
'$BUILD_DIR/mongo/db/storage/storage_options',
+ '$BUILD_DIR/mongo/db/write_concern_options',
+ 'document_value',
]
)
diff --git a/src/mongo/db/pipeline/aggregation_request.cpp b/src/mongo/db/pipeline/aggregation_request.cpp
index 947cfbb3f81..920073c6558 100644
--- a/src/mongo/db/pipeline/aggregation_request.cpp
+++ b/src/mongo/db/pipeline/aggregation_request.cpp
@@ -43,6 +43,7 @@
#include "mongo/db/query/query_request.h"
#include "mongo/db/repl/read_concern_args.h"
#include "mongo/db/storage/storage_options.h"
+#include "mongo/db/write_concern_options.h"
namespace mongo {
@@ -61,8 +62,10 @@ const long long AggregationRequest::kDefaultBatchSize = 101;
AggregationRequest::AggregationRequest(NamespaceString nss, std::vector<BSONObj> pipeline)
: _nss(std::move(nss)), _pipeline(std::move(pipeline)), _batchSize(kDefaultBatchSize) {}
-StatusWith<AggregationRequest> AggregationRequest::parseFromBSON(NamespaceString nss,
- const BSONObj& cmdObj) {
+StatusWith<AggregationRequest> AggregationRequest::parseFromBSON(
+ NamespaceString nss,
+ const BSONObj& cmdObj,
+ boost::optional<ExplainOptions::Verbosity> explainVerbosity) {
// Parse required parameters.
auto pipelineElem = cmdObj[kPipelineName];
if (pipelineElem.eoo() || pipelineElem.type() != BSONType::Array) {
@@ -81,12 +84,13 @@ StatusWith<AggregationRequest> AggregationRequest::parseFromBSON(NamespaceString
const std::initializer_list<StringData> optionsParsedElseWhere = {
QueryRequest::cmdOptionMaxTimeMS,
- "writeConcern"_sd,
+ WriteConcernOptions::kWriteConcernField,
kPipelineName,
kCommandName,
repl::ReadConcernArgs::kReadConcernFieldName};
bool hasCursorElem = false;
+ bool hasExplainElem = false;
// Parse optional parameters.
for (auto&& elem : cmdObj) {
@@ -138,7 +142,11 @@ StatusWith<AggregationRequest> AggregationRequest::parseFromBSON(NamespaceString
str::stream() << kExplainName << " must be a boolean, not a "
<< typeName(elem.type())};
}
- request.setExplain(elem.Bool());
+
+ hasExplainElem = true;
+ if (elem.Bool()) {
+ request.setExplain(ExplainOptions::Verbosity::kQueryPlanner);
+ }
} else if (kFromRouterName == fieldName) {
if (elem.type() != BSONType::Bool) {
return {ErrorCodes::TypeMismatch,
@@ -165,11 +173,35 @@ StatusWith<AggregationRequest> AggregationRequest::parseFromBSON(NamespaceString
}
}
- if (!hasCursorElem && !request.isExplain()) {
+ if (explainVerbosity) {
+ if (hasExplainElem) {
+ return {
+ ErrorCodes::FailedToParse,
+ str::stream() << "The '" << kExplainName
+ << "' option is illegal when a explain verbosity is also provided"};
+ }
+
+ request.setExplain(explainVerbosity);
+ }
+
+ if (!hasCursorElem && !request.getExplain()) {
+ return {ErrorCodes::FailedToParse,
+ str::stream() << "The '" << kCursorName
+ << "' option is required, except for aggregation explain"};
+ }
+
+ if (request.getExplain() && cmdObj[repl::ReadConcernArgs::kReadConcernFieldName]) {
+ return {ErrorCodes::FailedToParse,
+ str::stream() << "Aggregation explain does not support the '"
+ << repl::ReadConcernArgs::kReadConcernFieldName
+ << "' option"};
+ }
+
+ if (request.getExplain() && cmdObj[WriteConcernOptions::kWriteConcernField]) {
return {ErrorCodes::FailedToParse,
- str::stream() << "The '" << kCursorName << "' option is required, unless '"
- << kExplainName
- << "' is true"};
+ str::stream() << "Aggregation explain does not support the'"
+ << WriteConcernOptions::kWriteConcernField
+ << "' option"};
}
return request;
@@ -181,7 +213,6 @@ Document AggregationRequest::serializeToCommandObj() const {
{kCommandName, _nss.coll()},
{kPipelineName, _pipeline},
// Only serialize booleans if different than their default.
- {kExplainName, _explain ? Value(true) : Value()},
{kAllowDiskUseName, _allowDiskUse ? Value(true) : Value()},
{kFromRouterName, _fromRouter ? Value(true) : Value()},
{bypassDocumentValidationCommandOption(),
@@ -189,7 +220,7 @@ Document AggregationRequest::serializeToCommandObj() const {
// Only serialize a collation if one was specified.
{kCollationName, _collation.isEmpty() ? Value() : Value(_collation)},
// Only serialize batchSize when explain is false.
- {kCursorName, _explain ? Value() : Value(Document{{kBatchSizeName, _batchSize}})},
+ {kCursorName, _explainMode ? Value() : Value(Document{{kBatchSizeName, _batchSize}})},
// Only serialize a hint if one was specified.
{kHintName, _hint.isEmpty() ? Value() : Value(_hint)}};
}
diff --git a/src/mongo/db/pipeline/aggregation_request.h b/src/mongo/db/pipeline/aggregation_request.h
index b25eaa020dc..156d397cbb0 100644
--- a/src/mongo/db/pipeline/aggregation_request.h
+++ b/src/mongo/db/pipeline/aggregation_request.h
@@ -34,6 +34,7 @@
#include "mongo/bson/bsonelement.h"
#include "mongo/bson/bsonobj.h"
#include "mongo/db/namespace_string.h"
+#include "mongo/db/query/explain_options.h"
namespace mongo {
@@ -62,8 +63,15 @@ public:
* Create a new instance of AggregationRequest by parsing the raw command object. Returns a
* non-OK status if a required field was missing, if there was an unrecognized field name or if
* there was a bad value for one of the fields.
+ *
+ * If we are parsing a request for an explained aggregation with an explain verbosity provided,
+ * then 'explainVerbosity' contains this information. In this case, 'cmdObj' may not itself
+ * contain the explain specifier. Otherwise, 'explainVerbosity' should be boost::none.
*/
- static StatusWith<AggregationRequest> parseFromBSON(NamespaceString nss, const BSONObj& cmdObj);
+ static StatusWith<AggregationRequest> parseFromBSON(
+ NamespaceString nss,
+ const BSONObj& cmdObj,
+ boost::optional<ExplainOptions::Verbosity> explainVerbosity = boost::none);
/**
* Constructs an AggregationRequest over the given namespace with the given pipeline. All
@@ -75,6 +83,9 @@ public:
* Serializes the options to a Document. Note that this serialization includes the original
* pipeline object, as specified. Callers will likely want to override this field with a
* serialization of a parsed and optimized Pipeline object.
+ *
+ * The explain option is not serialized. Since the explain command format is {explain:
+ * {aggregate: ...}, ...}, explain options are not part of the aggregate command object.
*/
Document serializeToCommandObj() const;
@@ -97,10 +108,6 @@ public:
return _pipeline;
}
- bool isExplain() const {
- return _explain;
- }
-
bool isFromRouter() const {
return _fromRouter;
}
@@ -124,6 +131,10 @@ public:
return _hint;
}
+ boost::optional<ExplainOptions::Verbosity> getExplain() const {
+ return _explainMode;
+ }
+
//
// Setters for optional fields.
//
@@ -144,8 +155,8 @@ public:
_hint = hint.getOwned();
}
- void setExplain(bool isExplain) {
- _explain = isExplain;
+ void setExplain(boost::optional<ExplainOptions::Verbosity> verbosity) {
+ _explainMode = verbosity;
}
void setAllowDiskUse(bool allowDiskUse) {
@@ -162,7 +173,6 @@ public:
private:
// Required fields.
-
const NamespaceString _nss;
// An unparsed version of the pipeline.
@@ -181,7 +191,9 @@ private:
// {$hint: <String>}, where <String> is the index name hinted.
BSONObj _hint;
- bool _explain = false;
+ // The explain mode to use, or boost::none if this is not a request for an aggregation explain.
+ boost::optional<ExplainOptions::Verbosity> _explainMode;
+
bool _allowDiskUse = false;
bool _fromRouter = false;
bool _bypassDocumentValidation = false;
diff --git a/src/mongo/db/pipeline/aggregation_request_test.cpp b/src/mongo/db/pipeline/aggregation_request_test.cpp
index 45313fc8a1e..a40bca8d8d1 100644
--- a/src/mongo/db/pipeline/aggregation_request_test.cpp
+++ b/src/mongo/db/pipeline/aggregation_request_test.cpp
@@ -58,7 +58,8 @@ TEST(AggregationRequestTest, ShouldParseAllKnownOptions) {
"bypassDocumentValidation: true, collation: {locale: 'en_US'}, cursor: {batchSize: 10}, "
"hint: {a: 1}}");
auto request = unittest::assertGet(AggregationRequest::parseFromBSON(nss, inputBson));
- ASSERT_TRUE(request.isExplain());
+ ASSERT_TRUE(request.getExplain());
+ ASSERT(*request.getExplain() == ExplainOptions::Verbosity::kQueryPlanner);
ASSERT_TRUE(request.shouldAllowDiskUse());
ASSERT_TRUE(request.isFromRouter());
ASSERT_TRUE(request.shouldBypassDocumentValidation());
@@ -69,6 +70,33 @@ TEST(AggregationRequestTest, ShouldParseAllKnownOptions) {
<< "en_US"));
}
+TEST(AggregationRequestTest, ShouldParseExplicitExplainFalseWithCursorOption) {
+ NamespaceString nss("a.collection");
+ const BSONObj inputBson = fromjson("{pipeline: [], explain: false, cursor: {batchSize: 10}}");
+ auto request = unittest::assertGet(AggregationRequest::parseFromBSON(nss, inputBson));
+ ASSERT_FALSE(request.getExplain());
+ ASSERT_EQ(request.getBatchSize(), 10);
+}
+
+TEST(AggregationRequestTest, ShouldParseWithSeparateQueryPlannerExplainModeArg) {
+ NamespaceString nss("a.collection");
+ const BSONObj inputBson = fromjson("{pipeline: []}");
+ auto request = unittest::assertGet(AggregationRequest::parseFromBSON(
+ nss, inputBson, ExplainOptions::Verbosity::kQueryPlanner));
+ ASSERT_TRUE(request.getExplain());
+ ASSERT(*request.getExplain() == ExplainOptions::Verbosity::kQueryPlanner);
+}
+
+TEST(AggregationRequestTest, ShouldParseWithSeparateQueryPlannerExplainModeArgAndCursorOption) {
+ NamespaceString nss("a.collection");
+ const BSONObj inputBson = fromjson("{pipeline: [], cursor: {batchSize: 10}}");
+ auto request = unittest::assertGet(
+ AggregationRequest::parseFromBSON(nss, inputBson, ExplainOptions::Verbosity::kExecStats));
+ ASSERT_TRUE(request.getExplain());
+ ASSERT(*request.getExplain() == ExplainOptions::Verbosity::kExecStats);
+ ASSERT_EQ(request.getBatchSize(), 10);
+}
+
//
// Serialization
//
@@ -87,7 +115,7 @@ TEST(AggregationRequestTest, ShouldOnlySerializeRequiredFieldsIfNoOptionalFields
TEST(AggregationRequestTest, ShouldNotSerializeOptionalValuesIfEquivalentToDefault) {
NamespaceString nss("a.collection");
AggregationRequest request(nss, {});
- request.setExplain(false);
+ request.setExplain(boost::none);
request.setAllowDiskUse(false);
request.setFromRouter(false);
request.setBypassDocumentValidation(false);
@@ -104,11 +132,10 @@ TEST(AggregationRequestTest, ShouldNotSerializeOptionalValuesIfEquivalentToDefau
TEST(AggregationRequestTest, ShouldSerializeOptionalValuesIfSet) {
NamespaceString nss("a.collection");
AggregationRequest request(nss, {});
- request.setExplain(true);
request.setAllowDiskUse(true);
request.setFromRouter(true);
request.setBypassDocumentValidation(true);
- request.setBatchSize(10); // batchSize not serialzed when explain is true.
+ request.setBatchSize(10);
const auto hintObj = BSON("a" << 1);
request.setHint(hintObj);
const auto collationObj = BSON("locale"
@@ -118,11 +145,12 @@ TEST(AggregationRequestTest, ShouldSerializeOptionalValuesIfSet) {
auto expectedSerialization =
Document{{AggregationRequest::kCommandName, nss.coll()},
{AggregationRequest::kPipelineName, Value(std::vector<Value>{})},
- {AggregationRequest::kExplainName, true},
{AggregationRequest::kAllowDiskUseName, true},
{AggregationRequest::kFromRouterName, true},
{bypassDocumentValidationCommandOption(), true},
{AggregationRequest::kCollationName, collationObj},
+ {AggregationRequest::kCursorName,
+ Value(Document({{AggregationRequest::kBatchSizeName, 10}}))},
{AggregationRequest::kHintName, hintObj}};
ASSERT_DOCUMENT_EQ(request.serializeToCommandObj(), expectedSerialization);
}
@@ -159,6 +187,18 @@ TEST(AggregationRequestTest, ShouldAcceptHintAsString) {
<< "a_1"));
}
+TEST(AggregationRequestTest, ShouldNotSerializeBatchSizeOrExplainWhenExplainSet) {
+ NamespaceString nss("a.collection");
+ AggregationRequest request(nss, {});
+ request.setBatchSize(10);
+ request.setExplain(ExplainOptions::Verbosity::kQueryPlanner);
+
+ auto expectedSerialization =
+ Document{{AggregationRequest::kCommandName, nss.coll()},
+ {AggregationRequest::kPipelineName, Value(std::vector<Value>{})}};
+ ASSERT_DOCUMENT_EQ(request.serializeToCommandObj(), expectedSerialization);
+}
+
//
// Error cases.
//
@@ -201,13 +241,20 @@ TEST(AggregationRequestTest, ShouldRejectHintAsArray) {
AggregationRequest::parseFromBSON(NamespaceString("a.collection"), inputBson).getStatus());
}
-TEST(AggregationRequestTest, ShouldRejectNonBoolExplain) {
+TEST(AggregationRequestTest, ShouldRejectExplainIfNumber) {
NamespaceString nss("a.collection");
const BSONObj inputBson =
fromjson("{pipeline: [{$match: {a: 'abc'}}], cursor: {}, explain: 1}");
ASSERT_NOT_OK(AggregationRequest::parseFromBSON(nss, inputBson).getStatus());
}
+TEST(AggregationRequestTest, ShouldRejectExplainIfObject) {
+ NamespaceString nss("a.collection");
+ const BSONObj inputBson =
+ fromjson("{pipeline: [{$match: {a: 'abc'}}], cursor: {}, explain: {}}");
+ ASSERT_NOT_OK(AggregationRequest::parseFromBSON(nss, inputBson).getStatus());
+}
+
TEST(AggregationRequestTest, ShouldRejectNonBoolFromRouter) {
NamespaceString nss("a.collection");
const BSONObj inputBson =
@@ -228,6 +275,52 @@ TEST(AggregationRequestTest, ShouldRejectNoCursorNoExplain) {
ASSERT_NOT_OK(AggregationRequest::parseFromBSON(nss, inputBson).getStatus());
}
+TEST(AggregationRequestTest, ShouldRejectExplainTrueWithSeparateExplainArg) {
+ NamespaceString nss("a.collection");
+ const BSONObj inputBson = fromjson("{pipeline: [], explain: true}");
+ ASSERT_NOT_OK(
+ AggregationRequest::parseFromBSON(nss, inputBson, ExplainOptions::Verbosity::kExecStats)
+ .getStatus());
+}
+
+TEST(AggregationRequestTest, ShouldRejectExplainFalseWithSeparateExplainArg) {
+ NamespaceString nss("a.collection");
+ const BSONObj inputBson = fromjson("{pipeline: [], explain: false}");
+ ASSERT_NOT_OK(
+ AggregationRequest::parseFromBSON(nss, inputBson, ExplainOptions::Verbosity::kExecStats)
+ .getStatus());
+}
+
+TEST(AggregationRequestTest, ShouldRejectExplainWithReadConcernMajority) {
+ NamespaceString nss("a.collection");
+ const BSONObj inputBson =
+ fromjson("{pipeline: [], explain: true, readConcern: {level: 'majority'}}");
+ ASSERT_NOT_OK(AggregationRequest::parseFromBSON(nss, inputBson).getStatus());
+}
+
+TEST(AggregationRequestTest, ShouldRejectExplainExecStatsVerbosityWithReadConcernMajority) {
+ NamespaceString nss("a.collection");
+ const BSONObj inputBson = fromjson("{pipeline: [], readConcern: {level: 'majority'}}");
+ ASSERT_NOT_OK(
+ AggregationRequest::parseFromBSON(nss, inputBson, ExplainOptions::Verbosity::kExecStats)
+ .getStatus());
+}
+
+TEST(AggregationRequestTest, ShouldRejectExplainWithWriteConcernMajority) {
+ NamespaceString nss("a.collection");
+ const BSONObj inputBson =
+ fromjson("{pipeline: [], explain: true, writeConcern: {w: 'majority'}}");
+ ASSERT_NOT_OK(AggregationRequest::parseFromBSON(nss, inputBson).getStatus());
+}
+
+TEST(AggregationRequestTest, ShouldRejectExplainExecStatsVerbosityWithWriteConcernMajority) {
+ NamespaceString nss("a.collection");
+ const BSONObj inputBson = fromjson("{pipeline: [], writeConcern: {w: 'majority'}}");
+ ASSERT_NOT_OK(
+ AggregationRequest::parseFromBSON(nss, inputBson, ExplainOptions::Verbosity::kExecStats)
+ .getStatus());
+}
+
//
// Ignore fields parsed elsewhere.
//
diff --git a/src/mongo/db/pipeline/document_source.cpp b/src/mongo/db/pipeline/document_source.cpp
index 440dc762175..ec1d8edfcb3 100644
--- a/src/mongo/db/pipeline/document_source.cpp
+++ b/src/mongo/db/pipeline/document_source.cpp
@@ -198,7 +198,8 @@ void DocumentSource::dispose() {
}
}
-void DocumentSource::serializeToArray(vector<Value>& array, bool explain) const {
+void DocumentSource::serializeToArray(vector<Value>& array,
+ boost::optional<ExplainOptions::Verbosity> explain) const {
Value entry = serialize(explain);
if (!entry.missing()) {
array.push_back(entry);
diff --git a/src/mongo/db/pipeline/document_source.h b/src/mongo/db/pipeline/document_source.h
index 0dd3b7b3154..b6ef71d1480 100644
--- a/src/mongo/db/pipeline/document_source.h
+++ b/src/mongo/db/pipeline/document_source.h
@@ -49,6 +49,7 @@
#include "mongo/db/pipeline/lite_parsed_document_source.h"
#include "mongo/db/pipeline/pipeline.h"
#include "mongo/db/pipeline/value.h"
+#include "mongo/db/query/explain_options.h"
#include "mongo/stdx/functional.h"
#include "mongo/util/intrusive_counter.h"
@@ -244,11 +245,15 @@ public:
/**
* In the default case, serializes the DocumentSource and adds it to the std::vector<Value>.
*
- * A subclass may choose to overwrite this, rather than serialize,
- * if it should output multiple stages (eg, $sort sometimes also outputs a $limit).
+ * A subclass may choose to overwrite this, rather than serialize, if it should output multiple
+ * stages (eg, $sort sometimes also outputs a $limit).
+ *
+ * The 'explain' parameter indicates the explain verbosity mode, or is equal boost::none if no
+ * explain is requested.
*/
-
- virtual void serializeToArray(std::vector<Value>& array, bool explain = false) const;
+ virtual void serializeToArray(
+ std::vector<Value>& array,
+ boost::optional<ExplainOptions::Verbosity> explain = boost::none) const;
/**
* Returns true if doesn't require an input source (most DocumentSources do).
@@ -473,8 +478,12 @@ private:
* This is used by the default implementation of serializeToArray() to add this object
* to a pipeline being serialized. Returning a missing() Value results in no entry
* being added to the array for this stage (DocumentSource).
+ *
+ * The 'explain' parameter indicates the explain verbosity mode, or is equal boost::none if no
+ * explain is requested.
*/
- virtual Value serialize(bool explain = false) const = 0;
+ virtual Value serialize(
+ boost::optional<ExplainOptions::Verbosity> explain = boost::none) const = 0;
};
/** This class marks DocumentSources that should be split between the merger and the shards.
diff --git a/src/mongo/db/pipeline/document_source_bucket_auto.cpp b/src/mongo/db/pipeline/document_source_bucket_auto.cpp
index b02e8d0226f..f2015df5f64 100644
--- a/src/mongo/db/pipeline/document_source_bucket_auto.cpp
+++ b/src/mongo/db/pipeline/document_source_bucket_auto.cpp
@@ -333,10 +333,11 @@ void DocumentSourceBucketAuto::dispose() {
pSource->dispose();
}
-Value DocumentSourceBucketAuto::serialize(bool explain) const {
+Value DocumentSourceBucketAuto::serialize(
+ boost::optional<ExplainOptions::Verbosity> explain) const {
MutableDocument insides;
- insides["groupBy"] = _groupByExpression->serialize(explain);
+ insides["groupBy"] = _groupByExpression->serialize(static_cast<bool>(explain));
insides["buckets"] = Value(_nBuckets);
if (_granularityRounder) {
@@ -347,8 +348,8 @@ Value DocumentSourceBucketAuto::serialize(bool explain) const {
MutableDocument outputSpec(nOutputFields);
for (size_t i = 0; i < nOutputFields; i++) {
intrusive_ptr<Accumulator> accum = _accumulatorFactories[i](pExpCtx);
- outputSpec[_fieldNames[i]] =
- Value{Document{{accum->getOpName(), _expressions[i]->serialize(explain)}}};
+ outputSpec[_fieldNames[i]] = Value{
+ Document{{accum->getOpName(), _expressions[i]->serialize(static_cast<bool>(explain))}}};
}
insides["output"] = outputSpec.freezeToValue();
diff --git a/src/mongo/db/pipeline/document_source_bucket_auto.h b/src/mongo/db/pipeline/document_source_bucket_auto.h
index 61f7da30c71..75c3f07b683 100644
--- a/src/mongo/db/pipeline/document_source_bucket_auto.h
+++ b/src/mongo/db/pipeline/document_source_bucket_auto.h
@@ -43,7 +43,7 @@ namespace mongo {
*/
class DocumentSourceBucketAuto final : public DocumentSource, public SplittableDocumentSource {
public:
- Value serialize(bool explain = false) const final;
+ Value serialize(boost::optional<ExplainOptions::Verbosity> explain = boost::none) const final;
GetDepsReturn getDependencies(DepsTracker* deps) const final;
GetNextResult getNext() final;
void dispose() final;
diff --git a/src/mongo/db/pipeline/document_source_bucket_auto_test.cpp b/src/mongo/db/pipeline/document_source_bucket_auto_test.cpp
index 2d71b676024..debd2eede82 100644
--- a/src/mongo/db/pipeline/document_source_bucket_auto_test.cpp
+++ b/src/mongo/db/pipeline/document_source_bucket_auto_test.cpp
@@ -87,9 +87,9 @@ public:
auto bucketAutoStage = createBucketAuto(bucketAutoSpec);
assertBucketAutoType(bucketAutoStage);
- const bool explain = true;
vector<Value> explainedStages;
- bucketAutoStage->serializeToArray(explainedStages, explain);
+ bucketAutoStage->serializeToArray(explainedStages,
+ ExplainOptions::Verbosity::kQueryPlanner);
ASSERT_EQUALS(explainedStages.size(), 1UL);
Value expectedExplain = Value(expectedObj);
diff --git a/src/mongo/db/pipeline/document_source_bucket_test.cpp b/src/mongo/db/pipeline/document_source_bucket_test.cpp
index bdfb3635bb7..1a61934dcb5 100644
--- a/src/mongo/db/pipeline/document_source_bucket_test.cpp
+++ b/src/mongo/db/pipeline/document_source_bucket_test.cpp
@@ -66,7 +66,7 @@ public:
// Serialize the DocumentSourceGroup and DocumentSourceSort from $bucket so that we can
// check the explain output to make sure $group and $sort have the correct fields.
- const bool explain = true;
+ auto explain = ExplainOptions::Verbosity::kQueryPlanner;
vector<Value> explainedStages;
groupStage->serializeToArray(explainedStages, explain);
sortStage->serializeToArray(explainedStages, explain);
diff --git a/src/mongo/db/pipeline/document_source_coll_stats.cpp b/src/mongo/db/pipeline/document_source_coll_stats.cpp
index 30ebb4916fc..56ebaca69e3 100644
--- a/src/mongo/db/pipeline/document_source_coll_stats.cpp
+++ b/src/mongo/db/pipeline/document_source_coll_stats.cpp
@@ -126,7 +126,7 @@ bool DocumentSourceCollStats::isValidInitialSource() const {
return true;
}
-Value DocumentSourceCollStats::serialize(bool explain) const {
+Value DocumentSourceCollStats::serialize(boost::optional<ExplainOptions::Verbosity> explain) const {
return Value(Document{{getSourceName(), _collStatsSpec}});
}
diff --git a/src/mongo/db/pipeline/document_source_coll_stats.h b/src/mongo/db/pipeline/document_source_coll_stats.h
index d8cf2eb6718..9e5aa2a4f3f 100644
--- a/src/mongo/db/pipeline/document_source_coll_stats.h
+++ b/src/mongo/db/pipeline/document_source_coll_stats.h
@@ -63,7 +63,7 @@ public:
bool isValidInitialSource() const final;
- Value serialize(bool explain = false) const;
+ Value serialize(boost::optional<ExplainOptions::Verbosity> explain = boost::none) const final;
static boost::intrusive_ptr<DocumentSource> createFromBson(
BSONElement elem, const boost::intrusive_ptr<ExpressionContext>& pExpCtx);
diff --git a/src/mongo/db/pipeline/document_source_count_test.cpp b/src/mongo/db/pipeline/document_source_count_test.cpp
index 5535ee5ea83..e083399b0fa 100644
--- a/src/mongo/db/pipeline/document_source_count_test.cpp
+++ b/src/mongo/db/pipeline/document_source_count_test.cpp
@@ -63,7 +63,7 @@ public:
dynamic_cast<DocumentSourceSingleDocumentTransformation*>(result[1].get());
ASSERT(projectStage);
- const bool explain = true;
+ auto explain = ExplainOptions::Verbosity::kQueryPlanner;
vector<Value> explainedStages;
groupStage->serializeToArray(explainedStages, explain);
projectStage->serializeToArray(explainedStages, explain);
diff --git a/src/mongo/db/pipeline/document_source_cursor.cpp b/src/mongo/db/pipeline/document_source_cursor.cpp
index 276a16196bc..ae62da0a978 100644
--- a/src/mongo/db/pipeline/document_source_cursor.cpp
+++ b/src/mongo/db/pipeline/document_source_cursor.cpp
@@ -174,7 +174,7 @@ void DocumentSourceCursor::recordPlanSummaryStats() {
_planSummaryStats.hasSortStage = hasSortStage;
}
-Value DocumentSourceCursor::serialize(bool explain) const {
+Value DocumentSourceCursor::serialize(boost::optional<ExplainOptions::Verbosity> explain) const {
// we never parse a documentSourceCursor, so we only serialize for explain
if (!explain)
return Value();
@@ -187,8 +187,7 @@ Value DocumentSourceCursor::serialize(bool explain) const {
massert(17392, "No _exec. Were we disposed before explained?", _exec);
_exec->restoreState();
- Explain::explainStages(
- _exec.get(), autoColl.getCollection(), ExplainCommon::QUERY_PLANNER, &explainBuilder);
+ Explain::explainStages(_exec.get(), autoColl.getCollection(), *explain, &explainBuilder);
_exec->saveState();
}
@@ -209,6 +208,10 @@ Value DocumentSourceCursor::serialize(bool explain) const {
BSONObj explainObj = explainBuilder.obj();
invariant(explainObj.hasField("queryPlanner"));
out["queryPlanner"] = Value(explainObj["queryPlanner"]);
+ if (*explain >= ExplainOptions::Verbosity::kExecStats) {
+ invariant(explainObj.hasField("executionStats"));
+ out["executionStats"] = Value(explainObj["executionStats"]);
+ }
return Value(DOC(getSourceName() << out.freezeToValue()));
}
diff --git a/src/mongo/db/pipeline/document_source_cursor.h b/src/mongo/db/pipeline/document_source_cursor.h
index 360b855ad39..3faeea86de6 100644
--- a/src/mongo/db/pipeline/document_source_cursor.h
+++ b/src/mongo/db/pipeline/document_source_cursor.h
@@ -58,7 +58,7 @@ public:
*/
Pipeline::SourceContainer::iterator doOptimizeAt(Pipeline::SourceContainer::iterator itr,
Pipeline::SourceContainer* container) final;
- Value serialize(bool explain = false) const final;
+ Value serialize(boost::optional<ExplainOptions::Verbosity> explain = boost::none) const final;
bool isValidInitialSource() const final {
return true;
}
diff --git a/src/mongo/db/pipeline/document_source_facet.cpp b/src/mongo/db/pipeline/document_source_facet.cpp
index 9b03b233051..0842f7f68bf 100644
--- a/src/mongo/db/pipeline/document_source_facet.cpp
+++ b/src/mongo/db/pipeline/document_source_facet.cpp
@@ -179,11 +179,11 @@ DocumentSource::GetNextResult DocumentSourceFacet::getNext() {
return resultDoc.freeze();
}
-Value DocumentSourceFacet::serialize(bool explain) const {
+Value DocumentSourceFacet::serialize(boost::optional<ExplainOptions::Verbosity> explain) const {
MutableDocument serialized;
for (auto&& facet : _facets) {
- serialized[facet.name] =
- Value(explain ? facet.pipeline->writeExplainOps() : facet.pipeline->serialize());
+ serialized[facet.name] = Value(explain ? facet.pipeline->writeExplainOps(*explain)
+ : facet.pipeline->serialize());
}
return Value(Document{{"$facet", serialized.freezeToValue()}});
}
diff --git a/src/mongo/db/pipeline/document_source_facet.h b/src/mongo/db/pipeline/document_source_facet.h
index 921b2e87609..4da558e37a1 100644
--- a/src/mongo/db/pipeline/document_source_facet.h
+++ b/src/mongo/db/pipeline/document_source_facet.h
@@ -135,7 +135,7 @@ private:
DocumentSourceFacet(std::vector<FacetPipeline> facetPipelines,
const boost::intrusive_ptr<ExpressionContext>& expCtx);
- Value serialize(bool explain = false) const final;
+ Value serialize(boost::optional<ExplainOptions::Verbosity> explain = boost::none) const final;
boost::intrusive_ptr<TeeBuffer> _teeBuffer;
std::vector<FacetPipeline> _facets;
diff --git a/src/mongo/db/pipeline/document_source_geo_near.cpp b/src/mongo/db/pipeline/document_source_geo_near.cpp
index 17e9997bbf5..c971509e373 100644
--- a/src/mongo/db/pipeline/document_source_geo_near.cpp
+++ b/src/mongo/db/pipeline/document_source_geo_near.cpp
@@ -97,7 +97,7 @@ intrusive_ptr<DocumentSource> DocumentSourceGeoNear::getMergeSource() {
return DocumentSourceSort::create(pExpCtx, BSON(distanceField->fullPath() << 1), limit);
}
-Value DocumentSourceGeoNear::serialize(bool explain) const {
+Value DocumentSourceGeoNear::serialize(boost::optional<ExplainOptions::Verbosity> explain) const {
MutableDocument result;
if (coordsIsArray) {
diff --git a/src/mongo/db/pipeline/document_source_geo_near.h b/src/mongo/db/pipeline/document_source_geo_near.h
index c38c311a4e5..03bcf304eee 100644
--- a/src/mongo/db/pipeline/document_source_geo_near.h
+++ b/src/mongo/db/pipeline/document_source_geo_near.h
@@ -49,7 +49,7 @@ public:
bool isValidInitialSource() const final {
return true;
}
- Value serialize(bool explain = false) const final;
+ Value serialize(boost::optional<ExplainOptions::Verbosity> explain = boost::none) const final;
BSONObjSet getOutputSorts() final {
return SimpleBSONObjComparator::kInstance.makeBSONObjSet(
{BSON(distanceField->fullPath() << -1)});
diff --git a/src/mongo/db/pipeline/document_source_graph_lookup.cpp b/src/mongo/db/pipeline/document_source_graph_lookup.cpp
index a11af0a44c3..e8e2cacc108 100644
--- a/src/mongo/db/pipeline/document_source_graph_lookup.cpp
+++ b/src/mongo/db/pipeline/document_source_graph_lookup.cpp
@@ -390,7 +390,8 @@ void DocumentSourceGraphLookUp::checkMemoryUsage() {
_cache.evictDownTo(_maxMemoryUsageBytes - _frontierUsageBytes - _visitedUsageBytes);
}
-void DocumentSourceGraphLookUp::serializeToArray(std::vector<Value>& array, bool explain) const {
+void DocumentSourceGraphLookUp::serializeToArray(
+ std::vector<Value>& array, boost::optional<ExplainOptions::Verbosity> explain) const {
// Serialize default options.
MutableDocument spec(DOC("from" << _from.coll() << "as" << _as.fullPath() << "connectToField"
<< _connectToField.fullPath()
diff --git a/src/mongo/db/pipeline/document_source_graph_lookup.h b/src/mongo/db/pipeline/document_source_graph_lookup.h
index 6e94e98b4af..8c14a2ab8fa 100644
--- a/src/mongo/db/pipeline/document_source_graph_lookup.h
+++ b/src/mongo/db/pipeline/document_source_graph_lookup.h
@@ -45,7 +45,9 @@ public:
const char* getSourceName() const final;
void dispose() final;
BSONObjSet getOutputSorts() final;
- void serializeToArray(std::vector<Value>& array, bool explain = false) const final;
+ void serializeToArray(
+ std::vector<Value>& array,
+ boost::optional<ExplainOptions::Verbosity> explain = boost::none) const final;
/**
* Returns the 'as' path, and possibly the fields modified by an absorbed $unwind.
@@ -107,7 +109,7 @@ private:
boost::optional<long long> maxDepth,
boost::optional<boost::intrusive_ptr<DocumentSourceUnwind>> unwindSrc);
- Value serialize(bool explain = false) const final {
+ Value serialize(boost::optional<ExplainOptions::Verbosity> explain = boost::none) const final {
// Should not be called; use serializeToArray instead.
MONGO_UNREACHABLE;
}
diff --git a/src/mongo/db/pipeline/document_source_group.cpp b/src/mongo/db/pipeline/document_source_group.cpp
index a70a65c06b9..d4f44850a8f 100644
--- a/src/mongo/db/pipeline/document_source_group.cpp
+++ b/src/mongo/db/pipeline/document_source_group.cpp
@@ -198,19 +198,19 @@ intrusive_ptr<DocumentSource> DocumentSourceGroup::optimize() {
return this;
}
-Value DocumentSourceGroup::serialize(bool explain) const {
+Value DocumentSourceGroup::serialize(boost::optional<ExplainOptions::Verbosity> explain) const {
MutableDocument insides;
// Add the _id.
if (_idFieldNames.empty()) {
invariant(_idExpressions.size() == 1);
- insides["_id"] = _idExpressions[0]->serialize(explain);
+ insides["_id"] = _idExpressions[0]->serialize(static_cast<bool>(explain));
} else {
// Decomposed document case.
invariant(_idExpressions.size() == _idFieldNames.size());
MutableDocument md;
for (size_t i = 0; i < _idExpressions.size(); i++) {
- md[_idFieldNames[i]] = _idExpressions[i]->serialize(explain);
+ md[_idFieldNames[i]] = _idExpressions[i]->serialize(static_cast<bool>(explain));
}
insides["_id"] = md.freezeToValue();
}
@@ -219,8 +219,8 @@ Value DocumentSourceGroup::serialize(bool explain) const {
const size_t n = vFieldName.size();
for (size_t i = 0; i < n; ++i) {
intrusive_ptr<Accumulator> accum = vpAccumulatorFactory[i](pExpCtx);
- insides[vFieldName[i]] =
- Value(DOC(accum->getOpName() << vpExpression[i]->serialize(explain)));
+ insides[vFieldName[i]] = Value(
+ DOC(accum->getOpName() << vpExpression[i]->serialize(static_cast<bool>(explain))));
}
if (_doingMerge) {
diff --git a/src/mongo/db/pipeline/document_source_group.h b/src/mongo/db/pipeline/document_source_group.h
index e79ce631817..86b973b9e04 100644
--- a/src/mongo/db/pipeline/document_source_group.h
+++ b/src/mongo/db/pipeline/document_source_group.h
@@ -48,7 +48,7 @@ public:
// Virtuals from DocumentSource.
boost::intrusive_ptr<DocumentSource> optimize() final;
GetDepsReturn getDependencies(DepsTracker* deps) const final;
- Value serialize(bool explain = false) const final;
+ Value serialize(boost::optional<ExplainOptions::Verbosity> explain = boost::none) const final;
GetNextResult getNext() final;
void dispose() final;
const char* getSourceName() const final;
diff --git a/src/mongo/db/pipeline/document_source_index_stats.cpp b/src/mongo/db/pipeline/document_source_index_stats.cpp
index 3256f8e5211..863c37119c3 100644
--- a/src/mongo/db/pipeline/document_source_index_stats.cpp
+++ b/src/mongo/db/pipeline/document_source_index_stats.cpp
@@ -81,7 +81,8 @@ intrusive_ptr<DocumentSource> DocumentSourceIndexStats::createFromBson(
return new DocumentSourceIndexStats(pExpCtx);
}
-Value DocumentSourceIndexStats::serialize(bool explain) const {
+Value DocumentSourceIndexStats::serialize(
+ boost::optional<ExplainOptions::Verbosity> explain) const {
return Value(DOC(getSourceName() << Document()));
}
}
diff --git a/src/mongo/db/pipeline/document_source_index_stats.h b/src/mongo/db/pipeline/document_source_index_stats.h
index 1c1bc521aeb..26beb7edb8b 100644
--- a/src/mongo/db/pipeline/document_source_index_stats.h
+++ b/src/mongo/db/pipeline/document_source_index_stats.h
@@ -42,7 +42,7 @@ public:
// virtuals from DocumentSource
GetNextResult getNext() final;
const char* getSourceName() const final;
- Value serialize(bool explain = false) const final;
+ Value serialize(boost::optional<ExplainOptions::Verbosity> explain = boost::none) const final;
virtual bool isValidInitialSource() const final {
return true;
diff --git a/src/mongo/db/pipeline/document_source_limit.cpp b/src/mongo/db/pipeline/document_source_limit.cpp
index 797c6ed6652..704ba907165 100644
--- a/src/mongo/db/pipeline/document_source_limit.cpp
+++ b/src/mongo/db/pipeline/document_source_limit.cpp
@@ -85,7 +85,7 @@ DocumentSource::GetNextResult DocumentSourceLimit::getNext() {
return nextInput;
}
-Value DocumentSourceLimit::serialize(bool explain) const {
+Value DocumentSourceLimit::serialize(boost::optional<ExplainOptions::Verbosity> explain) const {
return Value(Document{{getSourceName(), _limit}});
}
diff --git a/src/mongo/db/pipeline/document_source_limit.h b/src/mongo/db/pipeline/document_source_limit.h
index 9c04213045d..c6660152d66 100644
--- a/src/mongo/db/pipeline/document_source_limit.h
+++ b/src/mongo/db/pipeline/document_source_limit.h
@@ -47,7 +47,7 @@ public:
*/
Pipeline::SourceContainer::iterator doOptimizeAt(Pipeline::SourceContainer::iterator itr,
Pipeline::SourceContainer* container) final;
- Value serialize(bool explain = false) const final;
+ Value serialize(boost::optional<ExplainOptions::Verbosity> explain = boost::none) const final;
GetDepsReturn getDependencies(DepsTracker* deps) const final {
return SEE_NEXT; // This doesn't affect needed fields
diff --git a/src/mongo/db/pipeline/document_source_lookup.cpp b/src/mongo/db/pipeline/document_source_lookup.cpp
index f173224f202..a474baefe6a 100644
--- a/src/mongo/db/pipeline/document_source_lookup.cpp
+++ b/src/mongo/db/pipeline/document_source_lookup.cpp
@@ -414,7 +414,8 @@ DocumentSource::GetNextResult DocumentSourceLookUp::unwindResult() {
return output.freeze();
}
-void DocumentSourceLookUp::serializeToArray(std::vector<Value>& array, bool explain) const {
+void DocumentSourceLookUp::serializeToArray(
+ std::vector<Value>& array, boost::optional<ExplainOptions::Verbosity> explain) const {
MutableDocument output(DOC(
getSourceName() << DOC("from" << _fromNs.coll() << "as" << _as.fullPath() << "localField"
<< _localField.fullPath()
diff --git a/src/mongo/db/pipeline/document_source_lookup.h b/src/mongo/db/pipeline/document_source_lookup.h
index e179abfe73e..febdedc0472 100644
--- a/src/mongo/db/pipeline/document_source_lookup.h
+++ b/src/mongo/db/pipeline/document_source_lookup.h
@@ -49,7 +49,9 @@ public:
GetNextResult getNext() final;
const char* getSourceName() const final;
- void serializeToArray(std::vector<Value>& array, bool explain = false) const final;
+ void serializeToArray(
+ std::vector<Value>& array,
+ boost::optional<ExplainOptions::Verbosity> explain = boost::none) const final;
/**
* Returns the 'as' path, and possibly fields modified by an absorbed $unwind.
@@ -120,7 +122,7 @@ private:
std::string foreignField,
const boost::intrusive_ptr<ExpressionContext>& pExpCtx);
- Value serialize(bool explain = false) const final {
+ Value serialize(boost::optional<ExplainOptions::Verbosity> explain = boost::none) const final {
// Should not be called; use serializeToArray instead.
MONGO_UNREACHABLE;
}
diff --git a/src/mongo/db/pipeline/document_source_match.cpp b/src/mongo/db/pipeline/document_source_match.cpp
index 3107e8b7677..e72377ca04e 100644
--- a/src/mongo/db/pipeline/document_source_match.cpp
+++ b/src/mongo/db/pipeline/document_source_match.cpp
@@ -57,7 +57,7 @@ const char* DocumentSourceMatch::getSourceName() const {
return "$match";
}
-Value DocumentSourceMatch::serialize(bool explain) const {
+Value DocumentSourceMatch::serialize(boost::optional<ExplainOptions::Verbosity> explain) const {
return Value(DOC(getSourceName() << Document(getQuery())));
}
diff --git a/src/mongo/db/pipeline/document_source_match.h b/src/mongo/db/pipeline/document_source_match.h
index badfe1ed8b9..faeda447cb3 100644
--- a/src/mongo/db/pipeline/document_source_match.h
+++ b/src/mongo/db/pipeline/document_source_match.h
@@ -42,7 +42,7 @@ public:
// virtuals from DocumentSource
GetNextResult getNext() final;
const char* getSourceName() const final;
- Value serialize(bool explain = false) const final;
+ Value serialize(boost::optional<ExplainOptions::Verbosity> explain = boost::none) const final;
boost::intrusive_ptr<DocumentSource> optimize() final;
BSONObjSet getOutputSorts() final {
return pSource ? pSource->getOutputSorts()
diff --git a/src/mongo/db/pipeline/document_source_merge_cursors.cpp b/src/mongo/db/pipeline/document_source_merge_cursors.cpp
index b03db6560df..a9f06ef6aea 100644
--- a/src/mongo/db/pipeline/document_source_merge_cursors.cpp
+++ b/src/mongo/db/pipeline/document_source_merge_cursors.cpp
@@ -86,7 +86,8 @@ intrusive_ptr<DocumentSource> DocumentSourceMergeCursors::createFromBson(
return new DocumentSourceMergeCursors(std::move(cursorDescriptors), pExpCtx);
}
-Value DocumentSourceMergeCursors::serialize(bool explain) const {
+Value DocumentSourceMergeCursors::serialize(
+ boost::optional<ExplainOptions::Verbosity> explain) const {
vector<Value> cursors;
for (size_t i = 0; i < _cursorDescriptors.size(); i++) {
cursors.push_back(
diff --git a/src/mongo/db/pipeline/document_source_merge_cursors.h b/src/mongo/db/pipeline/document_source_merge_cursors.h
index ad853a9f755..0842ca89bdd 100644
--- a/src/mongo/db/pipeline/document_source_merge_cursors.h
+++ b/src/mongo/db/pipeline/document_source_merge_cursors.h
@@ -52,7 +52,7 @@ public:
GetNextResult getNext() final;
const char* getSourceName() const final;
void dispose() final;
- Value serialize(bool explain = false) const final;
+ Value serialize(boost::optional<ExplainOptions::Verbosity> explain = boost::none) const final;
bool isValidInitialSource() const final {
return true;
}
diff --git a/src/mongo/db/pipeline/document_source_mock.cpp b/src/mongo/db/pipeline/document_source_mock.cpp
index 3918bbd5ad7..9f1aab2977b 100644
--- a/src/mongo/db/pipeline/document_source_mock.cpp
+++ b/src/mongo/db/pipeline/document_source_mock.cpp
@@ -54,7 +54,7 @@ const char* DocumentSourceMock::getSourceName() const {
return "mock";
}
-Value DocumentSourceMock::serialize(bool explain) const {
+Value DocumentSourceMock::serialize(boost::optional<ExplainOptions::Verbosity> explain) const {
return Value(Document{{getSourceName(), Document()}});
}
diff --git a/src/mongo/db/pipeline/document_source_mock.h b/src/mongo/db/pipeline/document_source_mock.h
index 7b966f5c418..74973f3fd27 100644
--- a/src/mongo/db/pipeline/document_source_mock.h
+++ b/src/mongo/db/pipeline/document_source_mock.h
@@ -46,7 +46,8 @@ public:
GetNextResult getNext() override;
const char* getSourceName() const override;
- Value serialize(bool explain = false) const override;
+ Value serialize(
+ boost::optional<ExplainOptions::Verbosity> explain = boost::none) const override;
void dispose() override;
bool isValidInitialSource() const override {
return true;
diff --git a/src/mongo/db/pipeline/document_source_out.cpp b/src/mongo/db/pipeline/document_source_out.cpp
index 7dab6365405..0bccc03643d 100644
--- a/src/mongo/db/pipeline/document_source_out.cpp
+++ b/src/mongo/db/pipeline/document_source_out.cpp
@@ -220,7 +220,7 @@ intrusive_ptr<DocumentSource> DocumentSourceOut::createFromBson(
return new DocumentSourceOut(outputNs, pExpCtx);
}
-Value DocumentSourceOut::serialize(bool explain) const {
+Value DocumentSourceOut::serialize(boost::optional<ExplainOptions::Verbosity> explain) const {
massert(
17000, "$out shouldn't have different db than input", _outputNs.db() == pExpCtx->ns.db());
diff --git a/src/mongo/db/pipeline/document_source_out.h b/src/mongo/db/pipeline/document_source_out.h
index 4e8a678404a..5fca80dfba1 100644
--- a/src/mongo/db/pipeline/document_source_out.h
+++ b/src/mongo/db/pipeline/document_source_out.h
@@ -41,7 +41,7 @@ public:
~DocumentSourceOut() final;
GetNextResult getNext() final;
const char* getSourceName() const final;
- Value serialize(bool explain = false) const final;
+ Value serialize(boost::optional<ExplainOptions::Verbosity> explain = boost::none) const final;
GetDepsReturn getDependencies(DepsTracker* deps) const final;
bool needsPrimaryShard() const final {
return true;
diff --git a/src/mongo/db/pipeline/document_source_redact.cpp b/src/mongo/db/pipeline/document_source_redact.cpp
index 0096017a2f8..ae79729f2e0 100644
--- a/src/mongo/db/pipeline/document_source_redact.cpp
+++ b/src/mongo/db/pipeline/document_source_redact.cpp
@@ -163,8 +163,8 @@ intrusive_ptr<DocumentSource> DocumentSourceRedact::optimize() {
return this;
}
-Value DocumentSourceRedact::serialize(bool explain) const {
- return Value(DOC(getSourceName() << _expression.get()->serialize(explain)));
+Value DocumentSourceRedact::serialize(boost::optional<ExplainOptions::Verbosity> explain) const {
+ return Value(DOC(getSourceName() << _expression.get()->serialize(static_cast<bool>(explain))));
}
intrusive_ptr<DocumentSource> DocumentSourceRedact::createFromBson(
diff --git a/src/mongo/db/pipeline/document_source_redact.h b/src/mongo/db/pipeline/document_source_redact.h
index b9cf6242d42..8177de12eeb 100644
--- a/src/mongo/db/pipeline/document_source_redact.h
+++ b/src/mongo/db/pipeline/document_source_redact.h
@@ -50,7 +50,7 @@ public:
static boost::intrusive_ptr<DocumentSource> createFromBson(
BSONElement elem, const boost::intrusive_ptr<ExpressionContext>& expCtx);
- Value serialize(bool explain = false) const final;
+ Value serialize(boost::optional<ExplainOptions::Verbosity> explain = boost::none) const final;
private:
DocumentSourceRedact(const boost::intrusive_ptr<ExpressionContext>& expCtx,
diff --git a/src/mongo/db/pipeline/document_source_replace_root.cpp b/src/mongo/db/pipeline/document_source_replace_root.cpp
index 1ae82d5cf3a..521cf748784 100644
--- a/src/mongo/db/pipeline/document_source_replace_root.cpp
+++ b/src/mongo/db/pipeline/document_source_replace_root.cpp
@@ -76,8 +76,8 @@ public:
_newRoot->optimize();
}
- Document serialize(bool explain) const final {
- return Document{{"newRoot", _newRoot->serialize(explain)}};
+ Document serialize(boost::optional<ExplainOptions::Verbosity> explain) const final {
+ return Document{{"newRoot", _newRoot->serialize(static_cast<bool>(explain))}};
}
DocumentSource::GetDepsReturn addDependencies(DepsTracker* deps) const final {
diff --git a/src/mongo/db/pipeline/document_source_sample.cpp b/src/mongo/db/pipeline/document_source_sample.cpp
index c7d98db7260..5117e5c516a 100644
--- a/src/mongo/db/pipeline/document_source_sample.cpp
+++ b/src/mongo/db/pipeline/document_source_sample.cpp
@@ -83,7 +83,7 @@ DocumentSource::GetNextResult DocumentSourceSample::getNext() {
return _sortStage->getNext();
}
-Value DocumentSourceSample::serialize(bool explain) const {
+Value DocumentSourceSample::serialize(boost::optional<ExplainOptions::Verbosity> explain) const {
return Value(DOC(getSourceName() << DOC("size" << _size)));
}
diff --git a/src/mongo/db/pipeline/document_source_sample.h b/src/mongo/db/pipeline/document_source_sample.h
index 28f1dd97b05..ec88c0737a8 100644
--- a/src/mongo/db/pipeline/document_source_sample.h
+++ b/src/mongo/db/pipeline/document_source_sample.h
@@ -37,7 +37,7 @@ class DocumentSourceSample final : public DocumentSource, public SplittableDocum
public:
GetNextResult getNext() final;
const char* getSourceName() const final;
- Value serialize(bool explain = false) const final;
+ Value serialize(boost::optional<ExplainOptions::Verbosity> explain = boost::none) const final;
GetDepsReturn getDependencies(DepsTracker* deps) const final {
return SEE_NEXT;
diff --git a/src/mongo/db/pipeline/document_source_sample_from_random_cursor.cpp b/src/mongo/db/pipeline/document_source_sample_from_random_cursor.cpp
index 9fc3b5bf105..ff6c4e6ec16 100644
--- a/src/mongo/db/pipeline/document_source_sample_from_random_cursor.cpp
+++ b/src/mongo/db/pipeline/document_source_sample_from_random_cursor.cpp
@@ -138,7 +138,8 @@ DocumentSource::GetNextResult DocumentSourceSampleFromRandomCursor::getNextNonDu
"sporadic failure, please try again.");
}
-Value DocumentSourceSampleFromRandomCursor::serialize(bool explain) const {
+Value DocumentSourceSampleFromRandomCursor::serialize(
+ boost::optional<ExplainOptions::Verbosity> explain) const {
return Value(DOC(getSourceName() << DOC("size" << _size)));
}
diff --git a/src/mongo/db/pipeline/document_source_sample_from_random_cursor.h b/src/mongo/db/pipeline/document_source_sample_from_random_cursor.h
index 0d0ac39ca49..19b7106b03d 100644
--- a/src/mongo/db/pipeline/document_source_sample_from_random_cursor.h
+++ b/src/mongo/db/pipeline/document_source_sample_from_random_cursor.h
@@ -41,7 +41,7 @@ class DocumentSourceSampleFromRandomCursor final : public DocumentSource {
public:
GetNextResult getNext() final;
const char* getSourceName() const final;
- Value serialize(bool explain = false) const final;
+ Value serialize(boost::optional<ExplainOptions::Verbosity> explain = boost::none) const final;
GetDepsReturn getDependencies(DepsTracker* deps) const final;
static boost::intrusive_ptr<DocumentSourceSampleFromRandomCursor> create(
diff --git a/src/mongo/db/pipeline/document_source_sample_test.cpp b/src/mongo/db/pipeline/document_source_sample_test.cpp
index 30a59baf22b..528676cb294 100644
--- a/src/mongo/db/pipeline/document_source_sample_test.cpp
+++ b/src/mongo/db/pipeline/document_source_sample_test.cpp
@@ -140,7 +140,7 @@ private:
* created with.
*/
void checkBsonRepresentation(const BSONObj& spec) {
- Value serialized = static_cast<DocumentSourceSample*>(sample())->serialize(false);
+ Value serialized = static_cast<DocumentSourceSample*>(sample())->serialize();
auto generatedSpec = serialized.getDocument().toBson();
ASSERT_BSONOBJ_EQ(spec, generatedSpec);
}
diff --git a/src/mongo/db/pipeline/document_source_single_document_transformation.cpp b/src/mongo/db/pipeline/document_source_single_document_transformation.cpp
index 35204d272a4..5fcd0ba5400 100644
--- a/src/mongo/db/pipeline/document_source_single_document_transformation.cpp
+++ b/src/mongo/db/pipeline/document_source_single_document_transformation.cpp
@@ -74,7 +74,8 @@ void DocumentSourceSingleDocumentTransformation::dispose() {
_parsedTransform.reset();
}
-Value DocumentSourceSingleDocumentTransformation::serialize(bool explain) const {
+Value DocumentSourceSingleDocumentTransformation::serialize(
+ boost::optional<ExplainOptions::Verbosity> explain) const {
return Value(Document{{getSourceName(), _parsedTransform->serialize(explain)}});
}
diff --git a/src/mongo/db/pipeline/document_source_single_document_transformation.h b/src/mongo/db/pipeline/document_source_single_document_transformation.h
index 1251cb454a4..86f4607513c 100644
--- a/src/mongo/db/pipeline/document_source_single_document_transformation.h
+++ b/src/mongo/db/pipeline/document_source_single_document_transformation.h
@@ -55,7 +55,7 @@ public:
virtual ~TransformerInterface() = default;
virtual Document applyTransformation(Document input) = 0;
virtual void optimize() = 0;
- virtual Document serialize(bool explain) const = 0;
+ virtual Document serialize(boost::optional<ExplainOptions::Verbosity> explain) const = 0;
virtual DocumentSource::GetDepsReturn addDependencies(DepsTracker* deps) const = 0;
virtual GetModPathsReturn getModifiedPaths() const = 0;
};
@@ -70,7 +70,7 @@ public:
GetNextResult getNext() final;
boost::intrusive_ptr<DocumentSource> optimize() final;
void dispose() final;
- Value serialize(bool explain) const final;
+ Value serialize(boost::optional<ExplainOptions::Verbosity> explain = boost::none) const final;
Pipeline::SourceContainer::iterator doOptimizeAt(Pipeline::SourceContainer::iterator itr,
Pipeline::SourceContainer* container) final;
DocumentSource::GetDepsReturn getDependencies(DepsTracker* deps) const final;
diff --git a/src/mongo/db/pipeline/document_source_skip.cpp b/src/mongo/db/pipeline/document_source_skip.cpp
index 114a2f7d1b4..61125a879f4 100644
--- a/src/mongo/db/pipeline/document_source_skip.cpp
+++ b/src/mongo/db/pipeline/document_source_skip.cpp
@@ -73,7 +73,7 @@ DocumentSource::GetNextResult DocumentSourceSkip::getNext() {
return pSource->getNext();
}
-Value DocumentSourceSkip::serialize(bool explain) const {
+Value DocumentSourceSkip::serialize(boost::optional<ExplainOptions::Verbosity> explain) const {
return Value(DOC(getSourceName() << _nToSkip));
}
diff --git a/src/mongo/db/pipeline/document_source_skip.h b/src/mongo/db/pipeline/document_source_skip.h
index 92d087e3c75..46cb6a6f3ab 100644
--- a/src/mongo/db/pipeline/document_source_skip.h
+++ b/src/mongo/db/pipeline/document_source_skip.h
@@ -43,7 +43,7 @@ public:
*/
Pipeline::SourceContainer::iterator doOptimizeAt(Pipeline::SourceContainer::iterator itr,
Pipeline::SourceContainer* container) final;
- Value serialize(bool explain = false) const final;
+ Value serialize(boost::optional<ExplainOptions::Verbosity> explain = boost::none) const final;
boost::intrusive_ptr<DocumentSource> optimize() final;
BSONObjSet getOutputSorts() final {
return pSource ? pSource->getOutputSorts()
diff --git a/src/mongo/db/pipeline/document_source_sort.cpp b/src/mongo/db/pipeline/document_source_sort.cpp
index 6554a892016..b32b94760d9 100644
--- a/src/mongo/db/pipeline/document_source_sort.cpp
+++ b/src/mongo/db/pipeline/document_source_sort.cpp
@@ -79,16 +79,17 @@ DocumentSource::GetNextResult DocumentSourceSort::getNext() {
return _output->next().second;
}
-void DocumentSourceSort::serializeToArray(vector<Value>& array, bool explain) const {
+void DocumentSourceSort::serializeToArray(
+ std::vector<Value>& array, boost::optional<ExplainOptions::Verbosity> explain) const {
if (explain) { // always one Value for combined $sort + $limit
- array.push_back(
- Value(DOC(getSourceName()
- << DOC("sortKey" << serializeSortKey(explain) << "mergePresorted"
- << (_mergingPresorted ? Value(true) : Value())
- << "limit"
- << (limitSrc ? Value(limitSrc->getLimit()) : Value())))));
+ array.push_back(Value(
+ DOC(getSourceName() << DOC(
+ "sortKey" << serializeSortKey(static_cast<bool>(explain)) << "mergePresorted"
+ << (_mergingPresorted ? Value(true) : Value())
+ << "limit"
+ << (limitSrc ? Value(limitSrc->getLimit()) : Value())))));
} else { // one Value for $sort and maybe a Value for $limit
- MutableDocument inner(serializeSortKey(explain));
+ MutableDocument inner(serializeSortKey(static_cast<bool>(explain)));
if (_mergingPresorted)
inner["$mergePresorted"] = Value(true);
array.push_back(Value(DOC(getSourceName() << inner.freeze())));
diff --git a/src/mongo/db/pipeline/document_source_sort.h b/src/mongo/db/pipeline/document_source_sort.h
index d9266011a55..52d2ff2b687 100644
--- a/src/mongo/db/pipeline/document_source_sort.h
+++ b/src/mongo/db/pipeline/document_source_sort.h
@@ -43,7 +43,9 @@ public:
// virtuals from DocumentSource
GetNextResult getNext() final;
const char* getSourceName() const final;
- void serializeToArray(std::vector<Value>& array, bool explain = false) const final;
+ void serializeToArray(
+ std::vector<Value>& array,
+ boost::optional<ExplainOptions::Verbosity> explain = boost::none) const final;
GetModPathsReturn getModifiedPaths() const final {
// A $sort does not modify any paths.
@@ -123,7 +125,7 @@ public:
private:
explicit DocumentSourceSort(const boost::intrusive_ptr<ExpressionContext>& pExpCtx);
- Value serialize(bool explain = false) const final {
+ Value serialize(boost::optional<ExplainOptions::Verbosity> explain = boost::none) const final {
MONGO_UNREACHABLE; // Should call serializeToArray instead.
}
diff --git a/src/mongo/db/pipeline/document_source_sort_by_count_test.cpp b/src/mongo/db/pipeline/document_source_sort_by_count_test.cpp
index 82e4aa6fb56..3e0a007c59b 100644
--- a/src/mongo/db/pipeline/document_source_sort_by_count_test.cpp
+++ b/src/mongo/db/pipeline/document_source_sort_by_count_test.cpp
@@ -68,7 +68,7 @@ public:
// Serialize the DocumentSourceGroup and DocumentSourceSort from $sortByCount so that we can
// check the explain output to make sure $group and $sort have the correct fields.
- const bool explain = true;
+ const auto explain = ExplainOptions::Verbosity::kQueryPlanner;
vector<Value> explainedStages;
groupStage->serializeToArray(explainedStages, explain);
sortStage->serializeToArray(explainedStages, explain);
diff --git a/src/mongo/db/pipeline/document_source_tee_consumer.cpp b/src/mongo/db/pipeline/document_source_tee_consumer.cpp
index 052bf7bad7b..c620fba222a 100644
--- a/src/mongo/db/pipeline/document_source_tee_consumer.cpp
+++ b/src/mongo/db/pipeline/document_source_tee_consumer.cpp
@@ -62,7 +62,8 @@ void DocumentSourceTeeConsumer::dispose() {
_bufferSource->dispose(_facetId);
}
-Value DocumentSourceTeeConsumer::serialize(bool explain) const {
+Value DocumentSourceTeeConsumer::serialize(
+ boost::optional<ExplainOptions::Verbosity> explain) const {
// This stage will be inserted into the beginning of a pipeline, but should not show up in the
// explain output.
return Value();
diff --git a/src/mongo/db/pipeline/document_source_tee_consumer.h b/src/mongo/db/pipeline/document_source_tee_consumer.h
index bfdbfbda1cb..cebeaee9a2e 100644
--- a/src/mongo/db/pipeline/document_source_tee_consumer.h
+++ b/src/mongo/db/pipeline/document_source_tee_consumer.h
@@ -63,7 +63,7 @@ public:
return GetDepsReturn::SEE_NEXT;
}
- Value serialize(bool explain = false) const final;
+ Value serialize(boost::optional<ExplainOptions::Verbosity> explain = boost::none) const final;
private:
DocumentSourceTeeConsumer(const boost::intrusive_ptr<ExpressionContext>& expCtx,
diff --git a/src/mongo/db/pipeline/document_source_unwind.cpp b/src/mongo/db/pipeline/document_source_unwind.cpp
index 683069597e0..079439bd842 100644
--- a/src/mongo/db/pipeline/document_source_unwind.cpp
+++ b/src/mongo/db/pipeline/document_source_unwind.cpp
@@ -239,7 +239,7 @@ DocumentSource::GetModPathsReturn DocumentSourceUnwind::getModifiedPaths() const
return {GetModPathsReturn::Type::kFiniteSet, std::move(modifiedFields)};
}
-Value DocumentSourceUnwind::serialize(bool explain) const {
+Value DocumentSourceUnwind::serialize(boost::optional<ExplainOptions::Verbosity> explain) const {
return Value(DOC(getSourceName() << DOC(
"path" << _unwindPath.fullPathWithPrefix() << "preserveNullAndEmptyArrays"
<< (_preserveNullAndEmptyArrays ? Value(true) : Value())
diff --git a/src/mongo/db/pipeline/document_source_unwind.h b/src/mongo/db/pipeline/document_source_unwind.h
index 9a9c466d037..53fbd33d083 100644
--- a/src/mongo/db/pipeline/document_source_unwind.h
+++ b/src/mongo/db/pipeline/document_source_unwind.h
@@ -38,7 +38,7 @@ public:
// virtuals from DocumentSource
GetNextResult getNext() final;
const char* getSourceName() const final;
- Value serialize(bool explain = false) const final;
+ Value serialize(boost::optional<ExplainOptions::Verbosity> explain = boost::none) const final;
BSONObjSet getOutputSorts() final;
/**
diff --git a/src/mongo/db/pipeline/expression_context.cpp b/src/mongo/db/pipeline/expression_context.cpp
index 5bccef201ae..2b149b1af9c 100644
--- a/src/mongo/db/pipeline/expression_context.cpp
+++ b/src/mongo/db/pipeline/expression_context.cpp
@@ -43,7 +43,7 @@ ExpressionContext::ExpressionContext(OperationContext* opCtx,
const AggregationRequest& request,
std::unique_ptr<CollatorInterface> collator,
StringMap<ResolvedNamespace> resolvedNamespaces)
- : isExplain(request.isExplain()),
+ : explain(request.getExplain()),
inShard(request.isFromRouter()),
extSortAllowed(request.shouldAllowDiskUse()),
bypassDocumentValidation(request.shouldBypassDocumentValidation()),
@@ -74,7 +74,7 @@ void ExpressionContext::setCollator(std::unique_ptr<CollatorInterface> coll) {
intrusive_ptr<ExpressionContext> ExpressionContext::copyWith(NamespaceString ns) const {
intrusive_ptr<ExpressionContext> expCtx = new ExpressionContext();
- expCtx->isExplain = isExplain;
+ expCtx->explain = explain;
expCtx->inShard = inShard;
expCtx->inRouter = inRouter;
expCtx->extSortAllowed = extSortAllowed;
diff --git a/src/mongo/db/pipeline/expression_context.h b/src/mongo/db/pipeline/expression_context.h
index af6d7d199c9..4d4c0b75f3a 100644
--- a/src/mongo/db/pipeline/expression_context.h
+++ b/src/mongo/db/pipeline/expression_context.h
@@ -29,6 +29,7 @@
#pragma once
#include <boost/intrusive_ptr.hpp>
+#include <boost/optional.hpp>
#include <memory>
#include <string>
#include <vector>
@@ -40,6 +41,7 @@
#include "mongo/db/pipeline/document_comparator.h"
#include "mongo/db/pipeline/value_comparator.h"
#include "mongo/db/query/collation/collator_interface.h"
+#include "mongo/db/query/explain_options.h"
#include "mongo/util/intrusive_counter.h"
#include "mongo/util/string_map.h"
@@ -98,7 +100,9 @@ public:
return it->second;
};
- bool isExplain = false;
+ // The explain verbosity requested by the user, or boost::none if no explain was requested.
+ boost::optional<ExplainOptions::Verbosity> explain;
+
bool inShard = false;
bool inRouter = false;
bool extSortAllowed = false;
diff --git a/src/mongo/db/pipeline/parsed_add_fields.h b/src/mongo/db/pipeline/parsed_add_fields.h
index 2213191f6c8..8cc3d24f913 100644
--- a/src/mongo/db/pipeline/parsed_add_fields.h
+++ b/src/mongo/db/pipeline/parsed_add_fields.h
@@ -75,7 +75,7 @@ public:
_variables = stdx::make_unique<Variables>(idGenerator.getIdCount());
}
- Document serialize(bool explain = false) const final {
+ Document serialize(boost::optional<ExplainOptions::Verbosity> explain) const final {
MutableDocument output;
_root->serialize(&output, explain);
return output.freeze();
diff --git a/src/mongo/db/pipeline/parsed_add_fields_test.cpp b/src/mongo/db/pipeline/parsed_add_fields_test.cpp
index d7b68afca1d..c9adc2b1a36 100644
--- a/src/mongo/db/pipeline/parsed_add_fields_test.cpp
+++ b/src/mongo/db/pipeline/parsed_add_fields_test.cpp
@@ -176,8 +176,13 @@ TEST(ParsedAddFieldsSerialize, SerializesToCorrectForm) {
fromjson("{a: {$add: [\"$a\", {$const: 2}]}, b: {d: {$const: 3}}, x: {y: {$const: 4}}}"));
// Should be the same if we're serializing for explain or for internal use.
- ASSERT_DOCUMENT_EQ(expectedSerialization, addition.serialize(false));
- ASSERT_DOCUMENT_EQ(expectedSerialization, addition.serialize(true));
+ ASSERT_DOCUMENT_EQ(expectedSerialization, addition.serialize(boost::none));
+ ASSERT_DOCUMENT_EQ(expectedSerialization,
+ addition.serialize(ExplainOptions::Verbosity::kQueryPlanner));
+ ASSERT_DOCUMENT_EQ(expectedSerialization,
+ addition.serialize(ExplainOptions::Verbosity::kExecStats));
+ ASSERT_DOCUMENT_EQ(expectedSerialization,
+ addition.serialize(ExplainOptions::Verbosity::kExecAllPlans));
}
// Verify that serialize treats the _id field as any other field: including when explicity included.
@@ -190,8 +195,13 @@ TEST(ParsedAddFieldsSerialize, AddsIdToSerializeWhenExplicitlyIncluded) {
auto expectedSerialization = Document(fromjson("{_id: {$const: false}}"));
// Should be the same if we're serializing for explain or for internal use.
- ASSERT_DOCUMENT_EQ(expectedSerialization, addition.serialize(false));
- ASSERT_DOCUMENT_EQ(expectedSerialization, addition.serialize(true));
+ ASSERT_DOCUMENT_EQ(expectedSerialization, addition.serialize(boost::none));
+ ASSERT_DOCUMENT_EQ(expectedSerialization,
+ addition.serialize(ExplainOptions::Verbosity::kQueryPlanner));
+ ASSERT_DOCUMENT_EQ(expectedSerialization,
+ addition.serialize(ExplainOptions::Verbosity::kExecStats));
+ ASSERT_DOCUMENT_EQ(expectedSerialization,
+ addition.serialize(ExplainOptions::Verbosity::kExecAllPlans));
}
// Verify that serialize treats the _id field as any other field: excluded when not explicitly
@@ -207,8 +217,13 @@ TEST(ParsedAddFieldsSerialize, OmitsIdFromSerializeWhenNotIncluded) {
auto expectedSerialization = Document(fromjson("{a: {$const: true}}"));
// Should be the same if we're serializing for explain or for internal use.
- ASSERT_DOCUMENT_EQ(expectedSerialization, addition.serialize(false));
- ASSERT_DOCUMENT_EQ(expectedSerialization, addition.serialize(true));
+ ASSERT_DOCUMENT_EQ(expectedSerialization, addition.serialize(boost::none));
+ ASSERT_DOCUMENT_EQ(expectedSerialization,
+ addition.serialize(ExplainOptions::Verbosity::kQueryPlanner));
+ ASSERT_DOCUMENT_EQ(expectedSerialization,
+ addition.serialize(ExplainOptions::Verbosity::kExecStats));
+ ASSERT_DOCUMENT_EQ(expectedSerialization,
+ addition.serialize(ExplainOptions::Verbosity::kExecAllPlans));
}
// Verify that the $addFields stage optimizes expressions into simpler forms when possible.
@@ -220,8 +235,13 @@ TEST(ParsedAddFieldsOptimize, OptimizesTopLevelExpressions) {
auto expectedSerialization = Document{{"a", Document{{"$const", 3}}}};
// Should be the same if we're serializing for explain or for internal use.
- ASSERT_DOCUMENT_EQ(expectedSerialization, addition.serialize(false));
- ASSERT_DOCUMENT_EQ(expectedSerialization, addition.serialize(true));
+ ASSERT_DOCUMENT_EQ(expectedSerialization, addition.serialize(boost::none));
+ ASSERT_DOCUMENT_EQ(expectedSerialization,
+ addition.serialize(ExplainOptions::Verbosity::kQueryPlanner));
+ ASSERT_DOCUMENT_EQ(expectedSerialization,
+ addition.serialize(ExplainOptions::Verbosity::kExecStats));
+ ASSERT_DOCUMENT_EQ(expectedSerialization,
+ addition.serialize(ExplainOptions::Verbosity::kExecAllPlans));
}
// Verify that the $addFields stage optimizes expressions even when they are nested.
@@ -233,8 +253,13 @@ TEST(ParsedAddFieldsOptimize, ShouldOptimizeNestedExpressions) {
auto expectedSerialization = Document{{"a", Document{{"b", Document{{"$const", 3}}}}}};
// Should be the same if we're serializing for explain or for internal use.
- ASSERT_DOCUMENT_EQ(expectedSerialization, addition.serialize(false));
- ASSERT_DOCUMENT_EQ(expectedSerialization, addition.serialize(true));
+ ASSERT_DOCUMENT_EQ(expectedSerialization, addition.serialize(boost::none));
+ ASSERT_DOCUMENT_EQ(expectedSerialization,
+ addition.serialize(ExplainOptions::Verbosity::kQueryPlanner));
+ ASSERT_DOCUMENT_EQ(expectedSerialization,
+ addition.serialize(ExplainOptions::Verbosity::kExecStats));
+ ASSERT_DOCUMENT_EQ(expectedSerialization,
+ addition.serialize(ExplainOptions::Verbosity::kExecAllPlans));
}
//
diff --git a/src/mongo/db/pipeline/parsed_exclusion_projection.cpp b/src/mongo/db/pipeline/parsed_exclusion_projection.cpp
index 0f800f9a112..ae3fa4ba2dc 100644
--- a/src/mongo/db/pipeline/parsed_exclusion_projection.cpp
+++ b/src/mongo/db/pipeline/parsed_exclusion_projection.cpp
@@ -135,7 +135,8 @@ void ExclusionNode::addModifiedPaths(std::set<std::string>* modifiedPaths) const
// ParsedExclusionProjection.
//
-Document ParsedExclusionProjection::serialize(bool explain) const {
+Document ParsedExclusionProjection::serialize(
+ boost::optional<ExplainOptions::Verbosity> explain) const {
return _root->serialize();
}
diff --git a/src/mongo/db/pipeline/parsed_exclusion_projection.h b/src/mongo/db/pipeline/parsed_exclusion_projection.h
index d0988d2d2cb..74314d97439 100644
--- a/src/mongo/db/pipeline/parsed_exclusion_projection.h
+++ b/src/mongo/db/pipeline/parsed_exclusion_projection.h
@@ -103,7 +103,7 @@ public:
return ProjectionType::kExclusion;
}
- Document serialize(bool explain = false) const final;
+ Document serialize(boost::optional<ExplainOptions::Verbosity> explain) const final;
/**
* Parses the projection specification given by 'spec', populating internal data structures.
diff --git a/src/mongo/db/pipeline/parsed_exclusion_projection_test.cpp b/src/mongo/db/pipeline/parsed_exclusion_projection_test.cpp
index 76b458260d0..f0e860067f7 100644
--- a/src/mongo/db/pipeline/parsed_exclusion_projection_test.cpp
+++ b/src/mongo/db/pipeline/parsed_exclusion_projection_test.cpp
@@ -87,7 +87,7 @@ TEST(ExclusionProjection, ShouldSerializeToEquivalentProjection) {
// Converts numbers to bools, converts dotted paths to nested documents. Note order of excluded
// fields is subject to change.
- auto serialization = exclusion.serialize();
+ auto serialization = exclusion.serialize(boost::none);
ASSERT_EQ(serialization.size(), 4UL);
ASSERT_VALUE_EQ(serialization["a"], Value(false));
ASSERT_VALUE_EQ(serialization["_id"], Value(false));
diff --git a/src/mongo/db/pipeline/parsed_inclusion_projection.cpp b/src/mongo/db/pipeline/parsed_inclusion_projection.cpp
index d294761852f..da6b9e72ce9 100644
--- a/src/mongo/db/pipeline/parsed_inclusion_projection.cpp
+++ b/src/mongo/db/pipeline/parsed_inclusion_projection.cpp
@@ -54,7 +54,8 @@ void InclusionNode::optimize() {
}
}
-void InclusionNode::serialize(MutableDocument* output, bool explain) const {
+void InclusionNode::serialize(MutableDocument* output,
+ boost::optional<ExplainOptions::Verbosity> explain) const {
// Always put "_id" first if it was included (implicitly or explicitly).
if (_inclusions.find("_id") != _inclusions.end()) {
output->addField("_id", Value(true));
@@ -77,7 +78,7 @@ void InclusionNode::serialize(MutableDocument* output, bool explain) const {
} else {
auto expressionIt = _expressions.find(field);
invariant(expressionIt != _expressions.end());
- output->addField(field, expressionIt->second->serialize(explain));
+ output->addField(field, expressionIt->second->serialize(static_cast<bool>(explain)));
}
}
}
diff --git a/src/mongo/db/pipeline/parsed_inclusion_projection.h b/src/mongo/db/pipeline/parsed_inclusion_projection.h
index ec4f8d0466b..1c0a667b025 100644
--- a/src/mongo/db/pipeline/parsed_inclusion_projection.h
+++ b/src/mongo/db/pipeline/parsed_inclusion_projection.h
@@ -62,7 +62,8 @@ public:
/**
* Serialize this projection.
*/
- void serialize(MutableDocument* output, bool explain) const;
+ void serialize(MutableDocument* output,
+ boost::optional<ExplainOptions::Verbosity> explain) const;
/**
* Adds dependencies of any fields that need to be included, or that are used by any
@@ -197,7 +198,7 @@ public:
/**
* Serialize the projection.
*/
- Document serialize(bool explain = false) const final {
+ Document serialize(boost::optional<ExplainOptions::Verbosity> explain) const final {
MutableDocument output;
if (_idExcluded) {
output.addField("_id", Value(false));
diff --git a/src/mongo/db/pipeline/parsed_inclusion_projection_test.cpp b/src/mongo/db/pipeline/parsed_inclusion_projection_test.cpp
index c2610418a40..a0e7398ef6f 100644
--- a/src/mongo/db/pipeline/parsed_inclusion_projection_test.cpp
+++ b/src/mongo/db/pipeline/parsed_inclusion_projection_test.cpp
@@ -141,8 +141,13 @@ TEST(InclusionProjection, ShouldSerializeToEquivalentProjection) {
"{_id: true, a: {$add: [\"$a\", {$const: 2}]}, b: {d: true}, x: {y: {$const: 4}}}"));
// Should be the same if we're serializing for explain or for internal use.
- ASSERT_DOCUMENT_EQ(expectedSerialization, inclusion.serialize(false));
- ASSERT_DOCUMENT_EQ(expectedSerialization, inclusion.serialize(true));
+ ASSERT_DOCUMENT_EQ(expectedSerialization, inclusion.serialize(boost::none));
+ ASSERT_DOCUMENT_EQ(expectedSerialization,
+ inclusion.serialize(ExplainOptions::Verbosity::kQueryPlanner));
+ ASSERT_DOCUMENT_EQ(expectedSerialization,
+ inclusion.serialize(ExplainOptions::Verbosity::kExecStats));
+ ASSERT_DOCUMENT_EQ(expectedSerialization,
+ inclusion.serialize(ExplainOptions::Verbosity::kExecAllPlans));
}
TEST(InclusionProjection, ShouldSerializeExplicitExclusionOfId) {
@@ -154,8 +159,13 @@ TEST(InclusionProjection, ShouldSerializeExplicitExclusionOfId) {
auto expectedSerialization = Document{{"_id", false}, {"a", true}};
// Should be the same if we're serializing for explain or for internal use.
- ASSERT_DOCUMENT_EQ(expectedSerialization, inclusion.serialize(false));
- ASSERT_DOCUMENT_EQ(expectedSerialization, inclusion.serialize(true));
+ ASSERT_DOCUMENT_EQ(expectedSerialization, inclusion.serialize(boost::none));
+ ASSERT_DOCUMENT_EQ(expectedSerialization,
+ inclusion.serialize(ExplainOptions::Verbosity::kQueryPlanner));
+ ASSERT_DOCUMENT_EQ(expectedSerialization,
+ inclusion.serialize(ExplainOptions::Verbosity::kExecStats));
+ ASSERT_DOCUMENT_EQ(expectedSerialization,
+ inclusion.serialize(ExplainOptions::Verbosity::kExecAllPlans));
}
@@ -169,8 +179,13 @@ TEST(InclusionProjection, ShouldOptimizeTopLevelExpressions) {
auto expectedSerialization = Document{{"_id", true}, {"a", Document{{"$const", 3}}}};
// Should be the same if we're serializing for explain or for internal use.
- ASSERT_DOCUMENT_EQ(expectedSerialization, inclusion.serialize(false));
- ASSERT_DOCUMENT_EQ(expectedSerialization, inclusion.serialize(true));
+ ASSERT_DOCUMENT_EQ(expectedSerialization, inclusion.serialize(boost::none));
+ ASSERT_DOCUMENT_EQ(expectedSerialization,
+ inclusion.serialize(ExplainOptions::Verbosity::kQueryPlanner));
+ ASSERT_DOCUMENT_EQ(expectedSerialization,
+ inclusion.serialize(ExplainOptions::Verbosity::kExecStats));
+ ASSERT_DOCUMENT_EQ(expectedSerialization,
+ inclusion.serialize(ExplainOptions::Verbosity::kExecAllPlans));
}
TEST(InclusionProjection, ShouldOptimizeNestedExpressions) {
@@ -184,8 +199,13 @@ TEST(InclusionProjection, ShouldOptimizeNestedExpressions) {
Document{{"_id", true}, {"a", Document{{"b", Document{{"$const", 3}}}}}};
// Should be the same if we're serializing for explain or for internal use.
- ASSERT_DOCUMENT_EQ(expectedSerialization, inclusion.serialize(false));
- ASSERT_DOCUMENT_EQ(expectedSerialization, inclusion.serialize(true));
+ ASSERT_DOCUMENT_EQ(expectedSerialization, inclusion.serialize(boost::none));
+ ASSERT_DOCUMENT_EQ(expectedSerialization,
+ inclusion.serialize(ExplainOptions::Verbosity::kQueryPlanner));
+ ASSERT_DOCUMENT_EQ(expectedSerialization,
+ inclusion.serialize(ExplainOptions::Verbosity::kExecStats));
+ ASSERT_DOCUMENT_EQ(expectedSerialization,
+ inclusion.serialize(ExplainOptions::Verbosity::kExecAllPlans));
}
TEST(InclusionProjection, ShouldReportThatAllExceptIncludedFieldsAreModified) {
diff --git a/src/mongo/db/pipeline/pipeline.cpp b/src/mongo/db/pipeline/pipeline.cpp
index 5c13f710b40..027f4527aa0 100644
--- a/src/mongo/db/pipeline/pipeline.cpp
+++ b/src/mongo/db/pipeline/pipeline.cpp
@@ -320,10 +320,10 @@ boost::optional<Document> Pipeline::getNext() {
: boost::optional<Document>{nextResult.releaseDocument()};
}
-vector<Value> Pipeline::writeExplainOps() const {
+vector<Value> Pipeline::writeExplainOps(ExplainOptions::Verbosity verbosity) const {
vector<Value> array;
for (SourceContainer::const_iterator it = _sources.begin(); it != _sources.end(); ++it) {
- (*it)->serializeToArray(array, /*explain=*/true);
+ (*it)->serializeToArray(array, verbosity);
}
return array;
}
diff --git a/src/mongo/db/pipeline/pipeline.h b/src/mongo/db/pipeline/pipeline.h
index 3aceb6c28f0..4f15289efef 100644
--- a/src/mongo/db/pipeline/pipeline.h
+++ b/src/mongo/db/pipeline/pipeline.h
@@ -36,6 +36,7 @@
#include "mongo/db/namespace_string.h"
#include "mongo/db/pipeline/dependencies.h"
#include "mongo/db/pipeline/value.h"
+#include "mongo/db/query/explain_options.h"
#include "mongo/util/intrusive_counter.h"
#include "mongo/util/timer.h"
@@ -148,10 +149,10 @@ public:
boost::optional<Document> getNext();
/**
- * Write the pipeline's operators to a std::vector<Value>, with the
- * explain flag true (for DocumentSource::serializeToArray()).
+ * Write the pipeline's operators to a std::vector<Value>, providing the level of detail
+ * specified by 'verbosity'.
*/
- std::vector<Value> writeExplainOps() const;
+ std::vector<Value> writeExplainOps(ExplainOptions::Verbosity verbosity) const;
/**
* Returns the dependencies needed by this pipeline. 'metadataAvailable' should reflect what
diff --git a/src/mongo/db/pipeline/pipeline_test.cpp b/src/mongo/db/pipeline/pipeline_test.cpp
index 098371f5cd7..aed8f8d6889 100644
--- a/src/mongo/db/pipeline/pipeline_test.cpp
+++ b/src/mongo/db/pipeline/pipeline_test.cpp
@@ -97,8 +97,9 @@ public:
auto outputPipe = uassertStatusOK(Pipeline::parse(request.getPipeline(), ctx));
outputPipe->optimizePipeline();
- ASSERT_VALUE_EQ(Value(outputPipe->writeExplainOps()),
- Value(outputPipeExpected["pipeline"]));
+ ASSERT_VALUE_EQ(
+ Value(outputPipe->writeExplainOps(ExplainOptions::Verbosity::kQueryPlanner)),
+ Value(outputPipeExpected["pipeline"]));
ASSERT_VALUE_EQ(Value(outputPipe->serialize()), Value(serializePipeExpected["pipeline"]));
}
@@ -963,8 +964,10 @@ public:
shardPipe = mergePipe->splitForSharded();
ASSERT(shardPipe != nullptr);
- ASSERT_VALUE_EQ(Value(shardPipe->writeExplainOps()), Value(shardPipeExpected["pipeline"]));
- ASSERT_VALUE_EQ(Value(mergePipe->writeExplainOps()), Value(mergePipeExpected["pipeline"]));
+ ASSERT_VALUE_EQ(Value(shardPipe->writeExplainOps(ExplainOptions::Verbosity::kQueryPlanner)),
+ Value(shardPipeExpected["pipeline"]));
+ ASSERT_VALUE_EQ(Value(mergePipe->writeExplainOps(ExplainOptions::Verbosity::kQueryPlanner)),
+ Value(mergePipeExpected["pipeline"]));
}
virtual ~Base() {}