// Tests the behavior of explain() when used with the aggregation pipeline and limits. // // This test makes assumptions about how the explain output will be formatted, so cannot be // transformed to be put inside a $facet stage, or when pipeline optimization is disabled. // @tags: [ // do_not_wrap_aggregations_in_facets, // requires_pipeline_optimization, // # Implicit index creation may change the plan/engine used. // assumes_no_implicit_index_creation, // ] (function() { "use strict"; load("jstests/libs/analyze_plan.js"); // For getAggPlanStages(). load("jstests/libs/sbe_util.js"); // For checkSBEEnabled. let coll = db.explain_limit; const kCollSize = 105; const kLimit = 10; const isSBEEnabled = checkSBEEnabled(db); // Return whether or explain() was successful and contained the appropriate fields given the // requested verbosity. Checks that the number of documents examined and returned are correct given // the value of the limit. function checkResults({results, verbosity}) { const [cursorSubdocs, limitAmount] = (() => { if (verbosity != "queryPlanner" && isSBEEnabled) { // We cannot use the "executionStats" section for SBE plans without some pre-processing, // since it has different explain format. To find execution stats for the LIMIT stages // from the "queryPlanner" section (there could be multiple of such stages if we're in a // sharded environment, one for each shard) we first extract their 'planNodeIds' into a // set. Then we filter out all "limit" stages from the "executionStats" section by // keeping only correlated plan stages, and return the final array. Since the name of // the field holding the limit amount also differs in SBE, we return a proper name as // well. const useQueryPlannerSection = true; const queryPlannerStages = getAggPlanStages(results, "LIMIT", useQueryPlannerSection); const execStatsStages = getAggPlanStages(results, "limit"); assert.gt(queryPlannerStages.length, 0, results); assert.gt(execStatsStages.length, 0, results); const planNodeIds = new Set(queryPlannerStages.map(stage => stage.planNodeId)); return [execStatsStages.filter(stage => planNodeIds.has(stage.planNodeId)), "limit"]; } return [getAggPlanStages(results, "LIMIT"), "limitAmount"]; })(); assert.gt(cursorSubdocs.length, 0, results); for (let stageResult of cursorSubdocs) { assert.eq(stageResult[limitAmount], NumberLong(kLimit), cursorSubdocs); if (verbosity !== "queryPlanner") { assert.eq(stageResult.nReturned, NumberLong(kLimit), results); } } // Explain should report that we only have to examine as many documents as the limit. if (verbosity !== "queryPlanner") { if (results.hasOwnProperty("executionStats")) { assert.eq(results.executionStats.nReturned, kLimit, results); assert.eq(results.executionStats.totalDocsExamined, kLimit, results); } else { // This must be output for a sharded explain. Verify that each shard reports the // expected execution stats. assert(results.hasOwnProperty("shards")); for (let elem in results.shards) { const shardExecStats = results.shards[elem].executionStats; assert.eq(shardExecStats.nReturned, kLimit, results); assert.eq(shardExecStats.totalDocsExamined, kLimit, results); } } } } // explain() should respect limit. coll.drop(); assert.commandWorked(coll.createIndex({a: 1})); for (let i = 0; i < kCollSize; i++) { assert.commandWorked(coll.insert({a: 1})); } const pipeline = [{$match: {a: 1}}, {$limit: kLimit}]; let plannerLevel = coll.explain("queryPlanner").aggregate(pipeline); checkResults({results: plannerLevel, verbosity: "queryPlanner"}); let execLevel = coll.explain("executionStats").aggregate(pipeline); checkResults({results: execLevel, verbosity: "executionStats"}); let allPlansExecLevel = coll.explain("allPlansExecution").aggregate(pipeline); checkResults({results: allPlansExecLevel, verbosity: "allPlansExecution"}); // Create a second index so that more than one plan is available. assert.commandWorked(coll.createIndex({a: 1, b: 1})); plannerLevel = coll.explain("queryPlanner").aggregate(pipeline); checkResults({results: plannerLevel, verbosity: "queryPlanner"}); execLevel = coll.explain("executionStats").aggregate(pipeline); checkResults({results: execLevel, verbosity: "executionStats"}); allPlansExecLevel = coll.explain("allPlansExecution").aggregate(pipeline); checkResults({results: allPlansExecLevel, verbosity: "allPlansExecution"}); })();