mirror of https://github.com/mongodb/mongo
SERVER-83098: Make existing jstest work with the new CQF explain (core/query and core/sbe) (2)
This commit is contained in:
parent
40c8c6eb88
commit
1aea6febd3
|
|
@ -6,8 +6,6 @@
|
||||||
* assumes_read_concern_local,
|
* assumes_read_concern_local,
|
||||||
* ]
|
* ]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// adding temporary comment
|
|
||||||
import {getOptimizer, getWinningPlan, isIxscan} from "jstests/libs/analyze_plan.js";
|
import {getOptimizer, getWinningPlan, isIxscan} from "jstests/libs/analyze_plan.js";
|
||||||
|
|
||||||
const coll = db.elemMatch_index;
|
const coll = db.elemMatch_index;
|
||||||
|
|
|
||||||
|
|
@ -44,5 +44,3 @@ switch (getOptimizer(explainResult)) {
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
t.drop();
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
// where agg execution differs from query. It also includes confirmation that hint works for find
|
// where agg execution differs from query. It also includes confirmation that hint works for find
|
||||||
// command against views, which is converted to a hinted aggregation on execution.
|
// command against views, which is converted to a hinted aggregation on execution.
|
||||||
|
|
||||||
import {getAggPlanStage, getPlanStage} from "jstests/libs/analyze_plan.js";
|
import {getAggPlanStage, getOptimizer, getPlanStage} from "jstests/libs/analyze_plan.js";
|
||||||
|
|
||||||
const testDB = db.getSiblingDB("agg_hint");
|
const testDB = db.getSiblingDB("agg_hint");
|
||||||
assert.commandWorked(testDB.dropDatabase());
|
assert.commandWorked(testDB.dropDatabase());
|
||||||
|
|
@ -22,11 +22,27 @@ const view = testDB.getCollection(viewName);
|
||||||
|
|
||||||
function confirmWinningPlanUsesExpectedIndex(
|
function confirmWinningPlanUsesExpectedIndex(
|
||||||
explainResult, expectedKeyPattern, stageName, pipelineOptimizedAway) {
|
explainResult, expectedKeyPattern, stageName, pipelineOptimizedAway) {
|
||||||
const planStage = pipelineOptimizedAway ? getPlanStage(explainResult, stageName)
|
const optimizer = getOptimizer(explainResult);
|
||||||
: getAggPlanStage(explainResult, stageName);
|
|
||||||
assert.neq(null, planStage);
|
|
||||||
|
|
||||||
assert.eq(planStage.keyPattern, expectedKeyPattern, tojson(planStage));
|
if (!(optimizer in stageName) || stageName[optimizer] === "") {
|
||||||
|
// TODO SERVER-77719: Ensure that the expected operator is defined for all optimizers. There
|
||||||
|
// should be an exception here.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const planStage = pipelineOptimizedAway ? getPlanStage(explainResult, stageName[optimizer])
|
||||||
|
: getAggPlanStage(explainResult, stageName[optimizer]);
|
||||||
|
|
||||||
|
switch (optimizer) {
|
||||||
|
case "classic":
|
||||||
|
assert.neq(null, planStage);
|
||||||
|
assert.eq(planStage.keyPattern, expectedKeyPattern, tojson(planStage));
|
||||||
|
break;
|
||||||
|
case "CQF":
|
||||||
|
// TODO SERVER-77719: Ensure that the decision for using the scan lines up with CQF
|
||||||
|
// optimizer. M2: allow only collscans, M4: check bonsai behavior for index scan.
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Runs explain on 'command', with the hint specified by 'hintKeyPattern' when not null.
|
// Runs explain on 'command', with the hint specified by 'hintKeyPattern' when not null.
|
||||||
|
|
@ -37,7 +53,10 @@ function confirmCommandUsesIndex({
|
||||||
command = null,
|
command = null,
|
||||||
hintKeyPattern = null,
|
hintKeyPattern = null,
|
||||||
expectedKeyPattern = null,
|
expectedKeyPattern = null,
|
||||||
stageName = "IXSCAN",
|
stageName = {
|
||||||
|
"classic": "IXSCAN",
|
||||||
|
"CQF": "IndexScan"
|
||||||
|
},
|
||||||
pipelineOptimizedAway = false
|
pipelineOptimizedAway = false
|
||||||
} = {}) {
|
} = {}) {
|
||||||
if (hintKeyPattern) {
|
if (hintKeyPattern) {
|
||||||
|
|
@ -60,7 +79,10 @@ function confirmAggUsesIndex({
|
||||||
aggPipeline = [],
|
aggPipeline = [],
|
||||||
hintKeyPattern = null,
|
hintKeyPattern = null,
|
||||||
expectedKeyPattern = null,
|
expectedKeyPattern = null,
|
||||||
stageName = "IXSCAN",
|
stageName = {
|
||||||
|
"classic": "IXSCAN",
|
||||||
|
"CQF": "IndexScan"
|
||||||
|
},
|
||||||
pipelineOptimizedAway = false
|
pipelineOptimizedAway = false
|
||||||
} = {}) {
|
} = {}) {
|
||||||
let options = {};
|
let options = {};
|
||||||
|
|
@ -248,16 +270,18 @@ for (let i = 0; i < 5; ++i) {
|
||||||
}
|
}
|
||||||
assert.commandWorked(testDB.createView(viewName, collName, []));
|
assert.commandWorked(testDB.createView(viewName, collName, []));
|
||||||
|
|
||||||
|
// TODO SERVER-77719: Ensure that the decision for using the scan lines up with CQF
|
||||||
|
// optimizer. M2: allow only collscans, M4: check bonsai behavior for index scan.
|
||||||
confirmCommandUsesIndex({
|
confirmCommandUsesIndex({
|
||||||
command: {count: view.getName(), query: {x: 3}},
|
command: {count: view.getName(), query: {x: 3}},
|
||||||
expectedKeyPattern: {x: 1},
|
expectedKeyPattern: {x: 1},
|
||||||
stageName: "COUNT_SCAN"
|
stageName: {"classic": "COUNT_SCAN", "CQF": ""}
|
||||||
});
|
});
|
||||||
confirmCommandUsesIndex({
|
confirmCommandUsesIndex({
|
||||||
command: {count: view.getName(), query: {x: 3}},
|
command: {count: view.getName(), query: {x: 3}},
|
||||||
hintKeyPattern: {x: 1},
|
hintKeyPattern: {x: 1},
|
||||||
expectedKeyPattern: {x: 1},
|
expectedKeyPattern: {x: 1},
|
||||||
stageName: "COUNT_SCAN"
|
stageName: {"classic": "COUNT_SCAN", "CQF": ""}
|
||||||
});
|
});
|
||||||
confirmCommandUsesIndex({
|
confirmCommandUsesIndex({
|
||||||
command: {count: view.getName(), query: {x: 3}},
|
command: {count: view.getName(), query: {x: 3}},
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,15 @@
|
||||||
* Test covering behavior for queries over a multikey index.
|
* Test covering behavior for queries over a multikey index.
|
||||||
*/
|
*/
|
||||||
// For making assertions about explain output.
|
// For making assertions about explain output.
|
||||||
import {getPlanStage, getWinningPlan, isIxscan, planHasStage} from "jstests/libs/analyze_plan.js";
|
import {
|
||||||
|
getOptimizer,
|
||||||
|
getPlanStage,
|
||||||
|
getWinningPlan,
|
||||||
|
isCollscan,
|
||||||
|
isIxscan,
|
||||||
|
isIxscanMultikey,
|
||||||
|
planHasStage
|
||||||
|
} from "jstests/libs/analyze_plan.js";
|
||||||
|
|
||||||
let coll = db.covered_multikey;
|
let coll = db.covered_multikey;
|
||||||
coll.drop();
|
coll.drop();
|
||||||
|
|
@ -21,7 +29,18 @@ 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}));
|
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();
|
let explainRes = coll.explain("queryPlanner").find({a: 1, b: 2}, {_id: 0, a: 1}).finish();
|
||||||
let winningPlan = getWinningPlan(explainRes.queryPlanner);
|
let winningPlan = getWinningPlan(explainRes.queryPlanner);
|
||||||
assert(isIxscan(db, winningPlan));
|
switch (getOptimizer(explainRes)) {
|
||||||
|
case "classic": {
|
||||||
|
assert(isIxscan(db, winningPlan));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "CQF": {
|
||||||
|
// TODO SERVER-77719: Ensure that the decision for using the scan lines up with CQF
|
||||||
|
// optimizer. M2: allow only collscans, M4: check bonsai behavior for index scan.
|
||||||
|
assert(isCollscan(db, winningPlan));
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
assert(!planHasStage(db, winningPlan, "FETCH"));
|
assert(!planHasStage(db, winningPlan, "FETCH"));
|
||||||
|
|
||||||
coll.drop();
|
coll.drop();
|
||||||
|
|
@ -47,10 +66,20 @@ assert.commandWorked(coll.createIndex({a: 1}));
|
||||||
assert.eq({a: []}, coll.findOne({a: []}, {_id: 0, a: 1}));
|
assert.eq({a: []}, coll.findOne({a: []}, {_id: 0, a: 1}));
|
||||||
explainRes = coll.explain("queryPlanner").find({a: []}, {_id: 0, a: 1}).finish();
|
explainRes = coll.explain("queryPlanner").find({a: []}, {_id: 0, a: 1}).finish();
|
||||||
winningPlan = getWinningPlan(explainRes.queryPlanner);
|
winningPlan = getWinningPlan(explainRes.queryPlanner);
|
||||||
assert(planHasStage(db, winningPlan, "IXSCAN"));
|
switch (getOptimizer(explainRes)) {
|
||||||
assert(planHasStage(db, winningPlan, "FETCH"));
|
case "classic": {
|
||||||
let ixscanStage = getPlanStage(winningPlan, "IXSCAN");
|
assert(isIxscan(db, winningPlan));
|
||||||
assert.eq(true, ixscanStage.isMultiKey);
|
assert(planHasStage(db, winningPlan, "FETCH"));
|
||||||
|
assert(isIxscanMultikey(winningPlan));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "CQF": {
|
||||||
|
// TODO SERVER-77719: Ensure that the decision for using the scan lines up with CQF
|
||||||
|
// optimizer. M2: allow only collscans, M4: check bonsai behavior for index scan.
|
||||||
|
assert(isCollscan(db, winningPlan));
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Verify that a query cannot be covered over a path which is multikey due to a single-element
|
// Verify that a query cannot be covered over a path which is multikey due to a single-element
|
||||||
// array.
|
// array.
|
||||||
|
|
@ -60,10 +89,20 @@ assert.commandWorked(coll.createIndex({a: 1}));
|
||||||
assert.eq({a: [2]}, coll.findOne({a: 2}, {_id: 0, 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();
|
explainRes = coll.explain("queryPlanner").find({a: 2}, {_id: 0, a: 1}).finish();
|
||||||
winningPlan = getWinningPlan(explainRes.queryPlanner);
|
winningPlan = getWinningPlan(explainRes.queryPlanner);
|
||||||
assert(planHasStage(db, winningPlan, "IXSCAN"));
|
switch (getOptimizer(explainRes)) {
|
||||||
assert(planHasStage(db, winningPlan, "FETCH"));
|
case "classic": {
|
||||||
ixscanStage = getPlanStage(winningPlan, "IXSCAN");
|
assert(isIxscan(db, winningPlan));
|
||||||
assert.eq(true, ixscanStage.isMultiKey);
|
assert(planHasStage(db, winningPlan, "FETCH"));
|
||||||
|
assert(isIxscanMultikey(winningPlan));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "CQF": {
|
||||||
|
// TODO SERVER-77719: Ensure that the decision for using the scan lines up with CQF
|
||||||
|
// optimizer. M2: allow only collscans, M4: check bonsai behavior for index scan.
|
||||||
|
assert(isCollscan(db, winningPlan));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Verify that a query cannot be covered over a path which is multikey due to a single-element
|
// 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.
|
// array, where the path is made multikey by an update rather than an insert.
|
||||||
|
|
@ -74,10 +113,20 @@ assert.commandWorked(coll.update({}, {$set: {a: [2]}}));
|
||||||
assert.eq({a: [2]}, coll.findOne({a: 2}, {_id: 0, 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();
|
explainRes = coll.explain("queryPlanner").find({a: 2}, {_id: 0, a: 1}).finish();
|
||||||
winningPlan = getWinningPlan(explainRes.queryPlanner);
|
winningPlan = getWinningPlan(explainRes.queryPlanner);
|
||||||
assert(planHasStage(db, winningPlan, "IXSCAN"));
|
switch (getOptimizer(explainRes)) {
|
||||||
assert(planHasStage(db, winningPlan, "FETCH"));
|
case "classic": {
|
||||||
ixscanStage = getPlanStage(winningPlan, "IXSCAN");
|
assert(isIxscan(db, winningPlan));
|
||||||
assert.eq(true, ixscanStage.isMultiKey);
|
assert(planHasStage(db, winningPlan, "FETCH"));
|
||||||
|
assert(isIxscanMultikey(winningPlan));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "CQF": {
|
||||||
|
// TODO SERVER-77719: Ensure that the decision for using the scan lines up with CQF
|
||||||
|
// optimizer. M2: allow only collscans, M4: check bonsai behavior for index scan.
|
||||||
|
assert(isCollscan(db, winningPlan));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Verify that a trailing empty array makes a 2dsphere index multikey.
|
// Verify that a trailing empty array makes a 2dsphere index multikey.
|
||||||
assert(coll.drop());
|
assert(coll.drop());
|
||||||
|
|
@ -85,15 +134,26 @@ assert.commandWorked(coll.createIndex({"a.b": 1, c: "2dsphere"}));
|
||||||
assert.commandWorked(coll.insert({a: {b: 1}, c: {type: "Point", coordinates: [0, 0]}}));
|
assert.commandWorked(coll.insert({a: {b: 1}, c: {type: "Point", coordinates: [0, 0]}}));
|
||||||
explainRes = coll.explain().find().hint({"a.b": 1, c: "2dsphere"}).finish();
|
explainRes = coll.explain().find().hint({"a.b": 1, c: "2dsphere"}).finish();
|
||||||
winningPlan = getWinningPlan(explainRes.queryPlanner);
|
winningPlan = getWinningPlan(explainRes.queryPlanner);
|
||||||
ixscanStage = getPlanStage(winningPlan, "IXSCAN");
|
let optimizer = getOptimizer(explainRes);
|
||||||
|
let stages = {"classic": "IXSCAN", "CQF": "IndexScan"};
|
||||||
|
let ixscanStage = getPlanStage(winningPlan, stages[optimizer]);
|
||||||
assert.neq(null, ixscanStage);
|
assert.neq(null, ixscanStage);
|
||||||
assert.eq(false, ixscanStage.isMultiKey);
|
assert.eq(false, ixscanStage.isMultiKey);
|
||||||
assert.commandWorked(coll.insert({a: {b: []}, c: {type: "Point", coordinates: [0, 0]}}));
|
assert.commandWorked(coll.insert({a: {b: []}, c: {type: "Point", coordinates: [0, 0]}}));
|
||||||
explainRes = coll.explain().find().hint({"a.b": 1, c: "2dsphere"}).finish();
|
explainRes = coll.explain().find().hint({"a.b": 1, c: "2dsphere"}).finish();
|
||||||
winningPlan = getWinningPlan(explainRes.queryPlanner);
|
winningPlan = getWinningPlan(explainRes.queryPlanner);
|
||||||
ixscanStage = getPlanStage(winningPlan, "IXSCAN");
|
switch (getOptimizer(explainRes)) {
|
||||||
assert.neq(null, ixscanStage);
|
case "classic": {
|
||||||
assert.eq(true, ixscanStage.isMultiKey);
|
assert(isIxscan(db, winningPlan));
|
||||||
|
assert(isIxscanMultikey(winningPlan));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "CQF": {
|
||||||
|
// TODO SERVER-77719: Ensure that the decision for using the scan lines up with CQF
|
||||||
|
// optimizer. M2: allow only collscans, M4: check bonsai behavior for index scan.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Verify that a mid-path empty array makes a 2dsphere index multikey.
|
// Verify that a mid-path empty array makes a 2dsphere index multikey.
|
||||||
assert(coll.drop());
|
assert(coll.drop());
|
||||||
|
|
@ -101,9 +161,18 @@ assert.commandWorked(coll.createIndex({"a.b": 1, c: "2dsphere"}));
|
||||||
assert.commandWorked(coll.insert({a: [], c: {type: "Point", coordinates: [0, 0]}}));
|
assert.commandWorked(coll.insert({a: [], c: {type: "Point", coordinates: [0, 0]}}));
|
||||||
explainRes = coll.explain().find().hint({"a.b": 1, c: "2dsphere"}).finish();
|
explainRes = coll.explain().find().hint({"a.b": 1, c: "2dsphere"}).finish();
|
||||||
winningPlan = getWinningPlan(explainRes.queryPlanner);
|
winningPlan = getWinningPlan(explainRes.queryPlanner);
|
||||||
ixscanStage = getPlanStage(winningPlan, "IXSCAN");
|
switch (getOptimizer(explainRes)) {
|
||||||
assert.neq(null, ixscanStage);
|
case "classic": {
|
||||||
assert.eq(true, ixscanStage.isMultiKey);
|
assert(isIxscan(db, winningPlan));
|
||||||
|
assert(isIxscanMultikey(winningPlan));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "CQF": {
|
||||||
|
// TODO SERVER-77719: Ensure that the decision for using the scan lines up with CQF
|
||||||
|
// optimizer. M2: allow only collscans, M4: check bonsai behavior for index scan.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Verify that a single-element array makes a 2dsphere index multikey.
|
// Verify that a single-element array makes a 2dsphere index multikey.
|
||||||
assert(coll.drop());
|
assert(coll.drop());
|
||||||
|
|
@ -111,6 +180,15 @@ assert.commandWorked(coll.createIndex({"a.b": 1, c: "2dsphere"}));
|
||||||
assert.commandWorked(coll.insert({a: {b: [3]}, c: {type: "Point", coordinates: [0, 0]}}));
|
assert.commandWorked(coll.insert({a: {b: [3]}, c: {type: "Point", coordinates: [0, 0]}}));
|
||||||
explainRes = coll.explain().find().hint({"a.b": 1, c: "2dsphere"}).finish();
|
explainRes = coll.explain().find().hint({"a.b": 1, c: "2dsphere"}).finish();
|
||||||
winningPlan = getWinningPlan(explainRes.queryPlanner);
|
winningPlan = getWinningPlan(explainRes.queryPlanner);
|
||||||
ixscanStage = getPlanStage(winningPlan, "IXSCAN");
|
switch (getOptimizer(explainRes)) {
|
||||||
assert.neq(null, ixscanStage);
|
case "classic": {
|
||||||
assert.eq(true, ixscanStage.isMultiKey);
|
assert(isIxscan(db, winningPlan));
|
||||||
|
assert(isIxscanMultikey(winningPlan));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "CQF": {
|
||||||
|
// TODO SERVER-77719: Ensure that the decision for using the scan lines up with CQF
|
||||||
|
// optimizer. M2: allow only collscans, M4: check bonsai behavior for index scan.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@
|
||||||
// does_not_support_causal_consistency,
|
// does_not_support_causal_consistency,
|
||||||
// ]
|
// ]
|
||||||
|
|
||||||
|
import {hasRejectedPlans} from "jstests/libs/analyze_plan.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests running explain on a variety of explainable commands (find, update, remove, etc.) when
|
* Tests running explain on a variety of explainable commands (find, update, remove, etc.) when
|
||||||
* there are multiple plans available. This is a regression test for SERVER-20849 and SERVER-21376.
|
* there are multiple plans available. This is a regression test for SERVER-20849 and SERVER-21376.
|
||||||
|
|
@ -55,25 +57,10 @@ assert.doesNotThrow(function() {
|
||||||
coll.explain("allPlansExecution").distinct("a", {a: {$gte: 1}});
|
coll.explain("allPlansExecution").distinct("a", {a: {$gte: 1}});
|
||||||
});
|
});
|
||||||
|
|
||||||
// SERVER-21376: Make sure the 'rejectedPlans' field is filled in appropriately.
|
|
||||||
function assertHasRejectedPlans(explainOutput) {
|
|
||||||
var queryPlannerOutput = explainOutput.queryPlanner;
|
|
||||||
|
|
||||||
// The 'rejectedPlans' section will be in a different place if passed through a mongos.
|
|
||||||
if ("SINGLE_SHARD" == queryPlannerOutput.winningPlan.stage) {
|
|
||||||
var shards = queryPlannerOutput.winningPlan.shards;
|
|
||||||
shards.forEach(function assertShardHasRejectedPlans(shard) {
|
|
||||||
assert.gt(shard.rejectedPlans.length, 0);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
assert.gt(queryPlannerOutput.rejectedPlans.length, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var res = coll.explain("queryPlanner").find({a: {$gte: 1}}).finish();
|
var res = coll.explain("queryPlanner").find({a: {$gte: 1}}).finish();
|
||||||
assert.commandWorked(res);
|
assert.commandWorked(res);
|
||||||
assertHasRejectedPlans(res);
|
assert(hasRejectedPlans(res));
|
||||||
|
|
||||||
res = coll.explain("executionStats").find({a: {$gte: 1}}).finish();
|
res = coll.explain("executionStats").find({a: {$gte: 1}}).finish();
|
||||||
assert.commandWorked(res);
|
assert.commandWorked(res);
|
||||||
assertHasRejectedPlans(res);
|
assert(hasRejectedPlans(res));
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@
|
||||||
// assumes_against_mongod_not_mongos,
|
// assumes_against_mongod_not_mongos,
|
||||||
// ]
|
// ]
|
||||||
|
|
||||||
|
import {getOptimizer} from "jstests/libs/analyze_plan.js";
|
||||||
|
|
||||||
const coll = db.explain_winning_plan;
|
const coll = db.explain_winning_plan;
|
||||||
coll.drop();
|
coll.drop();
|
||||||
|
|
||||||
|
|
@ -43,7 +45,16 @@ assert.eq(explain.executionStats.nReturned, numDocs);
|
||||||
// representing two candidate plans evaluated by the multi-planner.
|
// representing two candidate plans evaluated by the multi-planner.
|
||||||
assert(explain.executionStats.hasOwnProperty("allPlansExecution"), explain);
|
assert(explain.executionStats.hasOwnProperty("allPlansExecution"), explain);
|
||||||
assert(Array.isArray(explain.executionStats.allPlansExecution), explain);
|
assert(Array.isArray(explain.executionStats.allPlansExecution), explain);
|
||||||
assert.eq(explain.executionStats.allPlansExecution.length, 2, explain);
|
|
||||||
|
switch (getOptimizer(explain)) {
|
||||||
|
case "classic":
|
||||||
|
assert.eq(explain.executionStats.allPlansExecution.length, 2, explain);
|
||||||
|
break;
|
||||||
|
case "CQF":
|
||||||
|
// TODO SERVER-77719: Ensure that the decision for using the scan lines up with CQF
|
||||||
|
// optimizer. M2: allow only collscans, M4: check bonsai behavior for index scan.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Each candidate plan should have returned exactly 'maxResults' number of documents during the
|
// Each candidate plan should have returned exactly 'maxResults' number of documents during the
|
||||||
// trial period.
|
// trial period.
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
// requires_non_retryable_writes,
|
// requires_non_retryable_writes,
|
||||||
// ]
|
// ]
|
||||||
// Include helpers for analyzing explain output.
|
// Include helpers for analyzing explain output.
|
||||||
import {getWinningPlan, isIdhack} from "jstests/libs/analyze_plan.js";
|
import {getOptimizer, getWinningPlan, isIdhack} from "jstests/libs/analyze_plan.js";
|
||||||
import {checkSBEEnabled} from "jstests/libs/sbe_util.js";
|
import {checkSBEEnabled} from "jstests/libs/sbe_util.js";
|
||||||
|
|
||||||
const t = db.idhack;
|
const t = db.idhack;
|
||||||
|
|
@ -35,14 +35,25 @@ const query = {
|
||||||
};
|
};
|
||||||
let explain = t.find(query).explain("allPlansExecution");
|
let explain = t.find(query).explain("allPlansExecution");
|
||||||
assert.eq(1, explain.executionStats.nReturned, explain);
|
assert.eq(1, explain.executionStats.nReturned, explain);
|
||||||
assert.eq(1, explain.executionStats.totalKeysExamined, explain);
|
|
||||||
let winningPlan = getWinningPlan(explain.queryPlanner);
|
switch (getOptimizer(explain)) {
|
||||||
assert(isIdhack(db, winningPlan), winningPlan);
|
case "classic": {
|
||||||
|
assert.eq(1, explain.executionStats.totalKeysExamined, explain);
|
||||||
|
let winningPlan = getWinningPlan(explain.queryPlanner);
|
||||||
|
assert(isIdhack(db, winningPlan), winningPlan);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "CQF":
|
||||||
|
// TODO SERVER-70847, how to recognize the case of an IDHACK for Bonsai?
|
||||||
|
// TODO SERVER-77719: Ensure that the decision for using the scan lines up with CQF
|
||||||
|
// optimizer. M2: allow only collscans, M4: check bonsai behavior for index scan.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// ID hack cannot be used with hint().
|
// ID hack cannot be used with hint().
|
||||||
t.createIndex({_id: 1, a: 1});
|
t.createIndex({_id: 1, a: 1});
|
||||||
explain = t.find(query).hint({_id: 1, a: 1}).explain();
|
explain = t.find(query).hint({_id: 1, a: 1}).explain();
|
||||||
winningPlan = getWinningPlan(explain.queryPlanner);
|
let winningPlan = getWinningPlan(explain.queryPlanner);
|
||||||
assert(!isIdhack(db, winningPlan), winningPlan);
|
assert(!isIdhack(db, winningPlan), winningPlan);
|
||||||
|
|
||||||
// ID hack cannot be used with skip().
|
// ID hack cannot be used with skip().
|
||||||
|
|
@ -61,7 +72,18 @@ assert(!isIdhack(db, winningPlan), winningPlan);
|
||||||
const parentStage = checkSBEEnabled(db) ? "PROJECTION_COVERED" : "FETCH";
|
const parentStage = checkSBEEnabled(db) ? "PROJECTION_COVERED" : "FETCH";
|
||||||
explain = t.find(query, {_id: 1}).explain();
|
explain = t.find(query, {_id: 1}).explain();
|
||||||
winningPlan = getWinningPlan(explain.queryPlanner);
|
winningPlan = getWinningPlan(explain.queryPlanner);
|
||||||
assert(isIdhack(db, winningPlan), winningPlan);
|
|
||||||
|
switch (getOptimizer(explain)) {
|
||||||
|
case "classic": {
|
||||||
|
assert(isIdhack(db, winningPlan), winningPlan);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "CQF":
|
||||||
|
// TODO SERVER-70847, how to recognize the case of an IDHACK for Bonsai?
|
||||||
|
// TODO SERVER-77719: Ensure that the decision for using the scan lines up with CQF
|
||||||
|
// optimizer. M2: allow only collscans, M4: check bonsai behavior for index scan.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Check doc from covered ID hack query.
|
// Check doc from covered ID hack query.
|
||||||
assert.eq({_id: {x: 2}}, t.findOne(query, {_id: 1}), explain);
|
assert.eq({_id: {x: 2}}, t.findOne(query, {_id: 1}), explain);
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,13 @@
|
||||||
* requires_fcv_70,
|
* requires_fcv_70,
|
||||||
* ]
|
* ]
|
||||||
*/
|
*/
|
||||||
import {getExecutionStages, getPlanStages, isIxscan} from "jstests/libs/analyze_plan.js";
|
import {
|
||||||
|
getExecutionStages,
|
||||||
|
getOptimizer,
|
||||||
|
getPlanStages,
|
||||||
|
isCollscan,
|
||||||
|
isIxscan
|
||||||
|
} from "jstests/libs/analyze_plan.js";
|
||||||
|
|
||||||
const collName = jsTestName();
|
const collName = jsTestName();
|
||||||
const coll = db.getCollection(collName);
|
const coll = db.getCollection(collName);
|
||||||
|
|
@ -40,7 +46,18 @@ function getHash(coll, filterSpec, field, indexSpec) {
|
||||||
* @param {int} expectedKeysExamined - The expected number of keys in the index that were examined.
|
* @param {int} expectedKeysExamined - The expected number of keys in the index that were examined.
|
||||||
*/
|
*/
|
||||||
function assertExplainIxscan(explainPlan, expectedIndexSpec, expectedKeysExamined = 1) {
|
function assertExplainIxscan(explainPlan, expectedIndexSpec, expectedKeysExamined = 1) {
|
||||||
assert(isIxscan(db, explainPlan), explainPlan);
|
switch (getOptimizer(explainPlan)) {
|
||||||
|
case "classic": {
|
||||||
|
assert(isIxscan(db, explainPlan), explainPlan);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "CQF": {
|
||||||
|
// TODO SERVER-77719: Ensure that the decision for using the scan lines up with CQF
|
||||||
|
// optimizer. M2: allow only collscans, M4: check bonsai behavior for index scan.
|
||||||
|
assert(isCollscan(db, explainPlan));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
let execStages = getExecutionStages(explainPlan);
|
let execStages = getExecutionStages(explainPlan);
|
||||||
execStages.forEach(execStage => {
|
execStages.forEach(execStage => {
|
||||||
if (execStage.stage == "SHARDING_FILTER" && execStage.nReturned == 0) {
|
if (execStage.stage == "SHARDING_FILTER" && execStage.nReturned == 0) {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
// # TODO SERVER-77719: Enable this check for Bonsai.
|
// # TODO SERVER-77719: Enable this check for Bonsai.
|
||||||
// cqf_experimental_incompatible
|
// cqf_experimental_incompatible
|
||||||
// ]
|
// ]
|
||||||
import {getPlanStages, getWinningPlan} from "jstests/libs/analyze_plan.js";
|
import {getOptimizer, getPlanStages, getWinningPlan} from "jstests/libs/analyze_plan.js";
|
||||||
import {FixtureHelpers} from "jstests/libs/fixture_helpers.js";
|
import {FixtureHelpers} from "jstests/libs/fixture_helpers.js";
|
||||||
|
|
||||||
const coll = db.jstests_ord;
|
const coll = db.jstests_ord;
|
||||||
|
|
@ -32,10 +32,20 @@ for (let i = 0; i < 1000; ++i) {
|
||||||
// preliminary check here, using explain. However, this does not guarantee the query below will use
|
// preliminary check here, using explain. However, this does not guarantee the query below will use
|
||||||
// an ixscan, because the chosen plan may not be the same.
|
// an ixscan, because the chosen plan may not be the same.
|
||||||
const explainRes = assert.commandWorked(coll.find({$or: [{a: 1}, {b: 1}]}).explain());
|
const explainRes = assert.commandWorked(coll.find({$or: [{a: 1}, {b: 1}]}).explain());
|
||||||
const orStages = getPlanStages(getWinningPlan(explainRes.queryPlanner), "OR");
|
switch (getOptimizer(explainRes)) {
|
||||||
assert(orStages.length > 0, "Expected to find OR stage in explain: " + tojson(explainRes));
|
case "classic": {
|
||||||
assert(orStages.every(orStage => (getPlanStages(orStage, "IXSCAN").length > 0)),
|
const orStages = getPlanStages(getWinningPlan(explainRes.queryPlanner), "OR");
|
||||||
"Expected the plan to be an OR which has (at least) one IXSCAN: " + tojson(explainRes));
|
assert(orStages.length > 0, "Expected to find OR stage in explain: " + tojson(explainRes));
|
||||||
|
assert(
|
||||||
|
orStages.every(orStage => (getPlanStages(orStage, "IXSCAN").length > 0)),
|
||||||
|
"Expected the plan to be an OR which has (at least) one IXSCAN: " + tojson(explainRes));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "CQF":
|
||||||
|
// TODO SERVER-77719: Ensure that the decision for using the scan lines up with CQF
|
||||||
|
// optimizer. M2: allow only collscans, M4: check bonsai behavior for index scan.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// This query should match 180 out of 1180 total documents.
|
// This query should match 180 out of 1180 total documents.
|
||||||
const cursor = coll.find({$or: [{a: 1}, {b: 1}]}).batchSize(100);
|
const cursor = coll.find({$or: [{a: 1}, {b: 1}]}).batchSize(100);
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,14 @@
|
||||||
* when appropriate.
|
* when appropriate.
|
||||||
*/
|
*/
|
||||||
import {arrayEq} from "jstests/aggregation/extras/utils.js";
|
import {arrayEq} from "jstests/aggregation/extras/utils.js";
|
||||||
import {getWinningPlan, isIdhack, isIndexOnly, isIxscan} from "jstests/libs/analyze_plan.js";
|
import {
|
||||||
|
getOptimizer,
|
||||||
|
getWinningPlan,
|
||||||
|
isCollscan,
|
||||||
|
isIdhack,
|
||||||
|
isIndexOnly,
|
||||||
|
isIxscan
|
||||||
|
} from "jstests/libs/analyze_plan.js";
|
||||||
|
|
||||||
let coll = db["projection_dotted_paths"];
|
let coll = db["projection_dotted_paths"];
|
||||||
coll.drop();
|
coll.drop();
|
||||||
|
|
@ -21,16 +28,38 @@ assert.commandWorked(coll.insert({_id: 1, a: 1, b: {c: 1, d: 1, e: 1}, c: 1, e:
|
||||||
let resultDoc = coll.findOne({a: 1}, {_id: 0, a: 1, "b.c": 1, "b.d": 1, c: 1});
|
let resultDoc = coll.findOne({a: 1}, {_id: 0, a: 1, "b.c": 1, "b.d": 1, c: 1});
|
||||||
assert.eq(resultDoc, {a: 1, b: {c: 1, d: 1}, c: 1});
|
assert.eq(resultDoc, {a: 1, b: {c: 1, d: 1}, c: 1});
|
||||||
let explain = coll.find({a: 1}, {_id: 0, a: 1, "b.c": 1, "b.d": 1, c: 1}).explain("queryPlanner");
|
let explain = coll.find({a: 1}, {_id: 0, a: 1, "b.c": 1, "b.d": 1, c: 1}).explain("queryPlanner");
|
||||||
assert(isIxscan(db, getWinningPlan(explain.queryPlanner)), explain);
|
|
||||||
assert(isIndexOnly(db, getWinningPlan(explain.queryPlanner)), explain);
|
switch (getOptimizer(explain)) {
|
||||||
|
case "classic": {
|
||||||
|
assert(isIxscan(db, getWinningPlan(explain.queryPlanner)), explain);
|
||||||
|
assert(isIndexOnly(db, getWinningPlan(explain.queryPlanner)), explain);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "CQF":
|
||||||
|
// TODO SERVER-77719: Ensure that the decision for using the scan lines up with CQF
|
||||||
|
// optimizer. M2: allow only collscans, M4: check bonsai behavior for index scan.
|
||||||
|
assert(isCollscan(db, explain));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Project a subset of the indexed fields. Verify that the projection is computed correctly and
|
// Project a subset of the indexed fields. Verify that the projection is computed correctly and
|
||||||
// that the plan is covered.
|
// that the plan is covered.
|
||||||
resultDoc = coll.findOne({a: 1}, {_id: 0, "b.c": 1, c: 1});
|
resultDoc = coll.findOne({a: 1}, {_id: 0, "b.c": 1, c: 1});
|
||||||
assert.eq(resultDoc, {b: {c: 1}, c: 1});
|
assert.eq(resultDoc, {b: {c: 1}, c: 1});
|
||||||
explain = coll.find({a: 1}, {_id: 0, "b.c": 1, c: 1}).explain("queryPlanner");
|
explain = coll.find({a: 1}, {_id: 0, "b.c": 1, c: 1}).explain("queryPlanner");
|
||||||
assert(isIxscan(db, getWinningPlan(explain.queryPlanner)));
|
|
||||||
assert(isIndexOnly(db, getWinningPlan(explain.queryPlanner)));
|
switch (getOptimizer(explain)) {
|
||||||
|
case "classic": {
|
||||||
|
assert(isIxscan(db, getWinningPlan(explain.queryPlanner)));
|
||||||
|
assert(isIndexOnly(db, getWinningPlan(explain.queryPlanner)));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "CQF":
|
||||||
|
// TODO SERVER-77719: Ensure that the decision for using the scan lines up with CQF
|
||||||
|
// optimizer. M2: allow only collscans, M4: check bonsai behavior for index scan.
|
||||||
|
assert(isCollscan(db, explain));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Project exactly the set of fields in the index but also include _id. Verify that the
|
// Project exactly the set of fields in the index but also include _id. Verify that the
|
||||||
// projection is computed correctly and that the plan cannot be covered.
|
// projection is computed correctly and that the plan cannot be covered.
|
||||||
|
|
@ -38,30 +67,72 @@ resultDoc = coll.findOne({a: 1}, {_id: 1, a: 1, "b.c": 1, "b.d": 1, c: 1});
|
||||||
assert.docEq({_id: 1, a: 1, b: {c: 1, d: 1}, c: 1}, resultDoc);
|
assert.docEq({_id: 1, a: 1, b: {c: 1, d: 1}, c: 1}, resultDoc);
|
||||||
explain = coll.find({a: 1}, {_id: 0, "b.c": 1, c: 1}).explain("queryPlanner");
|
explain = coll.find({a: 1}, {_id: 0, "b.c": 1, c: 1}).explain("queryPlanner");
|
||||||
explain = coll.find({a: 1}, {_id: 1, a: 1, "b.c": 1, "b.d": 1, c: 1}).explain("queryPlanner");
|
explain = coll.find({a: 1}, {_id: 1, a: 1, "b.c": 1, "b.d": 1, c: 1}).explain("queryPlanner");
|
||||||
assert(isIxscan(db, getWinningPlan(explain.queryPlanner)));
|
|
||||||
assert(!isIndexOnly(db, getWinningPlan(explain.queryPlanner)));
|
switch (getOptimizer(explain)) {
|
||||||
|
case "classic": {
|
||||||
|
assert(isIxscan(db, getWinningPlan(explain.queryPlanner)));
|
||||||
|
assert(!isIndexOnly(db, getWinningPlan(explain.queryPlanner)));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "CQF":
|
||||||
|
// TODO SERVER-77719: Ensure that the decision for using the scan lines up with CQF
|
||||||
|
// optimizer. M2: allow only collscans, M4: check bonsai behavior for index scan.
|
||||||
|
assert(isCollscan(db, explain));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Project a not-indexed field that exists in the collection. The plan should not be covered.
|
// Project a not-indexed field that exists in the collection. The plan should not be covered.
|
||||||
resultDoc = coll.findOne({a: 1}, {_id: 0, "b.c": 1, "b.e": 1, c: 1});
|
resultDoc = coll.findOne({a: 1}, {_id: 0, "b.c": 1, "b.e": 1, c: 1});
|
||||||
assert.docEq({b: {c: 1, e: 1}, c: 1}, resultDoc);
|
assert.docEq({b: {c: 1, e: 1}, c: 1}, resultDoc);
|
||||||
explain = coll.find({a: 1}, {_id: 0, "b.c": 1, "b.e": 1, c: 1}).explain("queryPlanner");
|
explain = coll.find({a: 1}, {_id: 0, "b.c": 1, "b.e": 1, c: 1}).explain("queryPlanner");
|
||||||
assert(isIxscan(db, getWinningPlan(explain.queryPlanner)));
|
|
||||||
assert(!isIndexOnly(db, getWinningPlan(explain.queryPlanner)));
|
switch (getOptimizer(explain)) {
|
||||||
|
case "classic": {
|
||||||
|
assert(isIxscan(db, getWinningPlan(explain.queryPlanner)));
|
||||||
|
assert(!isIndexOnly(db, getWinningPlan(explain.queryPlanner)));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "CQF":
|
||||||
|
// TODO SERVER-77719: Ensure that the decision for using the scan lines up with CQF
|
||||||
|
// optimizer. M2: allow only collscans, M4: check bonsai behavior for index scan.
|
||||||
|
assert(isCollscan(db, explain));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Project a not-indexed field that does not exist in the collection. The plan should not be
|
// Project a not-indexed field that does not exist in the collection. The plan should not be
|
||||||
// covered.
|
// covered.
|
||||||
resultDoc = coll.findOne({a: 1}, {_id: 0, "b.c": 1, "b.z": 1, c: 1});
|
resultDoc = coll.findOne({a: 1}, {_id: 0, "b.c": 1, "b.z": 1, c: 1});
|
||||||
assert.docEq({b: {c: 1}, c: 1}, resultDoc);
|
assert.docEq({b: {c: 1}, c: 1}, resultDoc);
|
||||||
explain = coll.find({a: 1}, {_id: 0, "b.c": 1, "b.z": 1, c: 1}).explain("queryPlanner");
|
explain = coll.find({a: 1}, {_id: 0, "b.c": 1, "b.z": 1, c: 1}).explain("queryPlanner");
|
||||||
assert(isIxscan(db, getWinningPlan(explain.queryPlanner)));
|
|
||||||
assert(!isIndexOnly(db, getWinningPlan(explain.queryPlanner)));
|
switch (getOptimizer(explain)) {
|
||||||
|
case "classic": {
|
||||||
|
assert(isIxscan(db, getWinningPlan(explain.queryPlanner)));
|
||||||
|
assert(!isIndexOnly(db, getWinningPlan(explain.queryPlanner)));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "CQF":
|
||||||
|
// TODO SERVER-77719: Ensure that the decision for using the scan lines up with CQF
|
||||||
|
// optimizer. M2: allow only collscans, M4: check bonsai behavior for index scan.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Verify that the correct projection is computed with an idhack query.
|
// Verify that the correct projection is computed with an idhack query.
|
||||||
resultDoc = coll.findOne({_id: 1}, {_id: 0, "b.c": 1, "b.e": 1, c: 1});
|
resultDoc = coll.findOne({_id: 1}, {_id: 0, "b.c": 1, "b.e": 1, c: 1});
|
||||||
assert.docEq({b: {c: 1, e: 1}, c: 1}, resultDoc);
|
assert.docEq({b: {c: 1, e: 1}, c: 1}, resultDoc);
|
||||||
explain = coll.find({_id: 1}, {_id: 0, "b.c": 1, "b.e": 1, c: 1}).explain("queryPlanner");
|
explain = coll.find({_id: 1}, {_id: 0, "b.c": 1, "b.e": 1, c: 1}).explain("queryPlanner");
|
||||||
|
|
||||||
assert(isIdhack(db, getWinningPlan(explain.queryPlanner)), explain);
|
switch (getOptimizer(explain)) {
|
||||||
|
case "classic": {
|
||||||
|
assert(isIdhack(db, getWinningPlan(explain.queryPlanner)), explain);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "CQF":
|
||||||
|
// TODO SERVER-70847, how to recognize the case of an IDHACK for Bonsai?
|
||||||
|
// TODO SERVER-77719: Ensure that the decision for using the scan lines up with CQF
|
||||||
|
// optimizer. M2: allow only collscans, M4: check bonsai behavior for index scan.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// If we make a dotted path multikey, projections using that path cannot be covered. But
|
// If we make a dotted path multikey, projections using that path cannot be covered. But
|
||||||
// projections which do not include the multikey path can still be covered.
|
// projections which do not include the multikey path can still be covered.
|
||||||
|
|
@ -70,15 +141,37 @@ assert.commandWorked(coll.insert({a: 2, b: {c: 1, d: [1, 2, 3]}}));
|
||||||
resultDoc = coll.findOne({a: 2}, {_id: 0, "b.c": 1, "b.d": 1});
|
resultDoc = coll.findOne({a: 2}, {_id: 0, "b.c": 1, "b.d": 1});
|
||||||
assert.eq(resultDoc, {b: {c: 1, d: [1, 2, 3]}});
|
assert.eq(resultDoc, {b: {c: 1, d: [1, 2, 3]}});
|
||||||
explain = coll.find({a: 2}, {_id: 0, "b.c": 1, "b.d": 1}).explain("queryPlanner");
|
explain = coll.find({a: 2}, {_id: 0, "b.c": 1, "b.d": 1}).explain("queryPlanner");
|
||||||
assert(isIxscan(db, getWinningPlan(explain.queryPlanner)));
|
|
||||||
assert(!isIndexOnly(db, getWinningPlan(explain.queryPlanner)));
|
switch (getOptimizer(explain)) {
|
||||||
|
case "classic": {
|
||||||
|
assert(isIxscan(db, getWinningPlan(explain.queryPlanner)));
|
||||||
|
assert(!isIndexOnly(db, getWinningPlan(explain.queryPlanner)));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "CQF":
|
||||||
|
// TODO SERVER-77719: Ensure that the decision for using the scan lines up with CQF
|
||||||
|
// optimizer. M2: allow only collscans, M4: check bonsai behavior for index scan.
|
||||||
|
assert(isCollscan(db, explain));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
resultDoc = coll.findOne({a: 2}, {_id: 0, "b.c": 1});
|
resultDoc = coll.findOne({a: 2}, {_id: 0, "b.c": 1});
|
||||||
assert.eq(resultDoc, {b: {c: 1}});
|
assert.eq(resultDoc, {b: {c: 1}});
|
||||||
explain = coll.find({a: 2}, {_id: 0, "b.c": 1}).explain("queryPlanner");
|
explain = coll.find({a: 2}, {_id: 0, "b.c": 1}).explain("queryPlanner");
|
||||||
assert(isIxscan(db, getWinningPlan(explain.queryPlanner)));
|
|
||||||
// Path-level multikey info allows for generating a covered plan.
|
switch (getOptimizer(explain)) {
|
||||||
assert(isIndexOnly(db, getWinningPlan(explain.queryPlanner)));
|
case "classic": {
|
||||||
|
assert(isIxscan(db, getWinningPlan(explain.queryPlanner)));
|
||||||
|
// Path-level multikey info allows for generating a covered plan.
|
||||||
|
assert(isIndexOnly(db, getWinningPlan(explain.queryPlanner)));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "CQF":
|
||||||
|
// TODO SERVER-77719: Ensure that the decision for using the scan lines up with CQF
|
||||||
|
// optimizer. M2: allow only collscans, M4: check bonsai behavior for index scan.
|
||||||
|
assert(isCollscan(db, explain));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Verify that dotted projections work for multiple levels of nesting.
|
// Verify that dotted projections work for multiple levels of nesting.
|
||||||
assert.commandWorked(coll.createIndex({a: 1, "x.y.y": 1, "x.y.z": 1, "x.z": 1}));
|
assert.commandWorked(coll.createIndex({a: 1, "x.y.y": 1, "x.y.z": 1, "x.z": 1}));
|
||||||
|
|
@ -86,8 +179,18 @@ assert.commandWorked(coll.insert({a: 3, x: {y: {y: 1, f: 1, z: 1}, f: 1, z: 1}})
|
||||||
resultDoc = coll.findOne({a: 3}, {_id: 0, "x.y.y": 1, "x.y.z": 1, "x.z": 1});
|
resultDoc = coll.findOne({a: 3}, {_id: 0, "x.y.y": 1, "x.y.z": 1, "x.z": 1});
|
||||||
assert.eq(resultDoc, {x: {y: {y: 1, z: 1}, z: 1}});
|
assert.eq(resultDoc, {x: {y: {y: 1, z: 1}, z: 1}});
|
||||||
explain = coll.find({a: 3}, {_id: 0, "x.y.y": 1, "x.y.z": 1, "x.z": 1}).explain("queryPlanner");
|
explain = coll.find({a: 3}, {_id: 0, "x.y.y": 1, "x.y.z": 1, "x.z": 1}).explain("queryPlanner");
|
||||||
assert(isIxscan(db, getWinningPlan(explain.queryPlanner)));
|
|
||||||
assert(isIndexOnly(db, getWinningPlan(explain.queryPlanner)));
|
switch (getOptimizer(explain)) {
|
||||||
|
case "classic": {
|
||||||
|
assert(isIxscan(db, getWinningPlan(explain.queryPlanner)));
|
||||||
|
assert(isIndexOnly(db, getWinningPlan(explain.queryPlanner)));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "CQF":
|
||||||
|
// TODO SERVER-77719: Ensure that the decision for using the scan lines up with CQF
|
||||||
|
// optimizer. M2: allow only collscans, M4: check bonsai behavior for index scan.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// If projected nested paths do not exist in the indexed document, then they will get filled in
|
// If projected nested paths do not exist in the indexed document, then they will get filled in
|
||||||
// with nulls. This is a bug tracked by SERVER-23229.
|
// with nulls. This is a bug tracked by SERVER-23229.
|
||||||
|
|
@ -99,8 +202,19 @@ assert.eq(resultDoc, {x: {y: {y: null, z: null}, z: null}});
|
||||||
assert.commandWorked(coll.createIndex({"a.b.c": 1, "a.b": 1}));
|
assert.commandWorked(coll.createIndex({"a.b.c": 1, "a.b": 1}));
|
||||||
assert.commandWorked(coll.insert({a: {b: {c: 1, d: 1}}}));
|
assert.commandWorked(coll.insert({a: {b: {c: 1, d: 1}}}));
|
||||||
explain = coll.find({"a.b.c": 1}, {_id: 0, "a.b": 1}).explain();
|
explain = coll.find({"a.b.c": 1}, {_id: 0, "a.b": 1}).explain();
|
||||||
assert(isIxscan(db, getWinningPlan(explain.queryPlanner)));
|
|
||||||
assert(isIndexOnly(db, getWinningPlan(explain.queryPlanner)));
|
switch (getOptimizer(explain)) {
|
||||||
|
case "classic": {
|
||||||
|
assert(isIxscan(db, getWinningPlan(explain.queryPlanner)));
|
||||||
|
assert(isIndexOnly(db, getWinningPlan(explain.queryPlanner)));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "CQF":
|
||||||
|
// TODO SERVER-77719: Ensure that the decision for using the scan lines up with CQF
|
||||||
|
// optimizer. M2: allow only collscans, M4: check bonsai behavior for index scan.
|
||||||
|
assert(isCollscan(db, explain));
|
||||||
|
break;
|
||||||
|
}
|
||||||
assert.eq(coll.findOne({"a.b.c": 1}, {_id: 0, "a.b": 1}), {a: {b: {c: 1, d: 1}}});
|
assert.eq(coll.findOne({"a.b.c": 1}, {_id: 0, "a.b": 1}), {a: {b: {c: 1, d: 1}}});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -112,8 +226,19 @@ assert.eq(resultDoc, {x: {y: {y: null, z: null}, z: null}});
|
||||||
|
|
||||||
const filter = {"a.b": {c: 1, d: 1}};
|
const filter = {"a.b": {c: 1, d: 1}};
|
||||||
explain = coll.find(filter, {_id: 0, "a.b": 1}).explain();
|
explain = coll.find(filter, {_id: 0, "a.b": 1}).explain();
|
||||||
assert(isIxscan(db, getWinningPlan(explain.queryPlanner)));
|
|
||||||
assert(isIndexOnly(db, getWinningPlan(explain.queryPlanner)));
|
switch (getOptimizer(explain)) {
|
||||||
|
case "classic": {
|
||||||
|
assert(isIxscan(db, getWinningPlan(explain.queryPlanner)));
|
||||||
|
assert(isIndexOnly(db, getWinningPlan(explain.queryPlanner)));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "CQF":
|
||||||
|
// TODO SERVER-77719: Ensure that the decision for using the scan lines up with CQF
|
||||||
|
// optimizer. M2: allow only collscans, M4: check bonsai behavior for index scan.
|
||||||
|
assert(isCollscan(db, explain));
|
||||||
|
break;
|
||||||
|
}
|
||||||
assert.eq(coll.findOne(filter, {_id: 0, "a.b": 1}), {a: {b: {c: 1, d: 1}}});
|
assert.eq(coll.findOne(filter, {_id: 0, "a.b": 1}), {a: {b: {c: 1, d: 1}}});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
* assumes_no_implicit_index_creation,
|
* assumes_no_implicit_index_creation,
|
||||||
* ]
|
* ]
|
||||||
*/
|
*/
|
||||||
|
import {getOptimizer} from "jstests/libs/analyze_plan.js";
|
||||||
import {FixtureHelpers} from "jstests/libs/fixture_helpers.js";
|
import {FixtureHelpers} from "jstests/libs/fixture_helpers.js";
|
||||||
import {checkSBEEnabled} from "jstests/libs/sbe_util.js";
|
import {checkSBEEnabled} from "jstests/libs/sbe_util.js";
|
||||||
|
|
||||||
|
|
@ -41,6 +42,12 @@ let assertPlanCacheField = function(
|
||||||
tojson(firstExplain) + " with " + tojson(secondExplain));
|
tojson(firstExplain) + " with " + tojson(secondExplain));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO SERVER-77719: Ensure that the test is valid for different combinations of optimizer used
|
||||||
|
// for with/without index cases.
|
||||||
|
if (!(getOptimizer(firstExplain) == getOptimizer(secondExplain))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// SERVER-56980: When running in a sharded environment, we group the values for 'planCacheField'
|
// SERVER-56980: When running in a sharded environment, we group the values for 'planCacheField'
|
||||||
// by shard. This is because in a multi-version environment, we want to ensure that we are
|
// by shard. This is because in a multi-version environment, we want to ensure that we are
|
||||||
// comparing the results produced by the same shard in the event that the planCacheKey format
|
// comparing the results produced by the same shard in the event that the planCacheKey format
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
*/
|
*/
|
||||||
import {
|
import {
|
||||||
getExecutionStages,
|
getExecutionStages,
|
||||||
|
getOptimizer,
|
||||||
getPlanStages,
|
getPlanStages,
|
||||||
getRejectedPlan,
|
getRejectedPlan,
|
||||||
getRejectedPlans,
|
getRejectedPlans,
|
||||||
|
|
@ -38,18 +39,27 @@ const a1IndexName = "a_1";
|
||||||
const b1IndexName = "b_1";
|
const b1IndexName = "b_1";
|
||||||
const explain = coll.find({a: 7, b: 9}).explain("executionStats");
|
const explain = coll.find({a: 7, b: 9}).explain("executionStats");
|
||||||
|
|
||||||
// Verify that the winner plan has index scan stage on 'a_1_b_1'.
|
switch (getOptimizer(explain)) {
|
||||||
let ixscans = getPlanStages(getWinningPlan(explain.queryPlanner), "IXSCAN");
|
case "classic": {
|
||||||
assert.neq(ixscans.length, 0, explain);
|
// Verify that the winner plan has index scan stage on 'a_1_b_1'.
|
||||||
for (let ixscan of ixscans) {
|
let ixscans = getPlanStages(getWinningPlan(explain.queryPlanner), "IXSCAN");
|
||||||
assert.eq(ixscan.indexName, a1b1IndexName, explain);
|
assert.neq(ixscans.length, 0, explain);
|
||||||
|
for (let ixscan of ixscans) {
|
||||||
|
assert.eq(ixscan.indexName, a1b1IndexName, explain);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "CQF":
|
||||||
|
// TODO SERVER-77719: Ensure that the decision for using the scan lines up with CQF
|
||||||
|
// optimizer. M2: allow only collscans, M4: check bonsai behavior for index scan.
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify that the winning SBE plan has index scan stage on 'a_1_b_1'.
|
// Verify that the winning SBE plan has index scan stage on 'a_1_b_1'.
|
||||||
const executionStages = getExecutionStages(explain);
|
const executionStages = getExecutionStages(explain);
|
||||||
assert.neq(executionStages.length, 0, explain);
|
assert.neq(executionStages.length, 0, explain);
|
||||||
for (let executionStage of executionStages) {
|
for (let executionStage of executionStages) {
|
||||||
ixscans = getPlanStages(executionStage, "ixseek");
|
let ixscans = getPlanStages(executionStage, "ixseek");
|
||||||
assert.neq(ixscans.length, 0);
|
assert.neq(ixscans.length, 0);
|
||||||
for (let ixscan of ixscans) {
|
for (let ixscan of ixscans) {
|
||||||
assert.eq(ixscan.indexName, a1b1IndexName, ixscan);
|
assert.eq(ixscan.indexName, a1b1IndexName, ixscan);
|
||||||
|
|
@ -58,7 +68,9 @@ for (let executionStage of executionStages) {
|
||||||
|
|
||||||
// Verify that rejected plans should have index scan on 'a_1' or 'b_1'.
|
// Verify that rejected plans should have index scan on 'a_1' or 'b_1'.
|
||||||
for (let rejectedPlan of getRejectedPlans(explain)) {
|
for (let rejectedPlan of getRejectedPlans(explain)) {
|
||||||
ixscans = getPlanStages(getRejectedPlan(rejectedPlan), "IXSCAN");
|
let stages = {"classic": "IXSCAN", "CQF": "IndexScan"};
|
||||||
|
let optimizer = getOptimizer(explain);
|
||||||
|
let ixscans = getPlanStages(getRejectedPlan(rejectedPlan), stages[optimizer]);
|
||||||
assert.neq(ixscans.length, 0, explain);
|
assert.neq(ixscans.length, 0, explain);
|
||||||
for (let ixscan of ixscans) {
|
for (let ixscan of ixscans) {
|
||||||
assert.contains(ixscan.indexName, [a1IndexName, b1IndexName], ixscan);
|
assert.contains(ixscan.indexName, [a1IndexName, b1IndexName], ixscan);
|
||||||
|
|
|
||||||
|
|
@ -228,6 +228,8 @@ export function getRejectedPlans(root) {
|
||||||
export function hasRejectedPlans(root) {
|
export function hasRejectedPlans(root) {
|
||||||
function sectionHasRejectedPlans(explainSection, optimizer = "classic") {
|
function sectionHasRejectedPlans(explainSection, optimizer = "classic") {
|
||||||
if (optimizer == "CQF") {
|
if (optimizer == "CQF") {
|
||||||
|
// TODO SERVER-77719: The existence of alternative/rejected plans will be re-evaluated
|
||||||
|
// in the future.
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -255,6 +257,8 @@ export function hasRejectedPlans(root) {
|
||||||
return cursorStages.find((cursorStage) => cursorStageHasRejectedPlans(cursorStage)) !==
|
return cursorStages.find((cursorStage) => cursorStageHasRejectedPlans(cursorStage)) !==
|
||||||
undefined;
|
undefined;
|
||||||
} else {
|
} else {
|
||||||
|
let optimizer = getOptimizer(root);
|
||||||
|
|
||||||
// This is some sort of query explain.
|
// This is some sort of query explain.
|
||||||
assert(root.hasOwnProperty("queryPlanner"), tojson(root));
|
assert(root.hasOwnProperty("queryPlanner"), tojson(root));
|
||||||
assert(root.queryPlanner.hasOwnProperty("winningPlan"), tojson(root));
|
assert(root.queryPlanner.hasOwnProperty("winningPlan"), tojson(root));
|
||||||
|
|
@ -262,15 +266,22 @@ export function hasRejectedPlans(root) {
|
||||||
// SERVER-77719: Update regarding the expected behavior of the CQF optimizer. Currently
|
// SERVER-77719: Update regarding the expected behavior of the CQF optimizer. Currently
|
||||||
// CQF explains are empty, when the optimizer returns alternative plans, we should
|
// CQF explains are empty, when the optimizer returns alternative plans, we should
|
||||||
// address this.
|
// address this.
|
||||||
let optimizer = getOptimizer(root);
|
|
||||||
|
|
||||||
// This is an unsharded explain.
|
// This is an unsharded explain.
|
||||||
return sectionHasRejectedPlans(root.queryPlanner, optimizer);
|
return sectionHasRejectedPlans(root.queryPlanner, optimizer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ("SINGLE_SHARD" == root.queryPlanner.winningPlan.stage) {
|
||||||
|
var shards = root.queryPlanner.winningPlan.shards;
|
||||||
|
shards.forEach(function assertShardHasRejectedPlans(shard) {
|
||||||
|
sectionHasRejectedPlans(shard, optimizer);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// This is a sharded explain. Each entry in the shards array contains a 'winningPlan' and
|
// This is a sharded explain. Each entry in the shards array contains a 'winningPlan' and
|
||||||
// 'rejectedPlans'.
|
// 'rejectedPlans'.
|
||||||
return root.queryPlanner.winningPlan.shards.find(
|
return root.queryPlanner.winningPlan.shards.find(
|
||||||
(shard) => sectionHasRejectedPlans(shard)) !== undefined;
|
(shard) => sectionHasRejectedPlans(shard, optimizer)) !== undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -895,3 +906,14 @@ export function getNumberOfColumnScans(explain) {
|
||||||
const columnIndexScans = getPlanStages(getWinningPlan(explain.queryPlanner), stages[optimizer]);
|
const columnIndexScans = getPlanStages(getWinningPlan(explain.queryPlanner), stages[optimizer]);
|
||||||
return columnIndexScans.length;
|
return columnIndexScans.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns whether a query is using a multikey index.
|
||||||
|
*
|
||||||
|
* This helper function can be used only for "classic" optimizer.
|
||||||
|
*/
|
||||||
|
export function isIxscanMultikey(winningPlan) {
|
||||||
|
// SERVER-77719: Update to expected this method to allow also use with CQF optimizer.
|
||||||
|
let ixscanStage = getPlanStage(winningPlan, "IXSCAN");
|
||||||
|
return ixscanStage.isMultiKey;
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue