PlanStage::StageState MultiPlanStage::work(WorkingSetID* out) { // Adds the amount of time taken by work() to executionTimeMillis. ScopedTimer timer(&_commonStats.executionTimeMillis); if (_failure) { *out = _statusMemberId; return PlanStage::FAILURE; } CandidatePlan& bestPlan = _candidates[_bestPlanIdx]; // Look for an already produced result that provides the data the caller wants. if (!bestPlan.results.empty()) { *out = bestPlan.results.front(); bestPlan.results.pop_front(); _commonStats.advanced++; return PlanStage::ADVANCED; } // best plan had no (or has no more) cached results StageState state = bestPlan.root->work(out); if (PlanStage::FAILURE == state && hasBackupPlan()) { QLOG() << "Best plan errored out switching to backup\n"; // Uncache the bad solution if we fall back // on the backup solution. // // XXX: Instead of uncaching we should find a way for the // cached plan runner to fall back on a different solution // if the best solution fails. Alternatively we could try to // defer cache insertion to be after the first produced result. _collection->infoCache()->getPlanCache()->remove(*_query); _bestPlanIdx = _backupPlanIdx; _backupPlanIdx = kNoSuchPlan; return _candidates[_bestPlanIdx].root->work(out); } if (hasBackupPlan() && PlanStage::ADVANCED == state) { QLOG() << "Best plan had a blocking stage, became unblocked\n"; _backupPlanIdx = kNoSuchPlan; } // Increment stats. if (PlanStage::ADVANCED == state) { _commonStats.advanced++; } else if (PlanStage::NEED_TIME == state) { _commonStats.needTime++; } return state; }
void run() { // 'a' is very selective, 'b' is not. for (int i = 0; i < N; ++i) { insert(BSON("a" << i << "b" << 1)); } // Add indices on 'a' and 'b'. addIndex(BSON("a" << 1)); addIndex(BSON("b" << 1)); // Run the query {a:1, b:{$gt:1}. CanonicalQuery* cq; verify(CanonicalQuery::canonicalize(ns, BSON("a" << 1 << "b" << BSON("$gt" << 1)), &cq).isOK()); ASSERT(NULL != cq); // Turn on the "force intersect" option. // This will be reverted by PlanRankingTestBase's destructor when the test completes. internalQueryForceIntersectionPlans = true; // Takes ownership of cq. QuerySolution* soln = pickBestPlan(cq); ASSERT(QueryPlannerTestLib::solutionMatches( "{fetch: {filter: null, node: {andHash: {nodes: [" "{ixscan: {filter: null, pattern: {a:1}}}," "{ixscan: {filter: null, pattern: {b:1}}}]}}}}", soln->root.get())); // Confirm that a backup plan is available. ASSERT(hasBackupPlan()); }
MultiPlanStage::~MultiPlanStage() { if (bestPlanChosen()) { delete _candidates[_bestPlanIdx].root; // for now, the runner that executes this multi-plan-stage wants to own // the query solution for the best plan. So we won't delete it here. // eventually, plan stages may own their query solutions. // // delete _candidates[_bestPlanIdx].solution; // (owned by containing runner) if (hasBackupPlan()) { delete _candidates[_backupPlanIdx].solution; delete _candidates[_backupPlanIdx].root; } // Clean up the losing candidates. clearCandidates(); } else { for (size_t ix = 0; ix < _candidates.size(); ++ix) { delete _candidates[ix].solution; delete _candidates[ix].root; } } for (vector<PlanStageStats*>::iterator it = _candidateStats.begin(); it != _candidateStats.end(); ++it) { delete *it; } }
PlanStageStats* MultiPlanStage::getStats() { if (bestPlanChosen()) { return _candidates[_bestPlanIdx].root->getStats(); } if (hasBackupPlan()) { return _candidates[_backupPlanIdx].root->getStats(); } _commonStats.isEOF = isEOF(); auto_ptr<PlanStageStats> ret(new PlanStageStats(_commonStats, STAGE_MULTI_PLAN)); return ret.release(); }
void MultiPlanStage::recoverFromYield() { if (_failure) return; // this logic is from multi_plan_runner // but does it really make sense to operate on // the _bestPlan if we've switched to the backup? if (bestPlanChosen()) { _candidates[_bestPlanIdx].root->recoverFromYield(); if (hasBackupPlan()) { _candidates[_backupPlanIdx].root->recoverFromYield(); } } else { allPlansRestoreState(); } }
void MultiPlanStage::invalidate(const DiskLoc& dl, InvalidationType type) { if (_failure) { return; } if (bestPlanChosen()) { CandidatePlan& bestPlan = _candidates[_bestPlanIdx]; bestPlan.root->invalidate(dl, type); invalidateHelper(bestPlan.ws, dl, &bestPlan.results, _collection); if (hasBackupPlan()) { CandidatePlan& backupPlan = _candidates[_backupPlanIdx]; backupPlan.root->invalidate(dl, type); invalidateHelper(backupPlan.ws, dl, &backupPlan.results, _collection); } } else { for (size_t ix = 0; ix < _candidates.size(); ++ix) { _candidates[ix].root->invalidate(dl, type); invalidateHelper(_candidates[ix].ws, dl, &_candidates[ix].results, _collection); } } }