Skip to content

Commit 1aea6fe

Browse files
mattsimplyEvergreen Agent
authored andcommitted
SERVER-83098: Make existing jstest work with the new CQF explain (core/query and core/sbe) (2)
1 parent 40c8c6e commit 1aea6fe

File tree

13 files changed

+414
-103
lines changed

13 files changed

+414
-103
lines changed

jstests/core/index/elemmatch_index.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
* assumes_read_concern_local,
77
* ]
88
*/
9-
10-
// adding temporary comment
119
import {getOptimizer, getWinningPlan, isIxscan} from "jstests/libs/analyze_plan.js";
1210

1311
const coll = db.elemMatch_index;

jstests/core/index/index_check7.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,3 @@ switch (getOptimizer(explainResult)) {
4444
default:
4545
break
4646
}
47-
48-
t.drop();

jstests/core/query/agg_hint.js

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
// where agg execution differs from query. It also includes confirmation that hint works for find
1212
// command against views, which is converted to a hinted aggregation on execution.
1313

14-
import {getAggPlanStage, getPlanStage} from "jstests/libs/analyze_plan.js";
14+
import {getAggPlanStage, getOptimizer, getPlanStage} from "jstests/libs/analyze_plan.js";
1515

1616
const testDB = db.getSiblingDB("agg_hint");
1717
assert.commandWorked(testDB.dropDatabase());
@@ -22,11 +22,27 @@ const view = testDB.getCollection(viewName);
2222

2323
function confirmWinningPlanUsesExpectedIndex(
2424
explainResult, expectedKeyPattern, stageName, pipelineOptimizedAway) {
25-
const planStage = pipelineOptimizedAway ? getPlanStage(explainResult, stageName)
26-
: getAggPlanStage(explainResult, stageName);
27-
assert.neq(null, planStage);
25+
const optimizer = getOptimizer(explainResult);
2826

29-
assert.eq(planStage.keyPattern, expectedKeyPattern, tojson(planStage));
27+
if (!(optimizer in stageName) || stageName[optimizer] === "") {
28+
// TODO SERVER-77719: Ensure that the expected operator is defined for all optimizers. There
29+
// should be an exception here.
30+
return;
31+
}
32+
33+
const planStage = pipelineOptimizedAway ? getPlanStage(explainResult, stageName[optimizer])
34+
: getAggPlanStage(explainResult, stageName[optimizer]);
35+
36+
switch (optimizer) {
37+
case "classic":
38+
assert.neq(null, planStage);
39+
assert.eq(planStage.keyPattern, expectedKeyPattern, tojson(planStage));
40+
break;
41+
case "CQF":
42+
// TODO SERVER-77719: Ensure that the decision for using the scan lines up with CQF
43+
// optimizer. M2: allow only collscans, M4: check bonsai behavior for index scan.
44+
break;
45+
}
3046
}
3147

