diff options
author | Anton Korshunov <anton.korshunov@mongodb.com> | 2021-01-18 22:06:40 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2021-01-30 03:46:12 +0000 |
commit | c088ca3d8fef6d806f92ea2d5869d8910e5cd30c (patch) | |
tree | e5d69b3273488d9d0c9aa2894184cc8a7545aac9 | |
parent | c08203fe14a89e789b7dc5353c427e34c4c2b31e (diff) | |
download | mongo-c088ca3d8fef6d806f92ea2d5869d8910e5cd30c.tar.gz |
SERVER-53270 Include SBE PlanStage tree string into explain output
50 files changed, 483 insertions, 281 deletions
diff --git a/jstests/core/bittest.js b/jstests/core/bittest.js index e5885be06de..9b3fa8639b6 100644 --- a/jstests/core/bittest.js +++ b/jstests/core/bittest.js @@ -10,7 +10,7 @@ var coll = db.jstests_bitwise; function assertQueryCorrect(query, count) { var explain = coll.find(query).explain("executionStats"); - assert(isCollscan(db, explain.queryPlanner.winningPlan), + assert(isCollscan(db, getWinningPlan(explain.queryPlanner)), "expected bit test query plan to be COLLSCAN"); assert.eq( count, explain.executionStats.nReturned, "bit test query not returning correct documents"); diff --git a/jstests/core/coveredIndex1.js b/jstests/core/coveredIndex1.js index a61b6381db3..34e98bd76a0 100644 --- a/jstests/core/coveredIndex1.js +++ b/jstests/core/coveredIndex1.js @@ -40,7 +40,7 @@ function assertIfQueryIsCovered(query, projection, isCovered, hint) { assert(explain.hasOwnProperty("queryPlanner"), tojson(explain)); assert(explain.queryPlanner.hasOwnProperty("winningPlan"), tojson(explain)); - const winningPlan = explain.queryPlanner.winningPlan; + const winningPlan = getWinningPlan(explain.queryPlanner); if (isCovered) { assert(isIndexOnly(db, winningPlan), "Query " + tojson(query) + " with projection " + tojson(projection) + diff --git a/jstests/core/coveredIndex2.js b/jstests/core/coveredIndex2.js index dc1fb1cf585..3b7891eaa06 100644 --- a/jstests/core/coveredIndex2.js +++ b/jstests/core/coveredIndex2.js @@ -5,31 +5,34 @@ // assumes_unsharded_collection, // requires_fastcount, // ] +(function() { +"use strict"; -t = db["jstests_coveredIndex2"]; +const t = db["jstests_coveredIndex2"]; t.drop(); // Include helpers for analyzing explain output. load("jstests/libs/analyze_plan.js"); -t.save({a: 1}); -t.save({a: 2}); +assert.commandWorked(t.insert({a: 1})); +assert.commandWorked(t.insert({a: 2})); assert.eq(t.findOne({a: 1}).a, 1, "Cannot find right record"); assert.eq(t.count(), 2, "Not right length"); // use simple index -t.createIndex({a: 1}); -var plan = t.find({a: 1}).explain(); -assert(!isIndexOnly(db, plan.queryPlanner.winningPlan), +assert.commandWorked(t.createIndex({a: 1})); +let plan = t.find({a: 1}).explain(); +assert(!isIndexOnly(db, getWinningPlan(plan.queryPlanner)), "Find using covered index but all fields are returned"); -var plan = t.find({a: 1}, {a: 1}).explain(); -assert(!isIndexOnly(db, plan.queryPlanner.winningPlan), +plan = t.find({a: 1}, {a: 1}).explain(); +assert(!isIndexOnly(db, getWinningPlan(plan.queryPlanner)), "Find using covered index but _id is returned"); -var plan = t.find({a: 1}, {a: 1, _id: 0}).explain(); -assert(isIndexOnly(db, plan.queryPlanner.winningPlan), "Find is not using covered index"); +plan = t.find({a: 1}, {a: 1, _id: 0}).explain(); +assert(isIndexOnly(db, getWinningPlan(plan.queryPlanner)), "Find is not using covered index"); // add multikey -t.save({a: [3, 4]}); -var plan = t.find({a: 1}, {a: 1, _id: 0}).explain(); -assert(!isIndexOnly(db, plan.queryPlanner.winningPlan), +assert.commandWorked(t.insert({a: [3, 4]})); +plan = t.find({a: 1}, {a: 1, _id: 0}).explain(); +assert(!isIndexOnly(db, getWinningPlan(plan.queryPlanner)), "Find is using covered index even after multikey insert"); +}()); diff --git a/jstests/core/covered_index_negative_1.js b/jstests/core/covered_index_negative_1.js index 5b6f68a56a2..f148a32afc7 100644 --- a/jstests/core/covered_index_negative_1.js +++ b/jstests/core/covered_index_negative_1.js @@ -23,7 +23,7 @@ coll.createIndex({f: "hashed"}); // Test no projection var plan = coll.find({a: 10, b: "strvar_10", c: 0}).hint({a: 1, b: -1, c: 1}).explain("executionStats"); -assert(!isIndexOnly(db, plan.queryPlanner.winningPlan), +assert(!isIndexOnly(db, getWinningPlan(plan.queryPlanner)), "negative.1.1 - indexOnly should be false on a non covered query"); assert.neq(0, plan.executionStats.totalDocsExamined, @@ -33,7 +33,7 @@ assert.neq(0, var plan = coll.find({a: 10, b: "strvar_10", c: 0}, {a: 1, b: 1, c: 1}) .hint({a: 1, b: -1, c: 1}) .explain("executionStats"); -assert(!isIndexOnly(db, plan.queryPlanner.winningPlan), +assert(!isIndexOnly(db, getWinningPlan(plan.queryPlanner)), "negative.1.2 - indexOnly should be false on a non covered query"); assert.neq(0, plan.executionStats.totalDocsExamined, @@ -41,7 +41,7 @@ assert.neq(0, // Test projection of non-indexed field var plan = coll.find({d: 100}, {d: 1, c: 1, _id: 0}).hint({d: 1}).explain("executionStats"); -assert(!isIndexOnly(db, plan.queryPlanner.winningPlan), +assert(!isIndexOnly(db, getWinningPlan(plan.queryPlanner)), "negative.1.3 - indexOnly should be false on a non covered query"); assert.neq(0, plan.executionStats.totalDocsExamined, @@ -49,7 +49,7 @@ assert.neq(0, // Test query and projection on a multi-key index var plan = coll.find({e: 99}, {e: 1, _id: 0}).hint({e: 1}).explain("executionStats"); -assert(!isIndexOnly(db, plan.queryPlanner.winningPlan), +assert(!isIndexOnly(db, getWinningPlan(plan.queryPlanner)), "negative.1.4 - indexOnly should be false on a non covered query"); assert.neq(0, plan.executionStats.totalDocsExamined, @@ -77,7 +77,7 @@ assert.neq(0, var plan = coll.find({d: {$lt: 1000}}, {a: 1, b: 1, c: 1, _id: 0}) .hint({a: 1, b: -1, c: 1}) .explain("executionStats"); -assert(!isIndexOnly(db, plan.queryPlanner.winningPlan), +assert(!isIndexOnly(db, getWinningPlan(plan.queryPlanner)), "negative.1.7 - indexOnly should be false on a non covered query"); assert.neq(0, plan.executionStats.totalDocsExamined, @@ -85,7 +85,7 @@ assert.neq(0, // Test query on hashed indexed field var plan = coll.find({f: 10}, {f: 1, _id: 0}).hint({f: "hashed"}).explain("executionStats"); -assert(!isIndexOnly(db, plan.queryPlanner.winningPlan), +assert(!isIndexOnly(db, getWinningPlan(plan.queryPlanner)), "negative.1.8 - indexOnly should be false on a non covered query"); assert.neq(0, plan.executionStats.totalDocsExamined, diff --git a/jstests/core/covered_multikey.js b/jstests/core/covered_multikey.js index c1bea4d99fc..9270a2b2c20 100644 --- a/jstests/core/covered_multikey.js +++ b/jstests/core/covered_multikey.js @@ -23,8 +23,9 @@ assert.commandWorked(coll.createIndex({a: 1, b: 1})); assert.eq(1, coll.find({a: 1, b: 2}, {_id: 0, a: 1}).itcount()); assert.eq({a: 1}, coll.findOne({a: 1, b: 2}, {_id: 0, a: 1})); let explainRes = coll.explain("queryPlanner").find({a: 1, b: 2}, {_id: 0, a: 1}).finish(); -assert(isIxscan(db, explainRes.queryPlanner.winningPlan)); -assert(!planHasStage(db, explainRes.queryPlanner.winningPlan, "FETCH")); +let winningPlan = getWinningPlan(explainRes.queryPlanner); +assert(isIxscan(db, winningPlan)); +assert(!planHasStage(db, winningPlan, "FETCH")); coll.drop(); assert.commandWorked(coll.insert({a: 1, b: [1, 2, 3], c: 3, d: 5})); @@ -39,73 +40,81 @@ explainRes = coll.explain("queryPlanner") .find({a: 1, b: 1}, {_id: 0, c: 1, d: 1}) .sort({c: -1, d: -1}) .finish(); -assert(!planHasStage(db, explainRes.queryPlanner.winningPlan, "FETCH")); +winningPlan = getWinningPlan(explainRes.queryPlanner); +assert(!planHasStage(db, winningPlan, "FETCH")); // Verify that a query cannot be covered over a path which is multikey due to an empty array. -coll.drop(); +assert(coll.drop()); assert.commandWorked(coll.insert({a: []})); assert.commandWorked(coll.createIndex({a: 1})); assert.eq({a: []}, coll.findOne({a: []}, {_id: 0, a: 1})); explainRes = coll.explain("queryPlanner").find({a: []}, {_id: 0, a: 1}).finish(); -assert(planHasStage(db, explainRes.queryPlanner.winningPlan, "IXSCAN")); -assert(planHasStage(db, explainRes.queryPlanner.winningPlan, "FETCH")); -let ixscanStage = getPlanStage(explainRes.queryPlanner.winningPlan, "IXSCAN"); +winningPlan = getWinningPlan(explainRes.queryPlanner); +assert(planHasStage(db, winningPlan, "IXSCAN")); +assert(planHasStage(db, winningPlan, "FETCH")); +let ixscanStage = getPlanStage(winningPlan, "IXSCAN"); assert.eq(true, ixscanStage.isMultiKey); // Verify that a query cannot be covered over a path which is multikey due to a single-element // array. -coll.drop(); +assert(coll.drop()); assert.commandWorked(coll.insert({a: [2]})); assert.commandWorked(coll.createIndex({a: 1})); assert.eq({a: [2]}, coll.findOne({a: 2}, {_id: 0, a: 1})); explainRes = coll.explain("queryPlanner").find({a: 2}, {_id: 0, a: 1}).finish(); -assert(planHasStage(db, explainRes.queryPlanner.winningPlan, "IXSCAN")); -assert(planHasStage(db, explainRes.queryPlanner.winningPlan, "FETCH")); -ixscanStage = getPlanStage(explainRes.queryPlanner.winningPlan, "IXSCAN"); +winningPlan = getWinningPlan(explainRes.queryPlanner); +assert(planHasStage(db, winningPlan, "IXSCAN")); +assert(planHasStage(db, winningPlan, "FETCH")); +ixscanStage = getPlanStage(winningPlan, "IXSCAN"); assert.eq(true, ixscanStage.isMultiKey); // Verify that a query cannot be covered over a path which is multikey due to a single-element // array, where the path is made multikey by an update rather than an insert. -coll.drop(); +assert(coll.drop()); assert.commandWorked(coll.insert({a: 2})); assert.commandWorked(coll.createIndex({a: 1})); assert.commandWorked(coll.update({}, {$set: {a: [2]}})); assert.eq({a: [2]}, coll.findOne({a: 2}, {_id: 0, a: 1})); explainRes = coll.explain("queryPlanner").find({a: 2}, {_id: 0, a: 1}).finish(); -assert(planHasStage(db, explainRes.queryPlanner.winningPlan, "IXSCAN")); -assert(planHasStage(db, explainRes.queryPlanner.winningPlan, "FETCH")); -ixscanStage = getPlanStage(explainRes.queryPlanner.winningPlan, "IXSCAN"); +winningPlan = getWinningPlan(explainRes.queryPlanner); +assert(planHasStage(db, winningPlan, "IXSCAN")); +assert(planHasStage(db, winningPlan, "FETCH")); +ixscanStage = getPlanStage(winningPlan, "IXSCAN"); assert.eq(true, ixscanStage.isMultiKey); // Verify that a trailing empty array makes a 2dsphere index multikey. -coll.drop(); +assert(coll.drop()); assert.commandWorked(coll.createIndex({"a.b": 1, c: "2dsphere"})); assert.commandWorked(coll.insert({a: {b: 1}, c: {type: "Point", coordinates: [0, 0]}})); explainRes = coll.explain().find().hint({"a.b": 1, c: "2dsphere"}).finish(); -ixscanStage = getPlanStage(explainRes.queryPlanner.winningPlan, "IXSCAN"); +winningPlan = getWinningPlan(explainRes.queryPlanner); +ixscanStage = getPlanStage(winningPlan, "IXSCAN"); assert.neq(null, ixscanStage); assert.eq(false, ixscanStage.isMultiKey); assert.commandWorked(coll.insert({a: {b: []}, c: {type: "Point", coordinates: [0, 0]}})); explainRes = coll.explain().find().hint({"a.b": 1, c: "2dsphere"}).finish(); -ixscanStage = getPlanStage(explainRes.queryPlanner.winningPlan, "IXSCAN"); +winningPlan = getWinningPlan(explainRes.queryPlanner); +ixscanStage = getPlanStage(winningPlan, "IXSCAN"); assert.neq(null, ixscanStage); assert.eq(true, ixscanStage.isMultiKey); // Verify that a mid-path empty array makes a 2dsphere index multikey. -coll.drop(); +assert(coll.drop()); assert.commandWorked(coll.createIndex({"a.b": 1, c: "2dsphere"})); assert.commandWorked(coll.insert({a: [], c: {type: "Point", coordinates: [0, 0]}})); explainRes = coll.explain().find().hint({"a.b": 1, c: "2dsphere"}).finish(); -ixscanStage = getPlanStage(explainRes.queryPlanner.winningPlan, "IXSCAN"); +winningPlan = getWinningPlan(explainRes.queryPlanner); +ixscanStage = getPlanStage(winningPlan, "IXSCAN"); assert.neq(null, ixscanStage); assert.eq(true, ixscanStage.isMultiKey); // Verify that a single-element array makes a 2dsphere index multikey. -coll.drop(); +assert(coll.drop()); assert.commandWorked(coll.createIndex({"a.b": 1, c: "2dsphere"})); assert.commandWorked(coll.insert({a: {b: [3]}, c: {type: "Point", coordinates: [0, 0]}})); explainRes = coll.explain().find().hint({"a.b": 1, c: "2dsphere"}).finish(); -ixscanStage = getPlanStage(explainRes.queryPlanner.winningPlan, "IXSCAN"); +winningPlan = getWinningPlan(explainRes.queryPlanner); +ixscanStage = getPlanStage(winningPlan, "IXSCAN"); assert.neq(null, ixscanStage); assert.eq(true, ixscanStage.isMultiKey); }()); diff --git a/jstests/core/distinct_compound_index.js b/jstests/core/distinct_compound_index.js index 4e1dfcc5cad..ba2c3ec280f 100644 --- a/jstests/core/distinct_compound_index.js +++ b/jstests/core/distinct_compound_index.js @@ -21,9 +21,9 @@ assert.commandWorked(coll.createIndex({a: 1, b: 1})); const explain_distinct_with_query = coll.explain("executionStats").distinct('b', {a: 1}); assert.commandWorked(explain_distinct_with_query); -assert(planHasStage(db, explain_distinct_with_query.queryPlanner.winningPlan, "DISTINCT_SCAN")); -assert( - planHasStage(db, explain_distinct_with_query.queryPlanner.winningPlan, "PROJECTION_COVERED")); +assert(planHasStage(db, getWinningPlan(explain_distinct_with_query.queryPlanner), "DISTINCT_SCAN")); +assert(planHasStage( + db, getWinningPlan(explain_distinct_with_query.queryPlanner), "PROJECTION_COVERED")); // If the collection is sharded, we expect at most 2 distinct values per shard. If the // collection is not sharded, we expect 2 returned. assert.lte(explain_distinct_with_query.executionStats.nReturned, @@ -31,8 +31,9 @@ assert.lte(explain_distinct_with_query.executionStats.nReturned, const explain_distinct_without_query = coll.explain("executionStats").distinct('b'); assert.commandWorked(explain_distinct_without_query); -assert(planHasStage(db, explain_distinct_without_query.queryPlanner.winningPlan, "COLLSCAN")); -assert(!planHasStage(db, explain_distinct_without_query.queryPlanner.winningPlan, "DISTINCT_SCAN")); +assert(planHasStage(db, getWinningPlan(explain_distinct_without_query.queryPlanner), "COLLSCAN")); +assert(!planHasStage( + db, getWinningPlan(explain_distinct_without_query.queryPlanner), "DISTINCT_SCAN")); assert.eq(40, explain_distinct_without_query.executionStats.nReturned); // Verify that compound special indexes such as '2dsphere' and 'text' can never use index to answer diff --git a/jstests/core/distinct_multikey.js b/jstests/core/distinct_multikey.js index 7208b67fb64..6d5ccd21252 100644 --- a/jstests/core/distinct_multikey.js +++ b/jstests/core/distinct_multikey.js @@ -17,19 +17,21 @@ assert.commandWorked(coll.insert({a: [5, 6, 7]})); let result = coll.distinct("a"); assert.eq([1, 2, 3, 4, 5, 6, 7], result.sort()); let explain = coll.explain("queryPlanner").distinct("a"); -assert(planHasStage(db, explain.queryPlanner.winningPlan, "PROJECTION_COVERED")); -assert(planHasStage(db, explain.queryPlanner.winningPlan, "DISTINCT_SCAN")); +let winningPlan = getWinningPlan(explain.queryPlanner); +assert(planHasStage(db, winningPlan, "PROJECTION_COVERED")); +assert(planHasStage(db, winningPlan, "DISTINCT_SCAN")); // Test that distinct can correctly use a multikey index when there is a predicate. This query // should not be eligible for the distinct scan and cannot be covered. result = coll.distinct("a", {a: 3}); assert.eq([1, 2, 3, 4], result.sort()); explain = coll.explain("queryPlanner").distinct("a", {a: 3}); -assert(planHasStage(db, explain.queryPlanner.winningPlan, "FETCH")); -assert(planHasStage(db, explain.queryPlanner.winningPlan, "IXSCAN")); +winningPlan = getWinningPlan(explain.queryPlanner); +assert(planHasStage(db, winningPlan, "FETCH")); +assert(planHasStage(db, winningPlan, "IXSCAN")); // Test distinct over a dotted multikey field, with a predicate. -coll.drop(); +assert(coll.drop()); assert.commandWorked(coll.createIndex({"a.b": 1})); assert.commandWorked(coll.insert({a: {b: [1, 2, 3]}})); assert.commandWorked(coll.insert({a: {b: [2, 3, 4]}})); @@ -37,12 +39,13 @@ assert.commandWorked(coll.insert({a: {b: [2, 3, 4]}})); result = coll.distinct("a.b", {"a.b": 3}); assert.eq([1, 2, 3, 4], result.sort()); explain = coll.explain("queryPlanner").distinct("a.b", {"a.b": 3}); -assert(planHasStage(db, explain.queryPlanner.winningPlan, "FETCH")); -assert(planHasStage(db, explain.queryPlanner.winningPlan, "IXSCAN")); +winningPlan = getWinningPlan(explain.queryPlanner); +assert(planHasStage(db, winningPlan, "FETCH")); +assert(planHasStage(db, winningPlan, "IXSCAN")); // Test that the distinct scan can be used when there is a predicate and the index is not // multikey. -coll.drop(); +assert(coll.drop()); assert.commandWorked(coll.createIndex({a: 1})); assert.commandWorked(coll.insert({a: 1})); assert.commandWorked(coll.insert({a: 2})); @@ -51,12 +54,13 @@ assert.commandWorked(coll.insert({a: 3})); result = coll.distinct("a", {a: {$gte: 2}}); assert.eq([2, 3], result.sort()); explain = coll.explain("queryPlanner").distinct("a", {a: {$gte: 2}}); -assert(planHasStage(db, explain.queryPlanner.winningPlan, "PROJECTION_COVERED")); -assert(planHasStage(db, explain.queryPlanner.winningPlan, "DISTINCT_SCAN")); +winningPlan = getWinningPlan(explain.queryPlanner); +assert(planHasStage(db, winningPlan, "PROJECTION_COVERED")); +assert(planHasStage(db, winningPlan, "DISTINCT_SCAN")); // Test a distinct which can use a multikey index, where the field being distinct'ed is not // multikey. -coll.drop(); +assert(coll.drop()); assert.commandWorked(coll.createIndex({a: 1, b: 1})); assert.commandWorked(coll.insert({a: 1, b: [2, 3]})); assert.commandWorked(coll.insert({a: 8, b: [3, 4]})); @@ -65,18 +69,20 @@ assert.commandWorked(coll.insert({a: 7, b: [4, 5]})); result = coll.distinct("a", {a: {$gte: 2}}); assert.eq([7, 8], result.sort()); explain = coll.explain("queryPlanner").distinct("a", {a: {$gte: 2}}); -assert(planHasStage(db, explain.queryPlanner.winningPlan, "PROJECTION_COVERED")); -assert(planHasStage(db, explain.queryPlanner.winningPlan, "DISTINCT_SCAN")); +winningPlan = getWinningPlan(explain.queryPlanner); +assert(planHasStage(db, winningPlan, "PROJECTION_COVERED")); +assert(planHasStage(db, winningPlan, "DISTINCT_SCAN")); // Test distinct over a trailing multikey field. result = coll.distinct("b", {a: {$gte: 2}}); assert.eq([3, 4, 5], result.sort()); explain = coll.explain("queryPlanner").distinct("b", {a: {$gte: 2}}); -assert(planHasStage(db, explain.queryPlanner.winningPlan, "FETCH")); -assert(planHasStage(db, explain.queryPlanner.winningPlan, "IXSCAN")); +winningPlan = getWinningPlan(explain.queryPlanner); +assert(planHasStage(db, winningPlan, "FETCH")); +assert(planHasStage(db, winningPlan, "IXSCAN")); // Test distinct over a trailing non-multikey field, where the leading field is multikey. -coll.drop(); +assert(coll.drop()); assert.commandWorked(coll.createIndex({a: 1, b: 1})); assert.commandWorked(coll.insert({a: [2, 3], b: 1})); assert.commandWorked(coll.insert({a: [3, 4], b: 8})); @@ -85,11 +91,12 @@ assert.commandWorked(coll.insert({a: [3, 5], b: 7})); result = coll.distinct("b", {a: 3}); assert.eq([1, 7, 8], result.sort()); explain = coll.explain("queryPlanner").distinct("b", {a: 3}); -assert(planHasStage(db, explain.queryPlanner.winningPlan, "PROJECTION_COVERED")); -assert(planHasStage(db, explain.queryPlanner.winningPlan, "DISTINCT_SCAN")); +winningPlan = getWinningPlan(explain.queryPlanner); +assert(planHasStage(db, winningPlan, "PROJECTION_COVERED")); +assert(planHasStage(db, winningPlan, "DISTINCT_SCAN")); // Test distinct over a trailing non-multikey dotted path where the leading field is multikey. -coll.drop(); +assert(coll.drop()); assert.commandWorked(coll.createIndex({a: 1, "b.c": 1})); assert.commandWorked(coll.insert({a: [2, 3], b: {c: 1}})); assert.commandWorked(coll.insert({a: [3, 4], b: {c: 8}})); @@ -98,6 +105,7 @@ assert.commandWorked(coll.insert({a: [3, 5], b: {c: 7}})); result = coll.distinct("b.c", {a: 3}); assert.eq([1, 7, 8], result.sort()); explain = coll.explain("queryPlanner").distinct("b.c", {a: 3}); -assert(planHasStage(db, explain.queryPlanner.winningPlan, "PROJECTION_DEFAULT")); -assert(planHasStage(db, explain.queryPlanner.winningPlan, "DISTINCT_SCAN")); +winningPlan = getWinningPlan(explain.queryPlanner); +assert(planHasStage(db, winningPlan, "PROJECTION_DEFAULT")); +assert(planHasStage(db, winningPlan, "DISTINCT_SCAN")); }()); diff --git a/jstests/core/distinct_multikey_dotted_path.js b/jstests/core/distinct_multikey_dotted_path.js index 158c4d61f4b..c8530fe6799 100644 --- a/jstests/core/distinct_multikey_dotted_path.js +++ b/jstests/core/distinct_multikey_dotted_path.js @@ -51,7 +51,7 @@ function distinctResultsFromPipeline(pipeline) { assert.sameMembers([1, 2, 3, null], results); const expl = coll.explain().distinct("a.b.c"); - assert.eq(true, planHasStage(db, expl.queryPlanner.winningPlan, "DISTINCT_SCAN"), expl); + assert.eq(true, planHasStage(db, getWinningPlan(expl.queryPlanner), "DISTINCT_SCAN"), expl); // Do an equivalent query using $group. const pipeline = getAggPipelineForDistinct("a.b.c"); @@ -68,7 +68,7 @@ function distinctResultsFromPipeline(pipeline) { const expl = coll.explain().distinct("a.b.c", numPredicate); - assert.eq(true, planHasStage(db, expl.queryPlanner.winningPlan, "DISTINCT_SCAN"), expl); + assert.eq(true, planHasStage(db, getWinningPlan(expl.queryPlanner), "DISTINCT_SCAN"), expl); const pipeline = [{$match: numPredicate}].concat(getAggPipelineForDistinct("a.b.c")); const aggResults = distinctResultsFromPipeline(pipeline); @@ -92,7 +92,7 @@ assert.commandWorked(coll.insert({a: {b: {c: []}}})); assert.sameMembers([1, 2, 3, 4, 5, 6, null, undefined], multiKeyResults); const expl = coll.explain().distinct("a.b.c"); - assert.eq(true, planHasStage(db, expl.queryPlanner.winningPlan, "DISTINCT_SCAN")); + assert.eq(true, planHasStage(db, getWinningPlan(expl.queryPlanner), "DISTINCT_SCAN")); // Not running same query with $group now that the field is multikey. See comment above. })(); @@ -108,8 +108,9 @@ assert.commandWorked(coll.insert({a: {b: {c: []}}})); assert.sameMembers([4, 5, 6], results); const expl = coll.explain().distinct("a.b.c", pred); - assert.eq(false, planHasStage(db, expl.queryPlanner.winningPlan, "DISTINCT_SCAN"), expl); - assert.eq(true, planHasStage(db, expl.queryPlanner.winningPlan, "IXSCAN"), expl); + const winningPlan = getWinningPlan(expl.queryPlanner); + assert.eq(false, planHasStage(db, winningPlan, "DISTINCT_SCAN"), expl); + assert.eq(true, planHasStage(db, winningPlan, "IXSCAN"), expl); // Not running same query with $group now that the field is multikey. See comment above. })(); @@ -134,7 +135,7 @@ assert.commandWorked(coll.insert({a: {b: {c: []}}})); multiKeyResults); const expl = coll.explain().distinct("a.b"); - assert.eq(true, planHasStage(db, expl.queryPlanner.winningPlan, "DISTINCT_SCAN")); + assert.eq(true, planHasStage(db, getWinningPlan(expl.queryPlanner), "DISTINCT_SCAN")); // Not running same query with $group now that the field is multikey. See comment above. })(); @@ -152,8 +153,9 @@ assert.commandWorked(coll.insert({a: {b: {c: []}}})); multiKeyResults); const expl = coll.explain().distinct("a.b", pred); - assert.eq(false, planHasStage(db, expl.queryPlanner.winningPlan, "DISTINCT_SCAN")); - assert.eq(true, planHasStage(db, expl.queryPlanner.winningPlan, "IXSCAN")); + const winningPlan = getWinningPlan(expl.queryPlanner); + assert.eq(false, planHasStage(db, winningPlan, "DISTINCT_SCAN")); + assert.eq(true, planHasStage(db, winningPlan, "IXSCAN")); // Not running same query with $group now that the field is multikey. See comment above. })(); @@ -165,7 +167,7 @@ assert.commandWorked(coll.insert({a: {b: {c: []}}})); assert.eq(res, [{c: 4}]); const expl = coll.explain().distinct("a.b.0"); - assert.eq(true, planHasStage(db, expl.queryPlanner.winningPlan, "COLLSCAN"), expl); + assert.eq(true, planHasStage(db, getWinningPlan(expl.queryPlanner), "COLLSCAN"), expl); // Will not attempt the equivalent query with aggregation, since $group by "a.b.0" will // only treat '0' as a field name (not array index). @@ -179,7 +181,7 @@ assert.commandWorked(coll.insert({a: {b: {c: []}}})); assert.sameMembers(res, [{c: 4}, "hello world"]); const expl = coll.explain().distinct("a.b.0"); - assert.eq(true, planHasStage(db, expl.queryPlanner.winningPlan, "DISTINCT_SCAN"), expl); + assert.eq(true, planHasStage(db, getWinningPlan(expl.queryPlanner), "DISTINCT_SCAN"), expl); // Will not attempt the equivalent query with aggregation, since $group by "a.b.0" will // only treat '0' as a field name (not array index). @@ -197,8 +199,9 @@ assert.commandWorked(coll.insert({a: {b: {c: []}}})); assert.commandWorked(coll.insert({a: [1, 2, 3]})); const expl = coll.explain().distinct("a.b.0", pred); - assert.eq(false, planHasStage(db, expl.queryPlanner.winningPlan, "DISTINCT_SCAN"), expl); - assert.eq(true, planHasStage(db, expl.queryPlanner.winningPlan, "IXSCAN"), expl); + const winningPlan = getWinningPlan(expl.queryPlanner); + assert.eq(false, planHasStage(db, winningPlan, "DISTINCT_SCAN"), expl); + assert.eq(true, planHasStage(db, winningPlan, "IXSCAN"), expl); // Will not attempt the equivalent query with aggregation, since $group by "a.b.0" will // only treat '0' as a field name (not array index). diff --git a/jstests/core/distinct_with_hashed_index.js b/jstests/core/distinct_with_hashed_index.js index 4cfd6a39a22..8476c5c5f14 100644 --- a/jstests/core/distinct_with_hashed_index.js +++ b/jstests/core/distinct_with_hashed_index.js @@ -30,32 +30,34 @@ assert.commandWorked(coll.createIndex({a: -1, b: "hashed", c: 1})); // 'distinct' on non-hashed prefix fields can use DISTINCT_SCAN. assert.eq(100, coll.distinct("a").length); let plan = coll.explain("executionStats").distinct("a"); -assert(isIndexOnly(db, plan.queryPlanner.winningPlan), plan.queryPlanner.winningPlan); -assert(planHasStage(db, plan.queryPlanner.winningPlan, "DISTINCT_SCAN"), - plan.queryPlanner.winningPlan); +let winningPlan = getWinningPlan(plan.queryPlanner); +assert(isIndexOnly(db, winningPlan), winningPlan); +assert(planHasStage(db, winningPlan, "DISTINCT_SCAN"), winningPlan); // 'distinct' on non-prefix fields cannot use index. assert.eq(26, coll.distinct("b").length); plan = coll.explain("executionStats").distinct("b"); -assert(isCollscan(db, plan.queryPlanner.winningPlan), plan.queryPlanner.winningPlan); +winningPlan = getWinningPlan(plan.queryPlanner); +assert(isCollscan(db, winningPlan), winningPlan); assert.eq(10, coll.distinct("c").length); plan = coll.explain("executionStats").distinct("c"); -assert(isCollscan(db, plan.queryPlanner.winningPlan), plan.queryPlanner.winningPlan); +winningPlan = getWinningPlan(plan.queryPlanner); +assert(isCollscan(db, winningPlan), winningPlan); // A 'distinct' command that cannot use 'DISTINCT_SCAN', can use index scan for the query part. assert.eq([2], coll.distinct("c", {a: 12, b: {subObj: "str_12"}})); plan = coll.explain("executionStats").distinct("c", {a: 12, b: {subObj: "str_12"}}); -assert(isIxscan(db, plan.queryPlanner.winningPlan), plan.queryPlanner.winningPlan); -assert(!planHasStage(db, plan.queryPlanner.winningPlan, "DISTINCT_SCAN"), - plan.queryPlanner.winningPlan); -assert(planHasStage(db, plan.queryPlanner.winningPlan, "FETCH"), plan.queryPlanner.winningPlan); +winningPlan = getWinningPlan(plan.queryPlanner); +assert(isIxscan(db, winningPlan), winningPlan); +assert(!planHasStage(db, winningPlan, "DISTINCT_SCAN"), winningPlan); +assert(planHasStage(db, winningPlan, "FETCH"), winningPlan); // 'distinct' with query predicate on index field can get converted to DISTINCT_SCAN. assert.eq([2], coll.distinct("c", {a: 12})); plan = coll.explain("executionStats").distinct("c", {a: 12}); -assert(isIndexOnly(db, plan.queryPlanner.winningPlan), plan.queryPlanner.winningPlan); -assert(planHasStage(db, plan.queryPlanner.winningPlan, "DISTINCT_SCAN"), - plan.queryPlanner.winningPlan); +winningPlan = getWinningPlan(plan.queryPlanner); +assert(isIndexOnly(db, winningPlan), winningPlan); +assert(planHasStage(db, winningPlan, "DISTINCT_SCAN"), winningPlan); // Can use index scan to answer the query even when the key of distinct is not eligible from // DISTINCT_SCAN. Since the query has a point predicate on "b", we need a filter, since the bounds @@ -63,10 +65,10 @@ assert(planHasStage(db, plan.queryPlanner.winningPlan, "DISTINCT_SCAN"), // filter. assert.eq([], coll.distinct("c", {a: 12, b: 4})); plan = coll.explain("executionStats").distinct("c", {a: 12, b: 4}); -assert(isIxscan(db, plan.queryPlanner.winningPlan), plan.queryPlanner.winningPlan); -assert(planHasStage(db, plan.queryPlanner.winningPlan, "FETCH"), plan.queryPlanner.winningPlan); -assert(!planHasStage(db, plan.queryPlanner.winningPlan, "DISTINCT_SCAN"), - plan.queryPlanner.winningPlan); +winningPlan = getWinningPlan(plan.queryPlanner); +assert(isIxscan(db, winningPlan), winningPlan); +assert(planHasStage(db, winningPlan, "FETCH"), winningPlan); +assert(!planHasStage(db, winningPlan, "DISTINCT_SCAN"), winningPlan); // Verify that simple $group on non-hashed field can use DISTINCT_SCAN. let pipeline = [{$group: {_id: "$a"}}]; @@ -114,29 +116,34 @@ assert.commandWorked(coll.createIndex({b: "hashed", c: 1})); // different values as the same and return only the first one. assert.eq(26, coll.distinct("b").length); plan = coll.explain("executionStats").distinct("b"); -assert(isCollscan(db, plan.queryPlanner.winningPlan), plan.queryPlanner.winningPlan); +winningPlan = getWinningPlan(plan.queryPlanner); +assert(isCollscan(db, winningPlan), winningPlan); // 'distinct' with query predicate can use index for the query part. assert.eq([1], coll.distinct("b", {b: 1})); plan = coll.explain("executionStats").distinct("b", {b: 1}); -assert(isIxscan(db, plan.queryPlanner.winningPlan), plan.queryPlanner.winningPlan); -assert(planHasStage(db, plan.queryPlanner.winningPlan, "FETCH"), plan.queryPlanner.winningPlan); +winningPlan = getWinningPlan(plan.queryPlanner); +assert(isIxscan(db, winningPlan), winningPlan); +assert(planHasStage(db, winningPlan, "FETCH"), winningPlan); // 'distinct' with query predicate cannot use index when query cannot use index. assert.eq([5], coll.distinct("b", {b: {$lt: 6, $gt: 4}})); plan = coll.explain("executionStats").distinct("b", {b: {$lt: 6, $gt: 4}}); -assert(isCollscan(db, plan.queryPlanner.winningPlan), plan.queryPlanner.winningPlan); +winningPlan = getWinningPlan(plan.queryPlanner); +assert(isCollscan(db, winningPlan), winningPlan); // 'distinct' with query predicate can use index for the query part. assert.eq([2], coll.distinct("c", {a: 12, b: 12})); plan = coll.explain("executionStats").distinct("c", {a: 12, b: 12}); -assert(isIxscan(db, plan.queryPlanner.winningPlan), plan.queryPlanner.winningPlan); -assert(planHasStage(db, plan.queryPlanner.winningPlan, "FETCH"), plan.queryPlanner.winningPlan); +winningPlan = getWinningPlan(plan.queryPlanner); +assert(isIxscan(db, winningPlan), winningPlan); +assert(planHasStage(db, winningPlan, "FETCH"), winningPlan); // 'distinct' on non-prefix fields cannot use index. assert.sameMembers([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], coll.distinct("c")); plan = coll.explain("executionStats").distinct("c"); -assert(isCollscan(db, plan.queryPlanner.winningPlan), plan.queryPlanner.winningPlan); +winningPlan = getWinningPlan(plan.queryPlanner); +assert(isCollscan(db, winningPlan), winningPlan); // Verify that simple $group on hashed field cannot use DISTINCT_SCAN. pipeline = [{$group: {_id: "$b"}}]; diff --git a/jstests/core/explain_distinct.js b/jstests/core/explain_distinct.js index 48840c52171..1282397c4df 100644 --- a/jstests/core/explain_distinct.js +++ b/jstests/core/explain_distinct.js @@ -12,11 +12,11 @@ load("jstests/libs/analyze_plan.js"); -var collName = "jstests_explain_distinct"; -var coll = db[collName]; +const collName = "jstests_explain_distinct"; +const coll = db[collName]; function runDistinctExplain(collection, keyString, query) { - var distinctCmd = {distinct: collection.getName(), key: keyString}; + const distinctCmd = {distinct: collection.getName(), key: keyString}; if (typeof query !== 'undefined') { distinctCmd.query = query; @@ -28,12 +28,13 @@ function runDistinctExplain(collection, keyString, query) { coll.drop(); // Collection doesn't exist. -var explain = runDistinctExplain(coll, 'a', {}); +let explain = runDistinctExplain(coll, 'a', {}); assert.commandWorked(explain); -assert(planHasStage(db, explain.queryPlanner.winningPlan, "EOF")); +let winningPlan = getWinningPlan(explain.queryPlanner); +assert(planHasStage(db, winningPlan, "EOF")); // Insert the data to perform distinct() on. -for (var i = 0; i < 10; i++) { +for (let i = 0; i < 10; i++) { assert.commandWorked(coll.insert({a: 1, b: 1})); assert.commandWorked(coll.insert({a: 2, c: 1})); } @@ -49,22 +50,24 @@ assert.commandWorked(runDistinctExplain(coll, 'a', null)); assert.commandWorked(runDistinctExplain(coll, 'a')); assert.eq([1], coll.distinct('b')); -var explain = runDistinctExplain(coll, 'b', {}); +explain = runDistinctExplain(coll, 'b', {}); assert.commandWorked(explain); +winningPlan = getWinningPlan(explain.queryPlanner); assert.eq(20, explain.executionStats.nReturned); -assert(isCollscan(db, explain.queryPlanner.winningPlan)); +assert(isCollscan(db, winningPlan)); assert.commandWorked(coll.createIndex({a: 1})); assert.eq([1, 2], coll.distinct('a')); -var explain = runDistinctExplain(coll, 'a', {}); +explain = runDistinctExplain(coll, 'a', {}); assert.commandWorked(explain); +winningPlan = getWinningPlan(explain.queryPlanner); assert.eq(2, explain.executionStats.nReturned); -assert(planHasStage(db, explain.queryPlanner.winningPlan, "PROJECTION_COVERED")); -assert(planHasStage(db, explain.queryPlanner.winningPlan, "DISTINCT_SCAN")); +assert(planHasStage(db, winningPlan, "PROJECTION_COVERED")); +assert(planHasStage(db, winningPlan, "DISTINCT_SCAN")); // Check that the DISTINCT_SCAN stage has the correct stats. -var stage = getPlanStage(explain.queryPlanner.winningPlan, "DISTINCT_SCAN"); +let stage = getPlanStage(explain.queryPlanner.winningPlan, "DISTINCT_SCAN"); assert.eq({a: 1}, stage.keyPattern); assert.eq("a_1", stage.indexName); assert.eq(false, stage.isMultiKey); @@ -77,17 +80,19 @@ assert("indexBounds" in stage); assert.commandWorked(coll.createIndex({a: 1, b: 1})); assert.eq([1], coll.distinct('a', {a: 1})); -var explain = runDistinctExplain(coll, 'a', {a: 1}); +explain = runDistinctExplain(coll, 'a', {a: 1}); assert.commandWorked(explain); +winningPlan = getWinningPlan(explain.queryPlanner); assert.eq(1, explain.executionStats.nReturned); -assert(planHasStage(db, explain.queryPlanner.winningPlan, "PROJECTION_COVERED")); -assert(planHasStage(db, explain.queryPlanner.winningPlan, "DISTINCT_SCAN")); +assert(planHasStage(db, winningPlan, "PROJECTION_COVERED")); +assert(planHasStage(db, winningPlan, "DISTINCT_SCAN")); assert.eq([1], coll.distinct('b', {a: 1})); -var explain = runDistinctExplain(coll, 'b', {a: 1}); +explain = runDistinctExplain(coll, 'b', {a: 1}); assert.commandWorked(explain); +winningPlan = getWinningPlan(explain.queryPlanner); assert.eq(1, explain.executionStats.nReturned); -assert(!planHasStage(db, explain.queryPlanner.winningPlan, "FETCH")); -assert(planHasStage(db, explain.queryPlanner.winningPlan, "PROJECTION_COVERED")); -assert(planHasStage(db, explain.queryPlanner.winningPlan, "DISTINCT_SCAN")); +assert(!planHasStage(db, winningPlan, "FETCH")); +assert(planHasStage(db, winningPlan, "PROJECTION_COVERED")); +assert(planHasStage(db, winningPlan, "DISTINCT_SCAN")); })(); diff --git a/jstests/core/explain_multikey.js b/jstests/core/explain_multikey.js index b0d42db9d2d..9bea359edb4 100644 --- a/jstests/core/explain_multikey.js +++ b/jstests/core/explain_multikey.js @@ -10,8 +10,8 @@ load("jstests/libs/analyze_plan.js"); -var coll = db.explain_multikey; -var keyPattern = { +const coll = db.explain_multikey; +const keyPattern = { a: 1, "b.c": 1, "b.d": 1, @@ -35,12 +35,13 @@ function createIndexAndRunExplain(testOptions) { assert.commandWorked(coll.createIndex(keyPattern)); assert.commandWorked(coll.insert(testOptions.docToInsert)); - var explain = db.runCommand({explain: testOptions.commandObj}); + const explain = db.runCommand({explain: testOptions.commandObj}); assert.commandWorked(explain); + const winningPlan = getWinningPlan(explain.queryPlanner); - assert(planHasStage(db, explain.queryPlanner.winningPlan, testOptions.stage), + assert(planHasStage(db, winningPlan, testOptions.stage), "expected stage to be present: " + tojson(explain)); - return getPlanStage(explain.queryPlanner.winningPlan, testOptions.stage); + return getPlanStage(winningPlan, testOptions.stage); } // Calls createIndexAndRunExplain() twice: once with a document that causes the created index to @@ -51,7 +52,7 @@ function verifyMultikeyInfoInExplainOutput(testOptions) { a: 1, b: [{c: ["w", "x"], d: 3}, {c: ["y", "z"], d: 4}], }; - var stage = createIndexAndRunExplain(testOptions); + let stage = createIndexAndRunExplain(testOptions); assert.eq(true, stage.isMultiKey, "expected index to be multikey: " + tojson(stage)); assert.eq({a: [], "b.c": ["b", "b.c"], "b.d": ["b"]}, stage.multiKeyPaths, tojson(stage)); diff --git a/jstests/core/index_filter_catalog_independent.js b/jstests/core/index_filter_catalog_independent.js index 71adead3863..35b2bf4134a 100644 --- a/jstests/core/index_filter_catalog_independent.js +++ b/jstests/core/index_filter_catalog_independent.js @@ -54,7 +54,7 @@ assertOneIndexFilter({x: 3}, [{x: 1, y: 1}]); let explain = assert.commandWorked(coll.find({x: 3}).explain()); checkIndexFilterSet(explain, true); -assertIsIxScanOnIndex(explain.queryPlanner.winningPlan, {x: 1, y: 1}); +assertIsIxScanOnIndex(getWinningPlan(explain.queryPlanner), {x: 1, y: 1}); // Drop an index. The filter should not change. assert.commandWorked(coll.dropIndex({x: 1, y: 1})); @@ -64,13 +64,13 @@ assertOneIndexFilter({x: 3}, [{x: 1, y: 1}]); // Since we dropped the {x: 1, y: 1} index, a COLLSCAN must be used. explain = coll.find({x: 3}).explain(); checkIndexFilterSet(explain, true); -assert(isCollscan(db, explain.queryPlanner.winningPlan)); +assert(isCollscan(db, getWinningPlan(explain.queryPlanner))); // Create another index. This should not change whether the index filter is applied. assert.commandWorked(coll.createIndex({x: 1, z: 1})); explain = assert.commandWorked(coll.find({x: 3}).explain()); checkIndexFilterSet(explain, true); -assert(isCollscan(db, explain.queryPlanner.winningPlan)); +assert(isCollscan(db, getWinningPlan(explain.queryPlanner))); // Changing the catalog and then setting an index filter should not result in duplicate entries. assert.commandWorked(coll.createIndex({x: 1, a: 1})); @@ -84,5 +84,5 @@ assertOneIndexFilter({x: 3}, [{x: 1, y: 1}]); explain = assert.commandWorked(coll.find({x: 3}).explain()); checkIndexFilterSet(explain, true); -assertIsIxScanOnIndex(explain.queryPlanner.winningPlan, {x: 1, y: 1}); +assertIsIxScanOnIndex(getWinningPlan(explain.queryPlanner), {x: 1, y: 1}); })(); diff --git a/jstests/core/index_filter_collation.js b/jstests/core/index_filter_collation.js index 7734acdfa38..74ec64f3b3c 100644 --- a/jstests/core/index_filter_collation.js +++ b/jstests/core/index_filter_collation.js @@ -77,10 +77,10 @@ function assertIsIxScanOnIndex(winningPlan, keyPattern) { // Run the queries and be sure the correct indexes are used. let explain = coll.find({x: 3}).explain(); checkIndexFilterSet(explain, true); -assertIsIxScanOnIndex(explain.queryPlanner.winningPlan, {x: 1, y: 1}); +assertIsIxScanOnIndex(getWinningPlan(explain.queryPlanner), {x: 1, y: 1}); // Run the queries and be sure the correct indexes are used. explain = coll.find({x: 3}).collation(caseInsensitive).explain(); checkIndexFilterSet(explain, true); -assertIsIxScanOnIndex(explain.queryPlanner.winningPlan, {x: 1}); +assertIsIxScanOnIndex(getWinningPlan(explain.queryPlanner), {x: 1}); })(); diff --git a/jstests/core/index_filter_on_hidden_index.js b/jstests/core/index_filter_on_hidden_index.js index 0315bd7fad7..3fe468a39a3 100644 --- a/jstests/core/index_filter_on_hidden_index.js +++ b/jstests/core/index_filter_on_hidden_index.js @@ -56,7 +56,7 @@ function validateIxscanOrCollscanUsed(queryShape, idxName) { if (idxName) { // Expect the given index was used. - const ixScanStage = getPlanStages(explain.queryPlanner.winningPlan, "IXSCAN")[0]; + const ixScanStage = getPlanStages(getWinningPlan(explain.queryPlanner), "IXSCAN")[0]; assert(ixScanStage, `Index '${idxName}' was not used.`); assert.eq(ixScanStage.indexName, idxName, `Index '${idxName}' was not used.`); } else { diff --git a/jstests/core/index_multikey.js b/jstests/core/index_multikey.js index 32ff3bd58c4..58c80bea66d 100644 --- a/jstests/core/index_multikey.js +++ b/jstests/core/index_multikey.js @@ -14,7 +14,7 @@ coll.drop(); function getIndexScanExplainOutput() { const explain = coll.find().hint({a: 1, b: 1}).explain(); assert.commandWorked(explain); - return getPlanStage(explain.queryPlanner.winningPlan, "IXSCAN"); + return getPlanStage(getWinningPlan(explain.queryPlanner), "IXSCAN"); } assert.commandWorked(coll.createIndex({a: 1, b: 1})); diff --git a/jstests/core/index_partial_read_ops.js b/jstests/core/index_partial_read_ops.js index 6e13b5e3ba4..c13e8832340 100644 --- a/jstests/core/index_partial_read_ops.js +++ b/jstests/core/index_partial_read_ops.js @@ -13,8 +13,8 @@ load("jstests/libs/analyze_plan.js"); (function() { "use strict"; -var explain; -var coll = db.index_partial_read_ops; +let explain; +const coll = db.index_partial_read_ops; coll.drop(); assert.commandWorked(coll.createIndex({x: 1}, {partialFilterExpression: {a: {$lte: 1.5}}})); @@ -28,21 +28,21 @@ assert.commandWorked(coll.insert({x: 6, a: 1})); // In index. // find() operations that should use index. explain = coll.explain('executionStats').find({x: 6, a: 1}).finish(); assert.eq(1, explain.executionStats.nReturned); -assert(isIxscan(db, explain.queryPlanner.winningPlan)); +assert(isIxscan(db, getWinningPlan(explain.queryPlanner))); explain = coll.explain('executionStats').find({x: {$gt: 1}, a: 1}).finish(); assert.eq(1, explain.executionStats.nReturned); -assert(isIxscan(db, explain.queryPlanner.winningPlan)); +assert(isIxscan(db, getWinningPlan(explain.queryPlanner))); explain = coll.explain('executionStats').find({x: 6, a: {$lte: 1}}).finish(); assert.eq(1, explain.executionStats.nReturned); -assert(isIxscan(db, explain.queryPlanner.winningPlan)); +assert(isIxscan(db, getWinningPlan(explain.queryPlanner))); // find() operations that should not use index. explain = coll.explain('executionStats').find({x: 6, a: {$lt: 1.6}}).finish(); assert.eq(1, explain.executionStats.nReturned); -assert(isCollscan(db, explain.queryPlanner.winningPlan)); +assert(isCollscan(db, getWinningPlan(explain.queryPlanner))); explain = coll.explain('executionStats').find({x: 6}).finish(); assert.eq(1, explain.executionStats.nReturned); -assert(isCollscan(db, explain.queryPlanner.winningPlan)); +assert(isCollscan(db, getWinningPlan(explain.queryPlanner))); // // Verify basic functionality with the count command. @@ -50,11 +50,11 @@ assert(isCollscan(db, explain.queryPlanner.winningPlan)); // Count operation that should use index. explain = coll.explain('executionStats').count({x: {$gt: 1}, a: 1}); -assert(isIxscan(db, explain.queryPlanner.winningPlan)); +assert(isIxscan(db, getWinningPlan(explain.queryPlanner))); // Count operation that should not use index. explain = coll.explain('executionStats').count({x: {$gt: 1}, a: 2}); -assert(isCollscan(db, explain.queryPlanner.winningPlan)); +assert(isCollscan(db, getWinningPlan(explain.queryPlanner))); // // Verify basic functionality with the aggregate command. @@ -62,11 +62,11 @@ assert(isCollscan(db, explain.queryPlanner.winningPlan)); // Aggregate operation that should use index. explain = coll.aggregate([{$match: {x: {$gt: 1}, a: 1}}], {explain: true}); -assert(isIxscan(db, explain.queryPlanner.winningPlan)); +assert(isIxscan(db, getWinningPlan(explain.queryPlanner))); // Aggregate operation that should not use index. explain = coll.aggregate([{$match: {x: {$gt: 1}, a: 2}}], {explain: true}); -assert(isCollscan(db, explain.queryPlanner.winningPlan)); +assert(isCollscan(db, getWinningPlan(explain.queryPlanner))); // // Verify basic functionality with the findAndModify command. @@ -76,13 +76,13 @@ assert(isCollscan(db, explain.queryPlanner.winningPlan)); explain = coll.explain('executionStats') .findAndModify({query: {x: {$gt: 1}, a: 1}, update: {$inc: {x: 1}}}); assert.eq(1, explain.executionStats.nReturned); -assert(isIxscan(db, explain.queryPlanner.winningPlan)); +assert(isIxscan(db, getWinningPlan(explain.queryPlanner))); // findAndModify operation that should not use index. explain = coll.explain('executionStats') .findAndModify({query: {x: {$gt: 1}, a: 2}, update: {$inc: {x: 1}}}); assert.eq(1, explain.executionStats.nReturned); -assert(isCollscan(db, explain.queryPlanner.winningPlan)); +assert(isCollscan(db, getWinningPlan(explain.queryPlanner))); // // Verify functionality with multiple overlapping partial indexes on the same key pattern. diff --git a/jstests/core/or2.js b/jstests/core/or2.js index 5ce72cf2cd7..be5653ca6a1 100644 --- a/jstests/core/or2.js +++ b/jstests/core/or2.js @@ -1,37 +1,40 @@ -t = db.jstests_or2; -t.drop(); +(function() { +"use strict"; // Include helpers for analyzing explain output. load("jstests/libs/analyze_plan.js"); -checkArrs = function(a, b) { +const t = db.jstests_or2; +t.drop(); + +function checkArrs(a, b) { assert.eq(a.length, b.length); - aStr = []; - bStr = []; + const aStr = []; + const bStr = []; a.forEach(function(x) { aStr.push(tojson(x)); }); b.forEach(function(x) { bStr.push(tojson(x)); }); - for (i = 0; i < aStr.length; ++i) { + for (let i = 0; i < aStr.length; ++i) { assert.neq(-1, bStr.indexOf(aStr[i])); } -}; +} -doTest = function(index) { +function doTest(index) { if (index == null) { index = true; } - t.save({_id: 0, x: 0, a: 1}); - t.save({_id: 1, x: 0, a: 2}); - t.save({_id: 2, x: 0, b: 1}); - t.save({_id: 3, x: 0, b: 2}); - t.save({_id: 4, x: 1, a: 1, b: 1}); - t.save({_id: 5, x: 1, a: 1, b: 2}); - t.save({_id: 6, x: 1, a: 2, b: 1}); - t.save({_id: 7, x: 1, a: 2, b: 2}); + assert.commandWorked(t.insert({_id: 0, x: 0, a: 1})); + assert.commandWorked(t.insert({_id: 1, x: 0, a: 2})); + assert.commandWorked(t.insert({_id: 2, x: 0, b: 1})); + assert.commandWorked(t.insert({_id: 3, x: 0, b: 2})); + assert.commandWorked(t.insert({_id: 4, x: 1, a: 1, b: 1})); + assert.commandWorked(t.insert({_id: 5, x: 1, a: 1, b: 2})); + assert.commandWorked(t.insert({_id: 6, x: 1, a: 2, b: 1})); + assert.commandWorked(t.insert({_id: 7, x: 1, a: 2, b: 2})); assert.throws(function() { t.find({x: 0, $or: "a"}).toArray(); @@ -43,43 +46,37 @@ doTest = function(index) { t.find({x: 0, $or: ["a"]}).toArray(); }); - a1 = t.find({x: 0, $or: [{a: 1}]}).toArray(); + const a1 = t.find({x: 0, $or: [{a: 1}]}).toArray(); checkArrs([{_id: 0, x: 0, a: 1}], a1); if (index) { - var explain = t.find({x: 0, $or: [{a: 1}]}).explain(); - assert(isIxscan(db, explain.queryPlanner.winningPlan)); + const explain = t.find({x: 0, $or: [{a: 1}]}).explain(); + assert(isIxscan(db, getWinningPlan(explain.queryPlanner))); } - a1b2 = t.find({x: 1, $or: [{a: 1}, {b: 2}]}).toArray(); + const a1b2 = t.find({x: 1, $or: [{a: 1}, {b: 2}]}).toArray(); checkArrs([{_id: 4, x: 1, a: 1, b: 1}, {_id: 5, x: 1, a: 1, b: 2}, {_id: 7, x: 1, a: 2, b: 2}], a1b2); if (index) { - var explain = t.find({x: 0, $or: [{a: 1}]}).explain(); - assert(isIxscan(db, explain.queryPlanner.winningPlan)); + const explain = t.find({x: 0, $or: [{a: 1}]}).explain(); + assert(isIxscan(db, getWinningPlan(explain.queryPlanner))); } - - /* - t.drop(); - obj = {_id:0,x:10,a:[1,2,3]}; - t.save( obj ); - t.update( {x:10,$or:[ {a:2} ]}, {$set:{'a.$':100}} ); - assert.eq( obj, t.findOne() ); // no change - */ -}; +} doTest(false); -t.createIndex({x: 1}); +assert(t.drop()); +assert.commandWorked(t.createIndex({x: 1})); doTest(); -t.drop(); -t.createIndex({x: 1, a: 1}); +assert(t.drop()); +assert.commandWorked(t.createIndex({x: 1, a: 1})); doTest(); -t.drop(); -t.createIndex({x: 1, b: 1}); +assert(t.drop()); +assert.commandWorked(t.createIndex({x: 1, b: 1})); doTest(); -t.drop(); -t.createIndex({x: 1, a: 1, b: 1}); +assert(t.drop()); +assert.commandWorked(t.createIndex({x: 1, a: 1, b: 1})); doTest(); +})(); diff --git a/jstests/core/or3.js b/jstests/core/or3.js index cb980b9ef12..ed7ab8ecd64 100644 --- a/jstests/core/or3.js +++ b/jstests/core/or3.js @@ -1,37 +1,40 @@ -t = db.jstests_or3; -t.drop(); +(function() { +"use strict"; // Include helpers for analyzing explain output. load("jstests/libs/analyze_plan.js"); -checkArrs = function(a, b) { +const t = db.jstests_or3; +t.drop(); + +function checkArrs(a, b) { assert.eq(a.length, b.length); - aStr = []; - bStr = []; + const aStr = []; + const bStr = []; a.forEach(function(x) { aStr.push(tojson(x)); }); b.forEach(function(x) { bStr.push(tojson(x)); }); - for (i = 0; i < aStr.length; ++i) { + for (let i = 0; i < aStr.length; ++i) { assert.neq(-1, bStr.indexOf(aStr[i])); } -}; +} -doTest = function(index) { +function doTest(index) { if (index == null) { index = true; } - t.save({_id: 0, x: 0, a: 1}); - t.save({_id: 1, x: 0, a: 2}); - t.save({_id: 2, x: 0, b: 1}); - t.save({_id: 3, x: 0, b: 2}); - t.save({_id: 4, x: 1, a: 1, b: 1}); - t.save({_id: 5, x: 1, a: 1, b: 2}); - t.save({_id: 6, x: 1, a: 2, b: 1}); - t.save({_id: 7, x: 1, a: 2, b: 2}); + assert.commandWorked(t.insert({_id: 0, x: 0, a: 1})); + assert.commandWorked(t.insert({_id: 1, x: 0, a: 2})); + assert.commandWorked(t.insert({_id: 2, x: 0, b: 1})); + assert.commandWorked(t.insert({_id: 3, x: 0, b: 2})); + assert.commandWorked(t.insert({_id: 4, x: 1, a: 1, b: 1})); + assert.commandWorked(t.insert({_id: 5, x: 1, a: 1, b: 2})); + assert.commandWorked(t.insert({_id: 6, x: 1, a: 2, b: 1})); + assert.commandWorked(t.insert({_id: 7, x: 1, a: 2, b: 2})); assert.throws(function() { t.find({x: 0, $nor: "a"}).toArray(); @@ -43,34 +46,36 @@ doTest = function(index) { t.find({x: 0, $nor: ["a"]}).toArray(); }); - an1 = t.find({$nor: [{a: 1}]}).toArray(); + const an1 = t.find({$nor: [{a: 1}]}).toArray(); checkArrs(t.find({a: {$ne: 1}}).toArray(), an1); - an1bn2 = t.find({x: 1, $nor: [{a: 1}, {b: 2}]}).toArray(); + const an1bn2 = t.find({x: 1, $nor: [{a: 1}, {b: 2}]}).toArray(); checkArrs([{_id: 6, x: 1, a: 2, b: 1}], an1bn2); checkArrs(t.find({x: 1, a: {$ne: 1}, b: {$ne: 2}}).toArray(), an1bn2); if (index) { - var explain = t.find({x: 1, $nor: [{a: 1}, {b: 2}]}).explain(); - assert(isIxscan(db, explain.queryPlanner.winningPlan)); + const explain = t.find({x: 1, $nor: [{a: 1}, {b: 2}]}).explain(); + assert(isIxscan(db, getWinningPlan(explain.queryPlanner))); } - an1b2 = t.find({$nor: [{a: 1}], $or: [{b: 2}]}).toArray(); + const an1b2 = t.find({$nor: [{a: 1}], $or: [{b: 2}]}).toArray(); checkArrs(t.find({a: {$ne: 1}, b: 2}).toArray(), an1b2); -}; +} doTest(false); -t.createIndex({x: 1}); +assert(t.drop()); +assert.commandWorked(t.createIndex({x: 1})); doTest(); -t.drop(); -t.createIndex({x: 1, a: 1}); +assert(t.drop()); +assert.commandWorked(t.createIndex({x: 1, a: 1})); doTest(); -t.drop(); -t.createIndex({x: 1, b: 1}); +assert(t.drop()); +assert.commandWorked(t.createIndex({x: 1, b: 1})); doTest(); -t.drop(); -t.createIndex({x: 1, a: 1, b: 1}); +assert(t.drop()); +assert.commandWorked(t.createIndex({x: 1, a: 1, b: 1})); doTest(); +})(); diff --git a/jstests/core/plan_cache_sbe.js b/jstests/core/plan_cache_sbe.js new file mode 100644 index 00000000000..89cc680a64f --- /dev/null +++ b/jstests/core/plan_cache_sbe.js @@ -0,0 +1,46 @@ +/** + * Test that for SBE plans a plan cache entry includes a serialized SBE plan tree, and does not for + * classic plans. + * + * @tags: [ + * # If all chunks are moved off of a shard, it can cause the plan cache to miss commands. + * assumes_balancer_off, + * does_not_support_stepdowns, + * # This test attempts to perform queries with plan cache filters set up. The former operation + * # may be routed to a secondary in the replica set, whereas the latter must be routed to the + * # primary. + * assumes_read_concern_unchanged, + * assumes_read_preference_unchanged, + * assumes_unsharded_collection,] + */ +(function() { +"use strict"; + +const coll = db.plan_cache_sbe; +coll.drop(); + +// Note that the "getParameter" command is expected to fail in versions of mongod that do not yet +// include the slot-based execution engine. When that happens, however, 'isSBEEnabled' still +// correctly evaluates to false. +const isSBEEnabled = (() => { + const getParam = db.adminCommand({getParameter: 1, featureFlagSBE: 1}); + return getParam.hasOwnProperty("featureFlagSBE") && getParam.featureFlagSBE.value; +})(); + +assert.commandWorked(coll.insert({a: 1, b: 1})); + +// We need two indexes so that the multi-planner is executed. +assert.commandWorked(coll.createIndex({a: 1})); +assert.commandWorked(coll.createIndex({a: 1, b: 1})); + +// Run query. +assert.eq(1, coll.find({a: 1}).itcount()); + +// Validate plan cache stats entry. +const allStats = coll.aggregate([{$planCacheStats: {}}]).toArray(); +assert.eq(allStats.length, 1, allStats); +const stats = allStats[0]; +assert(stats.hasOwnProperty("cachedPlan"), stats); +assert.eq(stats.cachedPlan.hasOwnProperty("queryPlan"), isSBEEnabled, stats); +assert.eq(stats.cachedPlan.hasOwnProperty("slotBasedPlan"), isSBEEnabled, stats); +})(); diff --git a/jstests/core/wildcard_index_hint.js b/jstests/core/wildcard_index_hint.js index ff481a62712..a5f0f9a4c92 100644 --- a/jstests/core/wildcard_index_hint.js +++ b/jstests/core/wildcard_index_hint.js @@ -14,7 +14,7 @@ const assertArrayEq = (l, r) => assert(arrayEq(l, r), tojson(l) + " != " + tojso // Extracts the winning plan for the given query and hint from the explain output. const winningPlan = (query, hint) => - assert.commandWorked(coll.find(query).hint(hint).explain()).queryPlanner.winningPlan; + getWinningPlan(assert.commandWorked(coll.find(query).hint(hint).explain()).queryPlanner); // Runs the given query and confirms that: // (1) the expected index was used to answer the query, and diff --git a/jstests/libs/analyze_plan.js b/jstests/libs/analyze_plan.js index 21f54ff1e21..0fff85a7f80 100644 --- a/jstests/libs/analyze_plan.js +++ b/jstests/libs/analyze_plan.js @@ -5,6 +5,17 @@ load("jstests/libs/fixture_helpers.js"); // For FixtureHelpers. /** + * Returns a sub-element of the 'queryPlanner' explain output which represents a winning plan. + */ +function getWinningPlan(queryPlanner) { + // The 'queryPlan' format is used when the SBE engine is turned on. If this field is present, + // it will hold a serialized winning plan, otherwise it will be stored in the 'winningPlan' + // field itself. + return queryPlanner.winningPlan.hasOwnProperty("queryPlan") ? queryPlanner.winningPlan.queryPlan + : queryPlanner.winningPlan; +} + +/** * Given the root stage of explain's JSON representation of a query plan ('root'), returns all * subdocuments whose stage is 'stage'. Returns an empty array if the plan does not have the * requested stage. @@ -27,7 +38,7 @@ function getPlanStages(root, stage) { } if ("queryPlanner" in root) { - results = results.concat(getPlanStages(root.queryPlanner.winningPlan, stage)); + results = results.concat(getPlanStages(getWinningPlan(root.queryPlanner), stage)); } if ("thenStage" in root) { @@ -193,7 +204,7 @@ function getAggPlanStages(root, stage) { getPlanStages(queryLayerOutput.executionStats.executionStages, stage)); } else { results = - results.concat(getPlanStages(queryLayerOutput.queryPlanner.winningPlan, stage)); + results.concat(getPlanStages(getWinningPlan(queryLayerOutput.queryPlanner), stage)); } return results; @@ -407,12 +418,12 @@ function assertExplainCount({explainResults, expectedCount}) { */ function assertCoveredQueryAndCount({collection, query, project, count}) { let explain = collection.find(query, project).explain(); - assert(isIndexOnly(db, explain.queryPlanner.winningPlan), + assert(isIndexOnly(db, getWinningPlan(explain.queryPlanner)), "Winning plan was not covered: " + tojson(explain.queryPlanner.winningPlan)); // Same query as a count command should also be covered. explain = collection.explain("executionStats").find(query).count(); - assert(isIndexOnly(db, explain.queryPlanner.winningPlan), + assert(isIndexOnly(db, getWinningPlan(explain.queryPlanner)), "Winning plan for count was not covered: " + tojson(explain.queryPlanner.winningPlan)); assertExplainCount({explainResults: explain, expectedCount: count}); } @@ -424,7 +435,7 @@ function assertCoveredQueryAndCount({collection, query, project, count}) { */ function assertStagesForExplainOfCommand({coll, cmdObj, expectedStages, stagesNotExpected}) { const plan = assert.commandWorked(coll.runCommand({explain: cmdObj})); - const winningPlan = plan.queryPlanner.winningPlan; + const winningPlan = getWinningPlan(plan.queryPlanner); for (let expectedStage of expectedStages) { assert(planHasStage(coll.getDB(), winningPlan, expectedStage), "Could not find stage " + expectedStage + ". Plan: " + tojson(plan)); diff --git a/jstests/noPassthrough/and_hash.js b/jstests/noPassthrough/and_hash.js index f3e7dd1949e..ba96d11ff16 100644 --- a/jstests/noPassthrough/and_hash.js +++ b/jstests/noPassthrough/and_hash.js @@ -49,7 +49,7 @@ function assertAndHashUsed({query, expectedResult, shouldUseAndHash} = {}) { const expl = queryResult.explain(); assertArrayEq({actual: queryResult.toArray(), expected: expectedResult}); - assert.eq(shouldUseAndHash, planHasStage(db, expl.queryPlanner.winningPlan, "AND_HASH")); + assert.eq(shouldUseAndHash, planHasStage(db, getWinningPlan(expl.queryPlanner), "AND_HASH")); } // Test basic index intersection where we expect AND_HASH to be used. diff --git a/jstests/noPassthrough/hybrid_multikey.js b/jstests/noPassthrough/hybrid_multikey.js index 6fa1845834d..ba0154708f4 100644 --- a/jstests/noPassthrough/hybrid_multikey.js +++ b/jstests/noPassthrough/hybrid_multikey.js @@ -3,6 +3,7 @@ * various index types. */ load("jstests/noPassthrough/libs/index_build.js"); +load("jstests/libs/analyze_plan.js"); // For getWinningPlan to analyze explain() output. (function() { "use strict"; @@ -50,7 +51,7 @@ const runTest = (config) => { // Ensure the index is usable and has the expected multikey state. let explain = testColl.find({x: 1}).hint(indexName).explain(); - let plan = explain.queryPlanner.winningPlan; + let plan = getWinningPlan(explain.queryPlanner); assert.eq("FETCH", plan.stage, explain); assert.eq("IXSCAN", plan.inputStage.stage, explain); assert.eq( diff --git a/jstests/noPassthrough/validate_adjust_multikey.js b/jstests/noPassthrough/validate_adjust_multikey.js index 5b5ec49267e..ad36ac48f35 100644 --- a/jstests/noPassthrough/validate_adjust_multikey.js +++ b/jstests/noPassthrough/validate_adjust_multikey.js @@ -2,6 +2,7 @@ * Tests foreground validation's ability to fix up allowable multikey metadata problems. */ (function() { +load("jstests/libs/analyze_plan.js"); // For getWinningPlan to analyze explain() output. const conn = MongoRunner.runMongod(); const dbName = jsTestName(); @@ -15,7 +16,7 @@ const assertValidate = (coll, assertFn) => { const assertIndexMultikey = (coll, hint, expectMultikey) => { const explain = coll.find().hint(hint).explain(); - const plan = explain.queryPlanner.winningPlan; + const plan = getWinningPlan(explain.queryPlanner); assert.eq("FETCH", plan.stage, explain); assert.eq("IXSCAN", plan.inputStage.stage, explain); assert.eq(expectMultikey, @@ -109,4 +110,4 @@ runTest((coll) => { }); MongoRunner.stopMongod(conn); -})();
\ No newline at end of file +})(); diff --git a/src/mongo/db/exec/plan_cache_util.h b/src/mongo/db/exec/plan_cache_util.h index b2a18634016..717f3c879ef 100644 --- a/src/mongo/db/exec/plan_cache_util.h +++ b/src/mongo/db/exec/plan_cache_util.h @@ -114,8 +114,10 @@ void updatePlanCache( if constexpr (std::is_same_v<PlanStageType, std::unique_ptr<sbe::PlanStage>>) { return std::make_pair( plan_explainer_factory::make(candidates[winnerIdx].root.get(), + &candidates[winnerIdx].data, candidates[winnerIdx].solution.get()), plan_explainer_factory::make(candidates[runnerUpIdx].root.get(), + &candidates[runnerUpIdx].data, candidates[runnerUpIdx].solution.get())); } else { static_assert(std::is_same_v<PlanStageType, PlanStage*>); @@ -139,6 +141,7 @@ void updatePlanCache( auto winnerExplainer = [&]() { if constexpr (std::is_same_v<PlanStageType, std::unique_ptr<sbe::PlanStage>>) { return plan_explainer_factory::make(candidates[winnerIdx].root.get(), + &candidates[winnerIdx].data, candidates[winnerIdx].solution.get()); } else { static_assert(std::is_same_v<PlanStageType, PlanStage*>); diff --git a/src/mongo/db/exec/sbe/parser/sbe_parser_test.cpp b/src/mongo/db/exec/sbe/parser/sbe_parser_test.cpp index 914352adce0..8fcf32cb165 100644 --- a/src/mongo/db/exec/sbe/parser/sbe_parser_test.cpp +++ b/src/mongo/db/exec/sbe/parser/sbe_parser_test.cpp @@ -231,9 +231,9 @@ TEST_F(SBEParserTest, TestIdenticalDebugOutputAfterParse) { sbe::Parser parser; for (const auto& stage : stages) { - const auto stageText = printer.print(stage.get()); + const auto stageText = printer.print(*stage); const auto parsedStage = parser.parse(nullptr, "testDb", stageText); - const auto stageTextAfterParse = printer.print(parsedStage.get()); + const auto stageTextAfterParse = printer.print(*parsedStage); ASSERT_EQ(stageText, stageTextAfterParse); } } @@ -243,7 +243,7 @@ TEST_F(SBEParserTest, TestPlanNodeIdIsParsed) { sbe::Parser parser; for (const auto& stage : stages) { - const auto stageText = printer.print(stage.get()); + const auto stageText = printer.print(*stage); const auto parsedStage = parser.parse(nullptr, "testDb", stageText); ASSERT_EQ(parsedStage->getCommonStats()->nodeId, planNodeId); } diff --git a/src/mongo/db/exec/sbe/stages/hash_agg.cpp b/src/mongo/db/exec/sbe/stages/hash_agg.cpp index 18e7ee6559a..4c4a5b4f132 100644 --- a/src/mongo/db/exec/sbe/stages/hash_agg.cpp +++ b/src/mongo/db/exec/sbe/stages/hash_agg.cpp @@ -197,16 +197,16 @@ std::vector<DebugPrinter::Block> HashAggStage::debugPrint() const { ret.emplace_back(DebugPrinter::Block("[`")); bool first = true; - for (auto& p : _aggs) { + value::orderedSlotMapTraverse(_aggs, [&](auto slot, auto&& expr) { if (!first) { ret.emplace_back(DebugPrinter::Block("`,")); } - DebugPrinter::addIdentifier(ret, p.first); + DebugPrinter::addIdentifier(ret, slot); ret.emplace_back("="); - DebugPrinter::addBlocks(ret, p.second->debugPrint()); + DebugPrinter::addBlocks(ret, expr->debugPrint()); first = false; - } + }); ret.emplace_back("`]"); DebugPrinter::addNewLine(ret); diff --git a/src/mongo/db/exec/sbe/stages/project.cpp b/src/mongo/db/exec/sbe/stages/project.cpp index a5ab48b1369..10cf54e2b2e 100644 --- a/src/mongo/db/exec/sbe/stages/project.cpp +++ b/src/mongo/db/exec/sbe/stages/project.cpp @@ -100,9 +100,9 @@ std::unique_ptr<PlanStageStats> ProjectStage::getStats(bool includeDebugInfo) co if (includeDebugInfo) { DebugPrinter printer; BSONObjBuilder bob; - for (auto&& [slot, expr] : _projects) { + value::orderedSlotMapTraverse(_projects, [&](auto slot, auto&& expr) { bob.append(str::stream() << slot, printer.print(expr->debugPrint())); - } + }); ret->debugInfo = BSON("projections" << bob.obj()); } @@ -116,19 +116,21 @@ const SpecificStats* ProjectStage::getSpecificStats() const { std::vector<DebugPrinter::Block> ProjectStage::debugPrint() const { auto ret = PlanStage::debugPrint(); + ret.emplace_back("[`"); bool first = true; - for (auto& p : _projects) { + value::orderedSlotMapTraverse(_projects, [&](auto slot, auto&& expr) { if (!first) { ret.emplace_back(DebugPrinter::Block("`,")); } - DebugPrinter::addIdentifier(ret, p.first); + DebugPrinter::addIdentifier(ret, slot); ret.emplace_back("="); - DebugPrinter::addBlocks(ret, p.second->debugPrint()); + DebugPrinter::addBlocks(ret, expr->debugPrint()); first = false; - } + }); ret.emplace_back("`]"); + DebugPrinter::addNewLine(ret); DebugPrinter::addBlocks(ret, _children[0]->debugPrint()); return ret; diff --git a/src/mongo/db/exec/sbe/util/debug_print.cpp b/src/mongo/db/exec/sbe/util/debug_print.cpp index b659f9a7ba6..939779be8de 100644 --- a/src/mongo/db/exec/sbe/util/debug_print.cpp +++ b/src/mongo/db/exec/sbe/util/debug_print.cpp @@ -116,8 +116,8 @@ std::string DebugPrinter::print(const std::vector<Block>& blocks) { return ret; } -std::string DebugPrinter::print(PlanStage* s) { - return print(s->debugPrint()); +std::string DebugPrinter::print(const PlanStage& s) { + return print(s.debugPrint()); } } // namespace sbe } // namespace mongo diff --git a/src/mongo/db/exec/sbe/util/debug_print.h b/src/mongo/db/exec/sbe/util/debug_print.h index 7b58391e207..19e29d7bdf4 100644 --- a/src/mongo/db/exec/sbe/util/debug_print.h +++ b/src/mongo/db/exec/sbe/util/debug_print.h @@ -116,7 +116,7 @@ public: std::make_move_iterator(blocks.begin()), std::make_move_iterator(blocks.end())); } - std::string print(PlanStage* s); + std::string print(const PlanStage& s); std::string print(const std::vector<Block>& blocks); private: diff --git a/src/mongo/db/exec/sbe/values/slot.h b/src/mongo/db/exec/sbe/values/slot.h index 95131fb7fdc..04449a49305 100644 --- a/src/mongo/db/exec/sbe/values/slot.h +++ b/src/mongo/db/exec/sbe/values/slot.h @@ -591,4 +591,20 @@ using SlotVector = std::vector<SlotId>; using SlotIdGenerator = IdGenerator<value::SlotId>; using FrameIdGenerator = IdGenerator<FrameId>; using SpoolIdGenerator = IdGenerator<SpoolId>; + +/** + * Given an unordered slot 'map', calls 'callback' for each slot/value pair in order of ascending + * slot id. + */ +template <typename T, typename C> +void orderedSlotMapTraverse(const SlotMap<T>& map, C callback) { + std::set<SlotId> slots; + for (auto&& elem : map) { + slots.insert(elem.first); + } + + for (auto slot : slots) { + callback(slot, map.at(slot)); + } +} } // namespace mongo::sbe::value diff --git a/src/mongo/db/pipeline/document_source_cursor.h b/src/mongo/db/pipeline/document_source_cursor.h index 6df87f7c706..2d3a5332b10 100644 --- a/src/mongo/db/pipeline/document_source_cursor.h +++ b/src/mongo/db/pipeline/document_source_cursor.h @@ -115,6 +115,10 @@ public: return &_stats; } + const PlanExplainer::ExplainVersion& getExplainVersion() const { + return _exec->getPlanExplainer().getVersion(); + } + protected: DocumentSourceCursor(const CollectionPtr& collection, std::unique_ptr<PlanExecutor, PlanExecutor::Deleter> exec, diff --git a/src/mongo/db/pipeline/plan_explainer_pipeline.cpp b/src/mongo/db/pipeline/plan_explainer_pipeline.cpp index 49e05ead917..4b3bcfac0a6 100644 --- a/src/mongo/db/pipeline/plan_explainer_pipeline.cpp +++ b/src/mongo/db/pipeline/plan_explainer_pipeline.cpp @@ -50,6 +50,16 @@ void collectPlanSummaryStats(const DocSourceType& source, PlanSummaryStats* stat statsOut->accumulate(docSpecificStats.planSummaryStats); } +const PlanExplainer::ExplainVersion& PlanExplainerPipeline::getVersion() const { + static const ExplainVersion kExplainVersion = "1"; + + if (auto docSourceCursor = + dynamic_cast<DocumentSourceCursor*>(_pipeline->getSources().front().get())) { + return docSourceCursor->getExplainVersion(); + } + return kExplainVersion; +} + std::string PlanExplainerPipeline::getPlanSummary() const { if (auto docSourceCursor = dynamic_cast<DocumentSourceCursor*>(_pipeline->getSources().front().get())) { diff --git a/src/mongo/db/pipeline/plan_explainer_pipeline.h b/src/mongo/db/pipeline/plan_explainer_pipeline.h index e2021b1c20e..f28adc3c7ea 100644 --- a/src/mongo/db/pipeline/plan_explainer_pipeline.h +++ b/src/mongo/db/pipeline/plan_explainer_pipeline.h @@ -44,6 +44,7 @@ public: return false; } + const ExplainVersion& getVersion() const final; std::string getPlanSummary() const final; void getSummaryStats(PlanSummaryStats* statsOut) const final; PlanStatsDetails getWinningPlanStats(ExplainOptions::Verbosity verbosity) const final; diff --git a/src/mongo/db/query/explain.cpp b/src/mongo/db/query/explain.cpp index c271cfe1c81..330e2709a6d 100644 --- a/src/mongo/db/query/explain.cpp +++ b/src/mongo/db/query/explain.cpp @@ -84,7 +84,6 @@ void generatePlannerInfo(PlanExecutor* exec, BSONObjBuilder* out) { BSONObjBuilder plannerBob(out->subobjStart("queryPlanner")); - plannerBob.append("plannerVersion", QueryPlanner::kPlannerVersion); plannerBob.append("namespace", exec->nss().ns()); // Find whether there is an index filter set for the query shape. The 'indexFilterSet' field @@ -266,6 +265,14 @@ void executePlan(PlanExecutor* exec) { // Discard the resulting documents. } } + +/** + * Returns a BSON document in the form of {explainVersion: <version>} with the 'version' parameter + * serialized into the <version> element. + */ +BSONObj explainVersionToBson(const PlanExplainer::ExplainVersion& version) { + return BSON("explainVersion" << version); +} } // namespace void Explain::explainStages(PlanExecutor* exec, @@ -280,6 +287,9 @@ void Explain::explainStages(PlanExecutor* exec, // Use the stats trees to produce explain BSON. // + auto&& explainer = exec->getPlanExplainer(); + out->appendElements(explainVersionToBson(explainer.getVersion())); + if (verbosity >= ExplainOptions::Verbosity::kQueryPlanner) { generatePlannerInfo(exec, collection, extraInfo, out); } @@ -309,6 +319,8 @@ void Explain::explainPipeline(PlanExecutor* exec, executePlan(pipelineExec); } + auto&& explainer = pipelineExec->getPlanExplainer(); + out->appendElements(explainVersionToBson(explainer.getVersion())); *out << "stages" << Value(pipelineExec->writeExplainOps(verbosity)); explain_common::generateServerInfo(out); @@ -381,14 +393,15 @@ void Explain::planCacheEntryToBSON(const PlanCacheEntry& entry, BSONObjBuilder* } } - auto explainer = stdx::visit( - visit_helper::Overloaded{[](const plan_ranker::StatsDetails&) { - return plan_explainer_factory::make(nullptr); - }, - [](const plan_ranker::SBEStatsDetails&) { - return plan_explainer_factory::make(nullptr, nullptr); - }}, - debugInfo.decision->stats); + auto explainer = + stdx::visit(visit_helper::Overloaded{[](const plan_ranker::StatsDetails&) { + return plan_explainer_factory::make(nullptr); + }, + [](const plan_ranker::SBEStatsDetails&) { + return plan_explainer_factory::make( + nullptr, nullptr, nullptr); + }}, + debugInfo.decision->stats); auto plannerStats = explainer->getCachedPlanStats(debugInfo, ExplainOptions::Verbosity::kQueryPlanner); auto execStats = diff --git a/src/mongo/db/query/get_executor.cpp b/src/mongo/db/query/get_executor.cpp index 8089c0b5b8d..e8e6abfb9d5 100644 --- a/src/mongo/db/query/get_executor.cpp +++ b/src/mongo/db/query/get_executor.cpp @@ -483,7 +483,8 @@ public: invariant(_roots.size() == 1); invariant(_solutions.size() == 1); invariant(_roots[0].first); - auto explainer = plan_explainer_factory::make(_roots[0].first.get(), _solutions[0].get()); + auto explainer = plan_explainer_factory::make( + _roots[0].first.get(), &_roots[0].second, _solutions[0].get()); return explainer->getPlanSummary(); } diff --git a/src/mongo/db/query/plan_executor_factory.cpp b/src/mongo/db/query/plan_executor_factory.cpp index f7e02c57529..d43cdfd9e32 100644 --- a/src/mongo/db/query/plan_executor_factory.cpp +++ b/src/mongo/db/query/plan_executor_factory.cpp @@ -132,7 +132,7 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> make( 5, "SBE plan", "slots"_attr = data.debugString(), - "stages"_attr = sbe::DebugPrinter{}.print(rootStage.get())); + "stages"_attr = sbe::DebugPrinter{}.print(*rootStage)); rootStage->prepare(data.ctx); @@ -164,7 +164,7 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> make( 5, "SBE plan", "slots"_attr = candidates.winner().data.debugString(), - "stages"_attr = sbe::DebugPrinter{}.print(candidates.winner().root.get())); + "stages"_attr = sbe::DebugPrinter{}.print(*candidates.winner().root)); return {{new PlanExecutorSBE(opCtx, std::move(cq), diff --git a/src/mongo/db/query/plan_executor_sbe.cpp b/src/mongo/db/query/plan_executor_sbe.cpp index 11b856b10b2..7c32db8ad00 100644 --- a/src/mongo/db/query/plan_executor_sbe.cpp +++ b/src/mongo/db/query/plan_executor_sbe.cpp @@ -108,7 +108,7 @@ PlanExecutorSBE::PlanExecutorSBE(OperationContext* opCtx, candidates.plans.erase(candidates.plans.begin() + candidates.winnerIdx); } _planExplainer = plan_explainer_factory::make( - _root.get(), _solution.get(), std::move(candidates.plans), isMultiPlan); + _root.get(), &winner.data, _solution.get(), std::move(candidates.plans), isMultiPlan); } void PlanExecutorSBE::saveState() { diff --git a/src/mongo/db/query/plan_explainer.h b/src/mongo/db/query/plan_explainer.h index 96f18f8d9ed..e6d19e32470 100644 --- a/src/mongo/db/query/plan_explainer.h +++ b/src/mongo/db/query/plan_explainer.h @@ -50,6 +50,11 @@ static constexpr int kMaxExplainStatsBSONSizeMB = 10 * 1024 * 1024; class PlanExplainer { public: /** + * A version of the explain format. "1" is used for the classic engine and "2" for SBE. + */ + using ExplainVersion = std::string; + + /** * This pair holds a serialized BSON document that details the plan selected by the query * planner, and optional summary stats for an execution tree if the verbosity level for the * generated stats is 'executionStats' or higher. The format of these stats are opaque to the @@ -66,6 +71,11 @@ public: virtual ~PlanExplainer() = default; /** + * Returns a version of the explain format supported by this explainer. + */ + virtual const ExplainVersion& getVersion() const = 0; + + /** * Returns 'true' if this PlanExplainer can provide information on the winning plan and rejected * candidate plans, meaning that the QueryPlanner generated multiple candidate plans and the * winning plan was chosen by the multi-planner. diff --git a/src/mongo/db/query/plan_explainer_factory.cpp b/src/mongo/db/query/plan_explainer_factory.cpp index da64fec81f5..74d0c1dfd3d 100644 --- a/src/mongo/db/query/plan_explainer_factory.cpp +++ b/src/mongo/db/query/plan_explainer_factory.cpp @@ -43,15 +43,18 @@ std::unique_ptr<PlanExplainer> make(PlanStage* root, const PlanEnumeratorExplain return std::make_unique<PlanExplainerImpl>(root, explainInfo); } -std::unique_ptr<PlanExplainer> make(sbe::PlanStage* root, const QuerySolution* solution) { - return make(root, solution, {}, false); +std::unique_ptr<PlanExplainer> make(sbe::PlanStage* root, + const stage_builder::PlanStageData* data, + const QuerySolution* solution) { + return make(root, data, solution, {}, false); } std::unique_ptr<PlanExplainer> make(sbe::PlanStage* root, + const stage_builder::PlanStageData* data, const QuerySolution* solution, std::vector<sbe::plan_ranker::CandidatePlan> rejectedCandidates, bool isMultiPlan) { return std::make_unique<PlanExplainerSBE>( - root, solution, std::move(rejectedCandidates), isMultiPlan); + root, data, solution, std::move(rejectedCandidates), isMultiPlan); } } // namespace mongo::plan_explainer_factory diff --git a/src/mongo/db/query/plan_explainer_factory.h b/src/mongo/db/query/plan_explainer_factory.h index 55e42d90791..f6736067bda 100644 --- a/src/mongo/db/query/plan_explainer_factory.h +++ b/src/mongo/db/query/plan_explainer_factory.h @@ -40,8 +40,11 @@ namespace mongo::plan_explainer_factory { std::unique_ptr<PlanExplainer> make(PlanStage* root); std::unique_ptr<PlanExplainer> make(PlanStage* root, const PlanEnumeratorExplainInfo& enumeratorInfo); -std::unique_ptr<PlanExplainer> make(sbe::PlanStage* root, const QuerySolution* solution); std::unique_ptr<PlanExplainer> make(sbe::PlanStage* root, + const stage_builder::PlanStageData* data, + const QuerySolution* solution); +std::unique_ptr<PlanExplainer> make(sbe::PlanStage* root, + const stage_builder::PlanStageData* data, const QuerySolution* solution, std::vector<sbe::plan_ranker::CandidatePlan> rejectedCandidates, bool isMultiPlan); diff --git a/src/mongo/db/query/plan_explainer_impl.cpp b/src/mongo/db/query/plan_explainer_impl.cpp index 78614450060..a04882ce57c 100644 --- a/src/mongo/db/query/plan_explainer_impl.cpp +++ b/src/mongo/db/query/plan_explainer_impl.cpp @@ -536,6 +536,11 @@ void appendMultikeyPaths(const BSONObj& keyPattern, subMultikeyPaths.doneFast(); } +const PlanExplainer::ExplainVersion& PlanExplainerImpl::getVersion() const { + static const ExplainVersion kExplainVersion = "1"; + return kExplainVersion; +} + bool PlanExplainerImpl::isMultiPlan() const { return getStageByType(_root, StageType::STAGE_MULTI_PLAN) != nullptr; } diff --git a/src/mongo/db/query/plan_explainer_impl.h b/src/mongo/db/query/plan_explainer_impl.h index 82d97e5a83b..859b633e0e1 100644 --- a/src/mongo/db/query/plan_explainer_impl.h +++ b/src/mongo/db/query/plan_explainer_impl.h @@ -48,6 +48,7 @@ public: : PlanExplainer{explainInfo}, _root{root} {} PlanExplainerImpl(PlanStage* root) : _root{root} {} + const ExplainVersion& getVersion() const final; bool isMultiPlan() const final; std::string getPlanSummary() const final; void getSummaryStats(PlanSummaryStats* statsOut) const final; diff --git a/src/mongo/db/query/plan_explainer_sbe.cpp b/src/mongo/db/query/plan_explainer_sbe.cpp index 677e0ed393a..e993b2cf6c0 100644 --- a/src/mongo/db/query/plan_explainer_sbe.cpp +++ b/src/mongo/db/query/plan_explainer_sbe.cpp @@ -298,22 +298,36 @@ PlanSummaryStats collectExecutionStatsSummary(const sbe::PlanStageStats* stats) return summary; } -PlanExplainer::PlanStatsDetails buildPlanStatsDetails(const QuerySolutionNode* node, - const sbe::PlanStageStats* stats, - ExplainOptions::Verbosity verbosity) { +PlanExplainer::PlanStatsDetails buildPlanStatsDetails( + const QuerySolutionNode* node, + const sbe::PlanStageStats* stats, + const boost::optional<BSONObj>& execPlanDebugInfo, + ExplainOptions::Verbosity verbosity) { BSONObjBuilder bob; if (verbosity >= ExplainOptions::Verbosity::kExecStats) { auto summary = collectExecutionStatsSummary(stats); statsToBSON(stats, &bob, &bob); + // At the 'kQueryPlanner' verbosity level we use the QSN-derived format for the given plan, + // and thus the winning plan and rejected plans at this verbosity should display the + // stringified SBE plan, which is added below. However, at the 'kExecStats' the execution + // stats use the PlanStage-derived format for the SBE tree, so there is no need to repeat + // the stringified SBE plan and we only included what's been generated from the + // PlanStageStats. return {bob.obj(), std::move(summary)}; } statsToBSON(node, &bob, &bob); - return {bob.obj(), boost::none}; + invariant(execPlanDebugInfo); + return {BSON("queryPlan" << bob.obj() << "slotBasedPlan" << *execPlanDebugInfo), boost::none}; } } // namespace +const PlanExplainer::ExplainVersion& PlanExplainerSBE::getVersion() const { + static const ExplainVersion kExplainVersion = "2"; + return kExplainVersion; +} + std::string PlanExplainerSBE::getPlanSummary() const { if (!_solution) { return {}; @@ -462,7 +476,7 @@ PlanExplainer::PlanStatsDetails PlanExplainerSBE::getWinningPlanStats( invariant(_root); invariant(_solution); auto stats = _root->getStats(true /* includeDebugInfo */); - return buildPlanStatsDetails(_solution->root(), stats.get(), verbosity); + return buildPlanStatsDetails(_solution->root(), stats.get(), _execPlanDebugInfo, verbosity); } std::vector<PlanExplainer::PlanStatsDetails> PlanExplainerSBE::getRejectedPlansStats( @@ -478,7 +492,9 @@ std::vector<PlanExplainer::PlanStatsDetails> PlanExplainerSBE::getRejectedPlansS invariant(candidate.solution); auto stats = candidate.root->getStats(true /* includeDebugInfo */); - res.push_back(buildPlanStatsDetails(candidate.solution->root(), stats.get(), verbosity)); + auto execPlanDebugInfo = buildExecPlanDebugInfo(candidate.root.get(), &candidate.data); + res.push_back(buildPlanStatsDetails( + candidate.solution->root(), stats.get(), execPlanDebugInfo, verbosity)); } return res; } @@ -491,7 +507,7 @@ std::vector<PlanExplainer::PlanStatsDetails> PlanExplainerSBE::getCachedPlanStat auto&& stats = decision.getStats<mongo::sbe::PlanStageStats>(); if (verbosity >= ExplainOptions::Verbosity::kExecStats) { for (auto&& planStats : stats.candidatePlanStats) { - res.push_back(buildPlanStatsDetails(nullptr, planStats.get(), verbosity)); + res.push_back(buildPlanStatsDetails(nullptr, planStats.get(), boost::none, verbosity)); } } else { // At the "queryPlanner" verbosity we only need to provide details about the winning plan diff --git a/src/mongo/db/query/plan_explainer_sbe.h b/src/mongo/db/query/plan_explainer_sbe.h index d8056ade4ed..dba5316e82b 100644 --- a/src/mongo/db/query/plan_explainer_sbe.h +++ b/src/mongo/db/query/plan_explainer_sbe.h @@ -41,6 +41,7 @@ namespace mongo { class PlanExplainerSBE final : public PlanExplainer { public: PlanExplainerSBE(const sbe::PlanStage* root, + const stage_builder::PlanStageData* data, const QuerySolution* solution, std::vector<sbe::plan_ranker::CandidatePlan> rejectedCandidates, bool isMultiPlan) @@ -48,12 +49,14 @@ public: _root{root}, _solution{solution}, _rejectedCandidates{std::move(rejectedCandidates)}, - _isMultiPlan{isMultiPlan} {} + _isMultiPlan{isMultiPlan}, + _execPlanDebugInfo{buildExecPlanDebugInfo(_root, data)} {} bool isMultiPlan() const final { return _isMultiPlan; } + const ExplainVersion& getVersion() const final; std::string getPlanSummary() const final; void getSummaryStats(PlanSummaryStats* statsOut) const final; PlanStatsDetails getWinningPlanStats(ExplainOptions::Verbosity verbosity) const final; @@ -63,9 +66,25 @@ public: ExplainOptions::Verbosity) const final; private: + boost::optional<BSONObj> buildExecPlanDebugInfo( + const sbe::PlanStage* root, const stage_builder::PlanStageData* data) const { + if (root && data) { + return BSON("slots" << data->debugString() << "stages" + << sbe::DebugPrinter().print(*_root)); + } + return boost::none; + } + const sbe::PlanStage* _root{nullptr}; const QuerySolution* _solution{nullptr}; const std::vector<sbe::plan_ranker::CandidatePlan> _rejectedCandidates; const bool _isMultiPlan{false}; + // Contains information about the slots returned by the PlanStage tree, along with the tree + // itself, serialized into a string. This optional object is then included into explain output + // and is only initialized when the _root of the PlanStage tree is available. The only case when + // it's not available is when PlanStatsDetails are generated from the plan cache (by + // calling getCachedPlanStats()), in which case this debug info is already included into the + // plan cache entry as part of a serialized winning plan. + boost::optional<BSONObj> _execPlanDebugInfo; }; } // namespace mongo diff --git a/src/mongo/db/query/plan_ranker_util.h b/src/mongo/db/query/plan_ranker_util.h index 06557f2b574..58bcc16d3df 100644 --- a/src/mongo/db/query/plan_ranker_util.h +++ b/src/mongo/db/query/plan_ranker_util.h @@ -84,8 +84,8 @@ StatusWith<std::unique_ptr<PlanRankingDecision>> pickBestPlan( candidates[i].solution->_enumeratorExplainInfo); } else { static_assert(std::is_same_v<PlanStageStatsType, mongo::sbe::PlanStageStats>); - return plan_explainer_factory::make(candidates[i].root.get(), - candidates[i].solution.get()); + return plan_explainer_factory::make( + candidates[i].root.get(), &candidates[i].data, candidates[i].solution.get()); } }(); @@ -143,8 +143,8 @@ StatusWith<std::unique_ptr<PlanRankingDecision>> pickBestPlan( // For SBE, we need to store a serialized winning plan within the ranking decision to be // able to included it into the explain output for a cached plan stats, since we cannot // reconstruct it from a PlanStageStats tree. - auto explainer = - plan_explainer_factory::make(candidates[0].root.get(), candidates[0].solution.get()); + auto explainer = plan_explainer_factory::make( + candidates[0].root.get(), &candidates[0].data, candidates[0].solution.get()); auto&& [stats, _] = explainer->getWinningPlanStats(ExplainOptions::Verbosity::kQueryPlanner); SBEStatsDetails details; diff --git a/src/mongo/db/query/query_planner.cpp b/src/mongo/db/query/query_planner.cpp index a910d7504d3..70f62ecc418 100644 --- a/src/mongo/db/query/query_planner.cpp +++ b/src/mongo/db/query/query_planner.cpp @@ -344,9 +344,6 @@ bool providesSort(const CanonicalQuery& query, const BSONObj& kp) { return query.getQueryRequest().getSort().isPrefixOf(kp, SimpleBSONElementComparator::kInstance); } -// static -const int QueryPlanner::kPlannerVersion = 1; - StatusWith<std::unique_ptr<PlanCacheIndexTree>> QueryPlanner::cacheDataFromTaggedTree( const MatchExpression* const taggedTree, const vector<IndexEntry>& relevantIndices) { if (!taggedTree) { diff --git a/src/mongo/db/query/query_planner.h b/src/mongo/db/query/query_planner.h index 236ec4f0269..dcd6ca6e542 100644 --- a/src/mongo/db/query/query_planner.h +++ b/src/mongo/db/query/query_planner.h @@ -80,9 +80,6 @@ public: std::map<IndexEntry::Identifier, size_t> indexMap; }; - // Identifies the version of the query planner module. Reported in explain. - static const int kPlannerVersion; - /** * Returns the list of possible query solutions for the provided 'query'. Uses the indices and * other data in 'params' to determine the set of available plans. diff --git a/src/mongo/db/query/sbe_cached_solution_planner.cpp b/src/mongo/db/query/sbe_cached_solution_planner.cpp index aece175a58f..321410b9a1a 100644 --- a/src/mongo/db/query/sbe_cached_solution_planner.cpp +++ b/src/mongo/db/query/sbe_cached_solution_planner.cpp @@ -52,7 +52,8 @@ CandidatePlans CachedSolutionPlanner::plan( invariant(candidates.size() == 1); return std::move(candidates[0]); }(); - auto explainer = plan_explainer_factory::make(candidate.root.get(), candidate.solution.get()); + auto explainer = plan_explainer_factory::make( + candidate.root.get(), &candidate.data, candidate.solution.get()); if (candidate.failed) { // On failure, fall back to replanning the whole query. We neither evict the existing cache @@ -123,7 +124,7 @@ CandidatePlans CachedSolutionPlanner::replan(bool shouldCache) const { _opCtx, _collection, _cq, *solutions[0], _yieldPolicy); prepareExecutionPlan(root.get(), &data); - auto explainer = plan_explainer_factory::make(root.get(), solutions[0].get()); + auto explainer = plan_explainer_factory::make(root.get(), &data, solutions[0].get()); LOGV2_DEBUG( 2058101, 1, @@ -153,6 +154,7 @@ CandidatePlans CachedSolutionPlanner::replan(bool shouldCache) const { MultiPlanner multiPlanner{_opCtx, _collection, _cq, cachingMode, _yieldPolicy}; auto&& [candidates, winnerIdx] = multiPlanner.plan(std::move(solutions), std::move(roots)); auto explainer = plan_explainer_factory::make(candidates[winnerIdx].root.get(), + &candidates[winnerIdx].data, candidates[winnerIdx].solution.get()); LOGV2_DEBUG(2058201, 1, diff --git a/src/mongo/db/query/sbe_multi_planner.cpp b/src/mongo/db/query/sbe_multi_planner.cpp index b44d786ed0d..b51c642a447 100644 --- a/src/mongo/db/query/sbe_multi_planner.cpp +++ b/src/mongo/db/query/sbe_multi_planner.cpp @@ -65,7 +65,8 @@ CandidatePlans MultiPlanner::finalizeExecutionPlans( LOGV2_DEBUG( 4822875, 5, "Winning solution", "bestSolution"_attr = redact(winner.solution->toString())); - auto explainer = plan_explainer_factory::make(winner.root.get(), winner.solution.get()); + auto explainer = + plan_explainer_factory::make(winner.root.get(), &winner.data, winner.solution.get()); LOGV2_DEBUG(4822876, 2, "Winning plan", "planSummary"_attr = explainer->getPlanSummary()); // Close all candidate plans but the winner. |