diff options
24 files changed, 173 insertions, 28 deletions
diff --git a/jstests/core/explain_includes_command.js b/jstests/core/explain_includes_command.js new file mode 100644 index 00000000000..633770118db --- /dev/null +++ b/jstests/core/explain_includes_command.js @@ -0,0 +1,53 @@ +/* + * Confirms that the explain command includes the command object that was run. + * + * @tags: [requires_fcv_49, sbe_incompatible] + */ +(function() { +"use strict"; +const collName = "explain_includes_command"; + +db[collName].drop(); +assert.commandWorked(db[collName].insert({_id: 0})); +assert.commandWorked(db[collName].insert({_id: 1})); +assert.commandWorked(db[collName].insert({_id: 2})); + +/* + * Runs explain on 'cmdToExplain' and ensures that the explain output contains the expected + * 'command' field. + */ +function testExplainContainsCommand(cmdToExplain) { + const verbosity = ["queryPlanner", "executionStats", "allPlansExecution"]; + for (const option of verbosity) { + const explainOutput = db.runCommand({explain: cmdToExplain, verbosity: option}); + assert("command" in explainOutput); + const explainCmd = explainOutput["command"]; + for (let key of Object.keys(cmdToExplain)) { + assert.eq(explainCmd[key], cmdToExplain[key]); + } + } +} + +// Test 'find'. +testExplainContainsCommand({find: collName, filter: {}}); +testExplainContainsCommand({find: collName, filter: {_id: 1}}); + +// Test 'aggregate'. +testExplainContainsCommand({aggregate: collName, pipeline: [{$match: {_id: 1}}], cursor: {}}); + +// Test 'update'. +testExplainContainsCommand({update: collName, updates: [{q: {_id: 1}, u: {_id: 10}}]}); + +// Test 'delete'. +testExplainContainsCommand({delete: collName, deletes: [{q: {_id: 1}, limit: 0}]}); + +// Test 'findAndModify'. +testExplainContainsCommand({findAndModify: collName, query: {_id: 10}, update: {_id: 1}}); +testExplainContainsCommand({findAndModify: collName, query: {_id: 1}, remove: true}); + +// Test 'count'. +testExplainContainsCommand({count: collName, query: {_id: 1}}); + +// Test 'distinct'. +testExplainContainsCommand({distinct: collName, key: "a"}); +})(); diff --git a/src/mongo/db/commands/count_cmd.cpp b/src/mongo/db/commands/count_cmd.cpp index ed880886f40..2f9a844950b 100644 --- a/src/mongo/db/commands/count_cmd.cpp +++ b/src/mongo/db/commands/count_cmd.cpp @@ -198,7 +198,7 @@ public: auto exec = std::move(statusWithPlanExecutor.getValue()); auto bodyBuilder = result->getBodyBuilder(); - Explain::explainStages(exec.get(), collection, verbosity, BSONObj(), &bodyBuilder); + Explain::explainStages(exec.get(), collection, verbosity, BSONObj(), cmdObj, &bodyBuilder); return Status::OK(); } diff --git a/src/mongo/db/commands/distinct.cpp b/src/mongo/db/commands/distinct.cpp index ab5f0145184..24ff3604b35 100644 --- a/src/mongo/db/commands/distinct.cpp +++ b/src/mongo/db/commands/distinct.cpp @@ -180,7 +180,8 @@ public: getExecutorDistinct(&collection, QueryPlannerParams::DEFAULT, &parsedDistinct)); auto bodyBuilder = result->getBodyBuilder(); - Explain::explainStages(executor.get(), collection, verbosity, BSONObj(), &bodyBuilder); + Explain::explainStages( + executor.get(), collection, verbosity, BSONObj(), cmdObj, &bodyBuilder); return Status::OK(); } diff --git a/src/mongo/db/commands/find_and_modify.cpp b/src/mongo/db/commands/find_and_modify.cpp index cade28cbfa1..4586958eeb7 100644 --- a/src/mongo/db/commands/find_and_modify.cpp +++ b/src/mongo/db/commands/find_and_modify.cpp @@ -299,7 +299,7 @@ public: auto bodyBuilder = result->getBodyBuilder(); Explain::explainStages( - exec.get(), collection.getCollection(), verbosity, BSONObj(), &bodyBuilder); + exec.get(), collection.getCollection(), verbosity, BSONObj(), cmdObj, &bodyBuilder); } else { auto request = UpdateRequest(); request.setNamespaceString(nsString); @@ -323,7 +323,7 @@ public: auto bodyBuilder = result->getBodyBuilder(); Explain::explainStages( - exec.get(), collection.getCollection(), verbosity, BSONObj(), &bodyBuilder); + exec.get(), collection.getCollection(), verbosity, BSONObj(), cmdObj, &bodyBuilder); } return Status::OK(); diff --git a/src/mongo/db/commands/find_cmd.cpp b/src/mongo/db/commands/find_cmd.cpp index afcb39f35c9..142d6bb9464 100644 --- a/src/mongo/db/commands/find_cmd.cpp +++ b/src/mongo/db/commands/find_cmd.cpp @@ -298,7 +298,8 @@ public: auto bodyBuilder = result->getBodyBuilder(); // Got the execution tree. Explain it. - Explain::explainStages(exec.get(), collection, verbosity, BSONObj(), &bodyBuilder); + Explain::explainStages( + exec.get(), collection, verbosity, BSONObj(), _request.body, &bodyBuilder); } /** diff --git a/src/mongo/db/commands/map_reduce_agg.cpp b/src/mongo/db/commands/map_reduce_agg.cpp index 84ec6647e60..4e404097d93 100644 --- a/src/mongo/db/commands/map_reduce_agg.cpp +++ b/src/mongo/db/commands/map_reduce_agg.cpp @@ -144,7 +144,7 @@ bool runAggregationMapReduce(OperationContext* opCtx, if (expCtx->explain) { Explain::explainPipeline( - exec.get(), false /* executePipeline */, *expCtx->explain, &result); + exec.get(), false /* executePipeline */, *expCtx->explain, cmd, &result); } PlanSummaryStats planSummaryStats; diff --git a/src/mongo/db/commands/run_aggregate.cpp b/src/mongo/db/commands/run_aggregate.cpp index 753c6ca1f79..7c8f6565026 100644 --- a/src/mongo/db/commands/run_aggregate.cpp +++ b/src/mongo/db/commands/run_aggregate.cpp @@ -761,7 +761,7 @@ Status runAggregate(OperationContext* opCtx, auto bodyBuilder = result->getBodyBuilder(); if (auto pipelineExec = dynamic_cast<PlanExecutorPipeline*>(explainExecutor)) { Explain::explainPipeline( - pipelineExec, true /* executePipeline */, *(expCtx->explain), &bodyBuilder); + pipelineExec, true /* executePipeline */, *(expCtx->explain), cmdObj, &bodyBuilder); } else { invariant(explainExecutor->getOpCtx() == opCtx); // The explainStages() function for a non-pipeline executor may need to execute the plan @@ -773,6 +773,7 @@ Status runAggregate(OperationContext* opCtx, ctx->getCollection(), *(expCtx->explain), BSON("optimizedPipeline" << true), + cmdObj, &bodyBuilder); } } else { diff --git a/src/mongo/db/commands/write_commands/write_commands.cpp b/src/mongo/db/commands/write_commands/write_commands.cpp index af7b96c9741..de4e0632cb8 100644 --- a/src/mongo/db/commands/write_commands/write_commands.cpp +++ b/src/mongo/db/commands/write_commands/write_commands.cpp @@ -557,8 +557,12 @@ private: &parsedUpdate, verbosity)); auto bodyBuilder = result->getBodyBuilder(); - Explain::explainStages( - exec.get(), collection.getCollection(), verbosity, BSONObj(), &bodyBuilder); + Explain::explainStages(exec.get(), + collection.getCollection(), + verbosity, + BSONObj(), + _commandObj, + &bodyBuilder); } write_ops::Update _batch; @@ -600,8 +604,9 @@ private: class Invocation final : public InvocationBase { public: Invocation(const WriteCommand* cmd, const OpMsgRequest& request) - : InvocationBase(cmd, request), _batch(DeleteOp::parse(request)) {} - + : InvocationBase(cmd, request), + _batch(DeleteOp::parse(request)), + _commandObj(request.body) {} private: NamespaceString ns() const override { @@ -654,11 +659,17 @@ private: &parsedDelete, verbosity)); auto bodyBuilder = result->getBodyBuilder(); - Explain::explainStages( - exec.get(), collection.getCollection(), verbosity, BSONObj(), &bodyBuilder); + Explain::explainStages(exec.get(), + collection.getCollection(), + verbosity, + BSONObj(), + _commandObj, + &bodyBuilder); } write_ops::Delete _batch; + + const BSONObj& _commandObj; }; std::unique_ptr<CommandInvocation> parse(OperationContext*, diff --git a/src/mongo/db/pipeline/document_source_cursor.cpp b/src/mongo/db/pipeline/document_source_cursor.cpp index a9310e2ca7c..2bfc196c45c 100644 --- a/src/mongo/db/pipeline/document_source_cursor.cpp +++ b/src/mongo/db/pipeline/document_source_cursor.cpp @@ -228,6 +228,7 @@ Value DocumentSourceCursor::serialize(boost::optional<ExplainOptions::Verbosity> _execStatus, _winningPlanTrialStats, BSONObj(), + BSONObj(), &explainStatsBuilder); } diff --git a/src/mongo/db/query/explain.cpp b/src/mongo/db/query/explain.cpp index a921e706501..17257934cc8 100644 --- a/src/mongo/db/query/explain.cpp +++ b/src/mongo/db/query/explain.cpp @@ -66,6 +66,7 @@ namespace mongo { namespace { + /** * Adds the 'queryPlanner' explain section to the BSON object being built by 'out'. * @@ -269,6 +270,7 @@ void Explain::explainStages(PlanExecutor* exec, Status executePlanStatus, boost::optional<PlanExplainer::PlanStatsDetails> winningPlanTrialStats, BSONObj extraInfo, + const BSONObj& command, BSONObjBuilder* out) { // // Use the stats trees to produce explain BSON. @@ -281,11 +283,14 @@ void Explain::explainStages(PlanExecutor* exec, if (verbosity >= ExplainOptions::Verbosity::kExecStats) { generateExecutionInfo(exec, verbosity, executePlanStatus, winningPlanTrialStats, out); } + + explain_common::appendIfRoom(command, "command", out); } void Explain::explainPipeline(PlanExecutor* exec, bool executePipeline, ExplainOptions::Verbosity verbosity, + const BSONObj& command, BSONObjBuilder* out) { invariant(exec); invariant(out); @@ -303,12 +308,15 @@ void Explain::explainPipeline(PlanExecutor* exec, *out << "stages" << Value(pipelineExec->writeExplainOps(verbosity)); explain_common::generateServerInfo(out); + + explain_common::appendIfRoom(command, "command", out); } void Explain::explainStages(PlanExecutor* exec, const CollectionPtr& collection, ExplainOptions::Verbosity verbosity, BSONObj extraInfo, + const BSONObj& command, BSONObjBuilder* out) { auto&& explainer = exec->getPlanExplainer(); auto winningPlanTrialStats = explainer.getWinningPlanStats(verbosity); @@ -331,8 +339,14 @@ void Explain::explainStages(PlanExecutor* exec, } } - explainStages( - exec, *collectionPtr, verbosity, executePlanStatus, winningPlanTrialStats, extraInfo, out); + explainStages(exec, + *collectionPtr, + verbosity, + executePlanStatus, + winningPlanTrialStats, + extraInfo, + command, + out); explain_common::generateServerInfo(out); } diff --git a/src/mongo/db/query/explain.h b/src/mongo/db/query/explain.h index b59cccb1c96..453e518d85a 100644 --- a/src/mongo/db/query/explain.h +++ b/src/mongo/db/query/explain.h @@ -59,6 +59,8 @@ public: * * The 'extraInfo' parameter specifies additional information to include into the output. * + * The 'command' parameter represents the command object that is being explained. + * * Does not take ownership of its arguments. * * During this call it may be required to execute the plan to collect statistics. If the @@ -72,6 +74,7 @@ public: const CollectionPtr& collection, ExplainOptions::Verbosity verbosity, BSONObj extraInfo, + const BSONObj& command, BSONObjBuilder* out); /** * Adds "queryPlanner" and "executionStats" (if requested in verbosity) fields to 'out'. Unlike @@ -88,6 +91,7 @@ public: * query wasn't executed). * - 'winningPlanTrialStats' is the stats of the winning plan during the trial period. May be * nullptr. + * - 'command' represents the command object that is being explained. * - 'out' is the builder for the explain output. */ static void explainStages( @@ -97,6 +101,7 @@ public: Status executePlanStatus, boost::optional<PlanExplainer::PlanStatsDetails> winningPlanTrialStats, BSONObj extraInfo, + const BSONObj& command, BSONObjBuilder* out); /** @@ -108,10 +113,13 @@ public: * If 'verbosity' >= 'kExecStats' the 'executePipeline' flag is used to indicate whether the * pipeline needs to be executed first, before the stats is collected. Otherwise, it is assumed * that the plan was already executed until EOF and the stats are ready for collection. + * + * The 'command' parameter represents the command object that is being explained. */ static void explainPipeline(PlanExecutor* exec, bool executePipeline, ExplainOptions::Verbosity verbosity, + const BSONObj& command, BSONObjBuilder* out); /** diff --git a/src/mongo/db/query/explain_common.cpp b/src/mongo/db/query/explain_common.cpp index 01242acf6fd..fd0b918bb69 100644 --- a/src/mongo/db/query/explain_common.cpp +++ b/src/mongo/db/query/explain_common.cpp @@ -47,4 +47,24 @@ void generateServerInfo(BSONObjBuilder* out) { serverBob.doneFast(); } +bool appendIfRoom(const BSONObj& toAppend, StringData fieldName, BSONObjBuilder* out) { + if ((out->len() + toAppend.objsize()) < BSONObjMaxUserSize) { + out->append(fieldName, toAppend); + return true; + } + + // The reserved buffer size for the warning message if 'out' exceeds the max BSON user size. + const int warningMsgSize = fieldName.size() + 60; + + // Unless 'out' has already exceeded the max BSON user size, add a warning indicating + // that data has been truncated. + if (out->len() < BSONObjMaxUserSize - warningMsgSize) { + out->append("warning", + str::stream() << "'" << fieldName << "'" + << " has been omitted due to BSON size limit"); + } + + return false; +} + } // namespace mongo::explain_common diff --git a/src/mongo/db/query/explain_common.h b/src/mongo/db/query/explain_common.h index d9b035beb8f..bc5637cebd1 100644 --- a/src/mongo/db/query/explain_common.h +++ b/src/mongo/db/query/explain_common.h @@ -43,4 +43,10 @@ namespace mongo::explain_common { */ void generateServerInfo(BSONObjBuilder* out); +/** + * Conditionally appends a BSONObj to 'bob' depending on whether or not the maximum user size for a + * BSON object will be exceeded. + */ +bool appendIfRoom(const BSONObj& toAppend, StringData fieldName, BSONObjBuilder* out); + } // namespace mongo::explain_common diff --git a/src/mongo/db/query/find.cpp b/src/mongo/db/query/find.cpp index f344eb7cfca..36bf3c974ed 100644 --- a/src/mongo/db/query/find.cpp +++ b/src/mongo/db/query/find.cpp @@ -634,8 +634,12 @@ bool runQuery(OperationContext* opCtx, bb.skip(sizeof(QueryResult::Value)); BSONObjBuilder explainBob; - Explain::explainStages( - exec.get(), collection.getCollection(), verbosity, BSONObj(), &explainBob); + Explain::explainStages(exec.get(), + collection.getCollection(), + verbosity, + BSONObj(), + upconvertedQuery, + &explainBob); // Add the resulting object to the return buffer. BSONObj explainObj = explainBob.obj(); diff --git a/src/mongo/dbtests/query_stage_multiplan.cpp b/src/mongo/dbtests/query_stage_multiplan.cpp index 2ac93c0e04d..3275bcaed17 100644 --- a/src/mongo/dbtests/query_stage_multiplan.cpp +++ b/src/mongo/dbtests/query_stage_multiplan.cpp @@ -511,8 +511,12 @@ TEST_F(QueryStageMultiPlanTest, MPSExplainAllPlans) { ASSERT_EQ(root->bestPlanIdx(), 0); BSONObjBuilder bob; - Explain::explainStages( - exec.get(), ctx.getCollection(), ExplainOptions::Verbosity::kExecAllPlans, BSONObj(), &bob); + Explain::explainStages(exec.get(), + ctx.getCollection(), + ExplainOptions::Verbosity::kExecAllPlans, + BSONObj(), + BSONObj(), + &bob); BSONObj explained = bob.done(); ASSERT_EQ(explained["executionStats"]["nReturned"].Int(), nDocs); diff --git a/src/mongo/s/commands/cluster_count_cmd.cpp b/src/mongo/s/commands/cluster_count_cmd.cpp index 00e186d4d05..fc8c7495194 100644 --- a/src/mongo/s/commands/cluster_count_cmd.cpp +++ b/src/mongo/s/commands/cluster_count_cmd.cpp @@ -264,7 +264,7 @@ public: auto bodyBuilder = result->getBodyBuilder(); return ClusterExplain::buildExplainResult( - opCtx, shardResponses, mongosStageName, millisElapsed, &bodyBuilder); + opCtx, shardResponses, mongosStageName, millisElapsed, cmdObj, &bodyBuilder); } private: diff --git a/src/mongo/s/commands/cluster_distinct_cmd.cpp b/src/mongo/s/commands/cluster_distinct_cmd.cpp index 9f836644ff5..41475a8cec2 100644 --- a/src/mongo/s/commands/cluster_distinct_cmd.cpp +++ b/src/mongo/s/commands/cluster_distinct_cmd.cpp @@ -156,7 +156,7 @@ public: auto bodyBuilder = result->getBodyBuilder(); return ClusterExplain::buildExplainResult( - opCtx, shardResponses, mongosStageName, millisElapsed, &bodyBuilder); + opCtx, shardResponses, mongosStageName, millisElapsed, cmdObj, &bodyBuilder); } bool run(OperationContext* opCtx, diff --git a/src/mongo/s/commands/cluster_explain.cpp b/src/mongo/s/commands/cluster_explain.cpp index 356f6c7c7a1..8c4cdb5e699 100644 --- a/src/mongo/s/commands/cluster_explain.cpp +++ b/src/mongo/s/commands/cluster_explain.cpp @@ -327,6 +327,7 @@ Status ClusterExplain::buildExplainResult( const vector<AsyncRequestsSender::Response>& shardResponses, const char* mongosStageName, long long millisElapsed, + const BSONObj& command, BSONObjBuilder* out) { // Explain only succeeds if all shards support the explain command. try { @@ -338,6 +339,7 @@ Status ClusterExplain::buildExplainResult( buildPlannerInfo(opCtx, shardResponses, mongosStageName, out); buildExecStats(shardResponses, mongosStageName, millisElapsed, out); explain_common::generateServerInfo(out); + appendIfRoom(out, command, "command"); return Status::OK(); } diff --git a/src/mongo/s/commands/cluster_explain.h b/src/mongo/s/commands/cluster_explain.h index 23e7f799194..60570c855d3 100644 --- a/src/mongo/s/commands/cluster_explain.h +++ b/src/mongo/s/commands/cluster_explain.h @@ -69,6 +69,7 @@ public: const std::vector<AsyncRequestsSender::Response>& shardResponses, const char* mongosStageName, long long millisElapsed, + const BSONObj& command, BSONObjBuilder* out); diff --git a/src/mongo/s/commands/cluster_find_and_modify_cmd.cpp b/src/mongo/s/commands/cluster_find_and_modify_cmd.cpp index 0dba2c8ba40..2bfbb451bfd 100644 --- a/src/mongo/s/commands/cluster_find_and_modify_cmd.cpp +++ b/src/mongo/s/commands/cluster_find_and_modify_cmd.cpp @@ -264,8 +264,12 @@ public: shard->getId(), response, shard->getConnString().getServers().front()}; auto bodyBuilder = result->getBodyBuilder(); - return ClusterExplain::buildExplainResult( - opCtx, {arsResponse}, ClusterExplain::kSingleShard, millisElapsed, &bodyBuilder); + return ClusterExplain::buildExplainResult(opCtx, + {arsResponse}, + ClusterExplain::kSingleShard, + millisElapsed, + cmdObj, + &bodyBuilder); } bool run(OperationContext* opCtx, diff --git a/src/mongo/s/commands/cluster_find_cmd.cpp b/src/mongo/s/commands/cluster_find_cmd.cpp index 3bd8ff6c7c3..c1fc600c88e 100644 --- a/src/mongo/s/commands/cluster_find_cmd.cpp +++ b/src/mongo/s/commands/cluster_find_cmd.cpp @@ -179,8 +179,12 @@ public: ClusterExplain::getStageNameForReadOp(shardResponses.size(), _request.body); auto bodyBuilder = result->getBodyBuilder(); - uassertStatusOK(ClusterExplain::buildExplainResult( - opCtx, shardResponses, mongosStageName, millisElapsed, &bodyBuilder)); + uassertStatusOK(ClusterExplain::buildExplainResult(opCtx, + shardResponses, + mongosStageName, + millisElapsed, + _request.body, + &bodyBuilder)); } catch (const ExceptionFor<ErrorCodes::CommandOnShardedViewNotSupportedOnMongod>& ex) { auto bodyBuilder = result->getBodyBuilder(); diff --git a/src/mongo/s/commands/cluster_write_cmd.cpp b/src/mongo/s/commands/cluster_write_cmd.cpp index 1340e023275..48d1d36d206 100644 --- a/src/mongo/s/commands/cluster_write_cmd.cpp +++ b/src/mongo/s/commands/cluster_write_cmd.cpp @@ -596,8 +596,12 @@ private: _commandOpWrite( opCtx, _batchedRequest.getNS(), explainCmd, targetingBatchItem, &shardResponses); auto bodyBuilder = result->getBodyBuilder(); - uassertStatusOK(ClusterExplain::buildExplainResult( - opCtx, shardResponses, ClusterExplain::kWriteOnShards, timer.millis(), &bodyBuilder)); + uassertStatusOK(ClusterExplain::buildExplainResult(opCtx, + shardResponses, + ClusterExplain::kWriteOnShards, + timer.millis(), + _request->body, + &bodyBuilder)); } NamespaceString ns() const override { diff --git a/src/mongo/s/commands/strategy.cpp b/src/mongo/s/commands/strategy.cpp index 12559d00c9f..44a5f0ad044 100644 --- a/src/mongo/s/commands/strategy.cpp +++ b/src/mongo/s/commands/strategy.cpp @@ -1339,6 +1339,6 @@ void Strategy::explainFind(OperationContext* opCtx, ClusterExplain::getStageNameForReadOp(shardResponses.size(), findCommand); uassertStatusOK(ClusterExplain::buildExplainResult( - opCtx, shardResponses, mongosStageName, millisElapsed, out)); + opCtx, shardResponses, mongosStageName, millisElapsed, findCommand, out)); } } // namespace mongo diff --git a/src/mongo/s/query/cluster_aggregate.cpp b/src/mongo/s/query/cluster_aggregate.cpp index 4b2da5f2b34..e99c6b70ad5 100644 --- a/src/mongo/s/query/cluster_aggregate.cpp +++ b/src/mongo/s/query/cluster_aggregate.cpp @@ -352,6 +352,12 @@ Status ClusterAggregate::runAggregate(OperationContext* opCtx, updateHostsTargetedMetrics(opCtx, namespaces.executionNss, cm, involvedNamespaces); // Report usage statistics for each stage in the pipeline. liteParsedPipeline.tickGlobalStageCounters(); + + // Add 'command' object to explain output. + if (expCtx->explain) { + explain_common::appendIfRoom( + request.serializeToCommandObj().toBson(), "command", result); + } } return status; } |