3248
// Runs explain on 'command', with the hint specified by 'hintKeyPattern' when not null.
@@ -37,7 +53,10 @@ function confirmCommandUsesIndex({
3753
command = null,
3854
hintKeyPattern = null,
3955
expectedKeyPattern = null,
40-
stageName = "IXSCAN",
56+
stageName = {
57+
"classic": "IXSCAN",
58+
"CQF": "IndexScan"
59+
},
4160
pipelineOptimizedAway = false
4261
} = {}) {
4362
if (hintKeyPattern) {
@@ -60,7 +79,10 @@ function confirmAggUsesIndex({
6079
aggPipeline = [],
6180
hintKeyPattern = null,
6281
expectedKeyPattern = null,
63-
stageName = "IXSCAN",
82+
stageName = {
83+
"classic": "IXSCAN",
84+
"CQF": "IndexScan"
85+
},
6486
pipelineOptimizedAway = false
6587
} = {}) {
6688
let options = {};
@@ -248,19 +270,21 @@ for (let i = 0; i < 5; ++i) {
248270
}
249271
assert.commandWorked(testDB.createView(viewName, collName, []));
250272

273+
// TODO SERVER-77719: Ensure that the decision for using the scan lines up with CQF
274+
// optimizer. M2: allow only collscans, M4: check bonsai behavior for index scan.
251275
confirmCommandUsesIndex({
252276
command: {count: view.getName(), query: {x: 3}},
253277
expectedKeyPattern: {x: 1},
254-
stageName: "COUNT_SCAN"
278+
stageName: {"classic": "COUNT_SCAN", "CQF": ""}
255279
});
256280
confirmCommandUsesIndex({
257281
command: {count: view.getName(), query: {x: 3}},
258282
hintKeyPattern: {x: 1},
259283
expectedKeyPattern: {x: 1},
260-
stageName: "COUNT_SCAN"
284+
stageName: {"classic": "COUNT_SCAN", "CQF": ""}
261285
});
262286
confirmCommandUsesIndex({
263287
command: {count: view.getName(), query: {x: 3}},
264288
hintKeyPattern: {_id: 1},
265289
expectedKeyPattern: {_id: 1}
266-
});
290+
});

jstests/core/query/covered_multikey.js

Lines changed: 102 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,15 @@
99
* Test covering behavior for queries over a multikey index.
1010
*/
1111
// For making assertions about explain output.
12-
import {getPlanStage, getWinningPlan, isIxscan, planHasStage} from "jstests/libs/analyze_plan.js";
12+
import {
13+
getOptimizer,
14+
getPlanStage,
15+
getWinningPlan,
16+
isCollscan,
17+
isIxscan,
18+
isIxscanMultikey,
19+
planHasStage
20+
} from "jstests/libs/analyze_plan.js";
1321

1422
let coll = db.covered_multikey;
1523
coll.drop();
@@ -21,7 +29,18 @@ assert.eq(1, coll.find({a: 1, b: 2}, {_id: 0, a: 1}).itcount());
2129
assert.eq({a: 1}, coll.findOne({a: 1, b: 2}, {_id: 0, a: 1}));
2230
let explainRes = coll.explain("queryPlanner").find({a: 1, b: 2}, {_id: 0, a: 1}).finish();
2331
let winningPlan = getWinningPlan(explainRes.queryPlanner);
24-
assert(isIxscan(db, winningPlan));
32+
switch (getOptimizer(explainRes)) {
33+
case "classic": {
34+
assert(isIxscan(db, winningPlan));
35+
break;
36+
}
37+
case "CQF": {
38+
// TODO SERVER-77719: Ensure that the decision for using the scan lines up with CQF
39+
// optimizer. M2: allow only collscans, M4: check bonsai behavior for index scan.
40+
assert(isCollscan(db, winningPlan));
41+
break
42+
}
43+
}
2544
assert(!planHasStage(db, winningPlan, "FETCH"));
2645

2746
coll.drop();
@@ -47,10 +66,20 @@ assert.commandWorked(coll.createIndex({a: 1}));
4766
assert.eq({a: []}, coll.findOne({a: []}, {_id: 0, a: 1}));
4867
explainRes = coll.explain("queryPlanner").find({a: []}, {_id: 0, a: 1}).finish();
4968
winningPlan = getWinningPlan(explainRes.queryPlanner);
50-
assert(planHasStage(db, winningPlan, "IXSCAN"));
51-
assert(planHasStage(db, winningPlan, "FETCH"));
52-
let ixscanStage = getPlanStage(winningPlan, "IXSCAN");
53-
assert.eq(true, ixscanStage.isMultiKey);
69+
switch (getOptimizer(explainRes)) {
70+
case "classic": {
71+
assert(isIxscan(db, winningPlan));
72+
assert(planHasStage(db, winningPlan, "FETCH"));
73+
assert(isIxscanMultikey(winningPlan));
74+
break;
75+
}
76+
case "CQF": {
77+
// TODO SERVER-77719: Ensure that the decision for using the scan lines up with CQF
78+
// optimizer. M2: allow only collscans, M4: check bonsai behavior for index scan.
79+
assert(isCollscan(db, winningPlan));
80+
break
81+
}
82+
}
5483

5584
// Verify that a query cannot be covered over a path which is multikey due to a single-element
5685
// array.
@@ -60,10 +89,20 @@ assert.commandWorked(coll.createIndex({a: 1}));
6089
assert.eq({a: [2]}, coll.findOne({a: 2}, {_id: 0, a: 1}));
6190
explainRes = coll.explain("queryPlanner").find({a: 2}, {_id: 0, a: 1}).finish();
6291
winningPlan = getWinningPlan(explainRes.queryPlanner);
63-
assert(planHasStage(db, winningPlan, "IXSCAN"));
64-
assert(planHasStage(db, winningPlan, "FETCH"));
65-
ixscanStage = getPlanStage(winningPlan, "IXSCAN");
66-
assert.eq(true, ixscanStage.isMultiKey);
92+
switch (getOptimizer(explainRes)) {
93+
case "classic": {
94+
assert(isIxscan(db, winningPlan));
95+
assert(planHasStage(db, winningPlan, "FETCH"));
96+
assert(isIxscanMultikey(winningPlan));
97+
break;
98+
}
99+
case "CQF": {
100+
// TODO SERVER-77719: Ensure that the decision for using the scan lines up with CQF
101+
// optimizer. M2: allow only collscans, M4: check bonsai behavior for index scan.
102+
assert(isCollscan(db, winningPlan));
103+
break;
104+
}
105+
}
67106

68107
// Verify that a query cannot be covered over a path which is multikey due to a single-element
69108
// array, where the path is made multikey by an update rather than an insert.
@@ -74,43 +113,82 @@ assert.commandWorked(coll.update({}, {$set: {a: [2]}}));
74113
assert.eq({a: [2]}, coll.findOne({a: 2}, {_id: 0, a: 1}));
75114
explainRes = coll.explain("queryPlanner").find({a: 2}, {_id: 0, a: 1}).finish();
76115
winningPlan = getWinningPlan(explainRes.queryPlanner);
77-
assert(planHasStage(db, winningPlan, "IXSCAN"));
78-
assert(planHasStage(db, winningPlan, "FETCH"));
79-
ixscanStage = getPlanStage(winningPlan, "IXSCAN");
80-
assert.eq(true, ixscanStage.isMultiKey);
116+
switch (getOptimizer(explainRes)) {
117+
case "classic": {
118+
assert(isIxscan(db, winningPlan));
119+
assert(planHasStage(db, winningPlan, "FETCH"));
120+
assert(isIxscanMultikey(winningPlan));
121+
break;
122+
}
123+
case "CQF": {
124+
// TODO SERVER-77719: Ensure that the decision for using the scan lines up with CQF
125+
// optimizer. M2: allow only collscans, M4: check bonsai behavior for index scan.
126+
assert(isCollscan(db, winningPlan));
127+
break;
128+
}
129+
}
81130

82131
// Verify that a trailing empty array makes a 2dsphere index multikey.
83132
assert(coll.drop());
84133
assert.commandWorked(coll.createIndex({"a.b": 1, c: "2dsphere"}));
85134
assert.commandWorked(coll.insert({a: {b: 1}, c: {type: "Point", coordinates: [0, 0]}}));
86135
explainRes = coll.explain().find().hint({"a.b": 1, c: "2dsphere"}).finish();
87136
winningPlan = getWinningPlan(explainRes.queryPlanner);
88-
ixscanStage = getPlanStage(winningPlan, "IXSCAN");
137+
let optimizer = getOptimizer(explainRes);
138+
let stages = {"classic": "IXSCAN", "CQF": "IndexScan"};
139+
let ixscanStage = getPlanStage(winningPlan, stages[optimizer]);
89140
assert.neq(null, ixscanStage);
90141
assert.eq(false, ixscanStage.isMultiKey);
91142
assert.commandWorked(coll.insert({a: {b: []}, c: {type: "Point", coordinates: [0, 0]}}));
92143
explainRes = coll.explain().find().hint({"a.b": 1, c: "2dsphere"}).finish();
93144
winningPlan = getWinningPlan(explainRes.queryPlanner);
94-
ixscanStage = getPlanStage(winningPlan, "IXSCAN");
95-
assert.neq(null, ixscanStage);
96-
assert.eq(true, ixscanStage.isMultiKey);
145+
switch (getOptimizer(explainRes)) {
146+
case "classic": {
147+
assert(isIxscan(db, winningPlan));
148+
assert(isIxscanMultikey(winningPlan));
149+
break;
150+
}
151+
case "CQF": {
152+
// TODO SERVER-77719: Ensure that the decision for using the scan lines up with CQF
153+
// optimizer. M2: allow only collscans, M4: check bonsai behavior for index scan.
154+
break;
155+
}
156+
}
97157

98158
// Verify that a mid-path empty array makes a 2dsphere index multikey.
99159
assert(coll.drop());
100160
assert.commandWorked(coll.createIndex({"a.b": 1, c: "2dsphere"}));
101161
assert.commandWorked(coll.insert({a: [], c: {type: "Point", coordinates: [0, 0]}}));
102162
explainRes = coll.explain().find().hint({"a.b": 1, c: "2dsphere"}).finish();
103163
winningPlan = getWinningPlan(explainRes.queryPlanner);
104-
ixscanStage = getPlanStage(winningPlan, "IXSCAN");
105-
assert.neq(null, ixscanStage);
106-
assert.eq(true, ixscanStage.isMultiKey);
164+
switch (getOptimizer(explainRes)) {
165+
case "classic": {
166+
assert(isIxscan(db, winningPlan));
167+
assert(isIxscanMultikey(winningPlan));
168+
break;
169+
}
170+
case "CQF": {
171+
// TODO SERVER-77719: Ensure that the decision for using the scan lines up with CQF
172+
// optimizer. M2: allow only collscans, M4: check bonsai behavior for index scan.
173+
break;
174+
}
175+
}
107176

108177
// Verify that a single-element array makes a 2dsphere index multikey.
109178
assert(coll.drop());
110179
assert.commandWorked(coll.createIndex({"a.b": 1, c: "2dsphere"}));
111180
assert.commandWorked(coll.insert({a: {b: [3]}, c: {type: "Point", coordinates: [0, 0]}}));
112181
explainRes = coll.explain().find().hint({"a.b": 1, c: "2dsphere"}).finish();
113182
winningPlan = getWinningPlan(explainRes.queryPlanner);
114-
ixscanStage = getPlanStage(winningPlan, "IXSCAN");
115-
assert.neq(null, ixscanStage);
116-
assert.eq(true, ixscanStage.isMultiKey);
183+
switch (getOptimizer(explainRes)) {
184+
case "classic": {
185+
assert(isIxscan(db, winningPlan));
186+
assert(isIxscanMultikey(winningPlan));
187+
break;
188+
}
189+
case "CQF": {
190+
// TODO SERVER-77719: Ensure that the decision for using the scan lines up with CQF
191+
// optimizer. M2: allow only collscans, M4: check bonsai behavior for index scan.
192+
break;
193+
}
194+
}

jstests/core/query/explain/explain_multi_plan.js

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
// does_not_support_causal_consistency,
88
// ]
99

10+
import {hasRejectedPlans} from "jstests/libs/analyze_plan.js";
11+
1012
/**
1113
* Tests running explain on a variety of explainable commands (find, update, remove, etc.) when
1214
* there are multiple plans available. This is a regression test for SERVER-20849 and SERVER-21376.
@@ -55,25 +57,10 @@ assert.doesNotThrow(function() {
5557
coll.explain("allPlansExecution").distinct("a", {a: {$gte: 1}});
5658
});
5759

58-
// SERVER-21376: Make sure the 'rejectedPlans' field is filled in appropriately.
59-
function assertHasRejectedPlans(explainOutput) {
60-
var queryPlannerOutput = explainOutput.queryPlanner;
61-
62-
// The 'rejectedPlans' section will be in a different place if passed through a mongos.
63-
if ("SINGLE_SHARD" == queryPlannerOutput.winningPlan.stage) {
64-
var shards = queryPlannerOutput.winningPlan.shards;
65-
shards.forEach(function assertShardHasRejectedPlans(shard) {
66-
assert.gt(shard.rejectedPlans.length, 0);
67-
});
68-
} else {
69-
assert.gt(queryPlannerOutput.rejectedPlans.length, 0);
70-
}
71-
}
72-
7360
var res = coll.explain("queryPlanner").find({a: {$gte: 1}}).finish();
7461
assert.commandWorked(res);
75-
assertHasRejectedPlans(res);
62+
assert(hasRejectedPlans(res));
7663

7764
res = coll.explain("executionStats").find({a: {$gte: 1}}).finish();
7865
assert.commandWorked(res);
79-
assertHasRejectedPlans(res);
66+
assert(hasRejectedPlans(res));

jstests/core/query/explain/explain_winning_plan.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
// assumes_against_mongod_not_mongos,
99
// ]
1010

11+
import {getOptimizer} from "jstests/libs/analyze_plan.js";
12+
1113
const coll = db.explain_winning_plan;
1214
coll.drop();
1315

@@ -43,7 +45,16 @@ assert.eq(explain.executionStats.nReturned, numDocs);
4345
// representing two candidate plans evaluated by the multi-planner.
4446
assert(explain.executionStats.hasOwnProperty("allPlansExecution"), explain);
4547
assert(Array.isArray(explain.executionStats.allPlansExecution), explain);
46-
assert.eq(explain.executionStats.allPlansExecution.length, 2, explain);
48+
49+
switch (getOptimizer(explain)) {
50+
case "classic":
51+
assert.eq(explain.executionStats.allPlansExecution.length, 2, explain);
52+
break;
53+
case "CQF":
54+
// TODO SERVER-77719: Ensure that the decision for using the scan lines up with CQF
55+
// optimizer. M2: allow only collscans, M4: check bonsai behavior for index scan.
56+
break;
57+
}
4758

4859
// Each candidate plan should have returned exactly 'maxResults' number of documents during the
4960
// trial period.
@@ -60,4 +71,4 @@ assert(explainSinglePlan.executionStats.hasOwnProperty("allPlansExecution"), exp
6071
assert(Array.isArray(explainSinglePlan.executionStats.allPlansExecution), explainSinglePlan);
6172
assert.eq(explainSinglePlan.executionStats.allPlansExecution.length, 0, explainSinglePlan);
6273

63-
assert(coll.drop());
74+
assert(coll.drop());

0 commit comments

Comments
 (0)