…lines 1–230 unchanged (230 lines)
231 }
232 ASSERT_TRUE(strategy.getMultiPlanner().has_value());
233 auto stats = strategy.getMultiPlanner()->getSpecificStats();
234 ASSERT_TRUE(stats->earlyExit);
235 ASSERT_EQ(stats->numResultsFound, 0);
236 ASSERT_EQ(stats->numCandidatePlans, 2);
237 ASSERT_EQ(stats->totalWorks, 10001);
238 ASSERT_EQ(status.getValue().needsWorksMeasuredForPlanCache, true);
239
240 ASSERT_TRUE(status.getValue().execState);
241 auto mp = dynamic_cast<MultiPlanStage*>(status.getValue().execState->root.get());
242 ASSERT_TRUE(mp);
243 ASSERT_EQ(mp->getStats()->children.size(), 2); // One winning and one rejected plan
244}
245
246TEST_F(CBRForNoMPResultsTest, CBRCannotDecideUsesMultiPlanner) {
247 createIndexOnEmptyCollection(operationContext(), BSON("a" << 1), "a_1");
248 createIndexOnEmptyCollection(operationContext(), BSON("b" << 1), "b_1");
249 createIndexOnEmptyCollection(operationContext(),
250 BSON("c" << 1),
251 "c_1",
252 BSON("partialFilterExpression" << BSON("b" << GTE << 0)));
253
254 // Insert documents into the collection
255 // Note that this makes b_1 the CBR loser
256 {
257 std::vector<BSONObj> docs;
258 for (int i = 0; i < 1000; i++) {
259 BSONObj obj = BSON("_id" << i << "b" << i);
260 docs.push_back(obj);
261 }
262 for (int i = 0; i < 10001; i++) {
263 BSONObj obj = BSON("_id" << i + 1000 << "a" << i << "b" << i << "c" << i);
264 docs.push_back(obj);
265 }
266 insertDocuments(docs);
267 }
268
269
270 auto colls = getCollsAccessor();
271
272 // The query matches no documents so none of the plans will advance
273 // during the initial MP run, so the strategy will fall back to CBR.
274 // It will cost a_1 and b_1, and a_1 will win (because of the sequential sample).
275 // a_1 and c_1 will be multiplanned. Both will exit with neither EOF nor documents
276 // after 10000 works.
277 auto [cq, plannerData] = createCQAndPlannerData(
278 colls, BSON("a" << GT << 0 << "b" << GT << 0 << "c" << GT << 0 << "d" << 0));
279
280 plannerData.plannerParams =
281 makePlannerParams({.indices = indices,
282 .collStats = std::make_unique<stats::CollectionStatisticsImpl>(
283 static_cast<double>(5001), kNss)});
284
285 CBRForNoMPResultsStrategySpy strategy;
286 auto status = planAndRank(strategy, plannerData);
287 ASSERT_OK(status.getStatus());
288 ASSERT_EQ(status.getValue().solutions.size(), 1);
289 auto& explainData = status.getValue().maybeExplainData.value();
290 ASSERT_EQ(explainData.rejectedPlansWithStages.size(),
291 5); // 2 from multiplanner + 3 from CBR (we mark all plans considered by CBR as
292 // rejected if it cannot decide)
293 // The winning plan must have made it all the way to the end of the collection
294 ASSERT_EQ(explainData.multiPlannerWinningPlanTrialStats->common.works, 10000);
295 ASSERT_EQ(explainData.multiPlannerWinningPlanTrialStats->common.advanced, 0);
296 // There must be two (ixscan + fetch) CBR estimates for the two costable plans.
297 ASSERT_EQ(explainData.estimates.size(), 4);
298
299 // CBR winner should make it into the second runTrials
300 ASSERT_EQ(explainData.rejectedPlansWithStages[0].planStage->getStats()->common.works, 10000);
301 ASSERT_EQ(explainData.rejectedPlansWithStages[0].planStage->getStats()->common.advanced, 0);
302 // CBR loser should get abandoned
303 ASSERT_EQ(explainData.rejectedPlansWithStages[1].planStage->getStats()->common.works, 3333);
304 ASSERT_EQ(explainData.rejectedPlansWithStages[1].planStage->getStats()->common.advanced, 0);
305 const std::string cbrLoserPlanString =
306 explainData.rejectedPlansWithStages[1].solution->toString();
307 // Make sure this candidate actually was the CBR loser
308 ASSERT_NE(cbrLoserPlanString.find("b_1"), std::string::npos);
309 // The rest should be CBR rejections
310 ASSERT(!explainData.rejectedPlansWithStages[2].planStage);
311 ASSERT(!explainData.rejectedPlansWithStages[3].planStage);
312 ASSERT(!explainData.rejectedPlansWithStages[4].planStage);
313
314 ASSERT_TRUE(strategy.getMultiPlanner().has_value());
315 auto stats = strategy.getMultiPlanner()->getSpecificStats();
316 ASSERT_FALSE(stats->earlyExit);
317 ASSERT_EQ(stats->numResultsFound, 0);
318 ASSERT_EQ(stats->numCandidatePlans, 3);
319 ASSERT_EQ(stats->totalWorks, 23333); // 10000 + 10000 + 3333
320
321 ASSERT_TRUE(status.getValue().execState);
322}
323
324TEST_F(CBRForNoMPResultsTest, MPPicksBlockingSortAndEOFs) {
325 createIndexOnEmptyCollection(operationContext(), BSON("a" << 1), "a_1");
…lines 326–357 unchanged (32 lines)