diff options
author | David Storch <david.storch@10gen.com> | 2017-02-10 11:52:18 -0500 |
---|---|---|
committer | David Storch <david.storch@10gen.com> | 2017-03-13 09:46:14 -0400 |
commit | 82b16740f8a66093b453a73a04b3b9bd00e5d7a0 (patch) | |
tree | 62d156fc9676526ecbea19cd03ef7a293579c4df /src/mongo/db/pipeline | |
parent | 73f9e8b8a8422becf8694fe3d82c0e647dc71189 (diff) | |
download | mongo-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')
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() {} |