// static Status PlanCacheListPlans::list(const PlanCache& planCache, const std::string& ns, const BSONObj& cmdObj, BSONObjBuilder* bob) { CanonicalQuery* cqRaw; Status status = canonicalize(ns, cmdObj, &cqRaw); if (!status.isOK()) { return status; } scoped_ptr<CanonicalQuery> cq(cqRaw); CachedSolution* crRaw; Status result = planCache.get(*cq, &crRaw); if (!result.isOK()) { return result; } scoped_ptr<CachedSolution> cr(crRaw); BSONArrayBuilder plansBuilder(bob->subarrayStart("plans")); size_t numPlans = cr->plannerData.size(); for (size_t i = 0; i < numPlans; ++i) { BSONObjBuilder planBob(plansBuilder.subobjStart()); // Create plan details field. // Currently, simple string representationg of // SolutionCacheData. Need to revisit format when we // need to parse user-provided plan details for planCacheAddPlan. SolutionCacheData* scd = cr->plannerData[i]; BSONObjBuilder detailsBob(planBob.subobjStart("details")); detailsBob.append("solution", scd->toString()); detailsBob.doneFast(); // XXX: Fix these field values once we have fleshed out cache entries. // reason should contain initial plan stats and score from ranking process. // feedback should contain execution stats from running the query to completion. planBob.append("reason", BSONObj()); planBob.append("feedback", BSONObj()); planBob.append("hint", scd->adminHintApplied); } plansBuilder.doneFast(); return Status::OK(); }
// static Status PlanCacheListPlans::list(OperationContext* txn, const PlanCache& planCache, const std::string& ns, const BSONObj& cmdObj, BSONObjBuilder* bob) { CanonicalQuery* cqRaw; Status status = canonicalize(txn, ns, cmdObj, &cqRaw); if (!status.isOK()) { return status; } scoped_ptr<CanonicalQuery> cq(cqRaw); if (!planCache.contains(*cq)) { // Return empty plans in results if query shape does not // exist in plan cache. BSONArrayBuilder plansBuilder(bob->subarrayStart("plans")); plansBuilder.doneFast(); return Status::OK(); } PlanCacheEntry* entryRaw; Status result = planCache.getEntry(*cq, &entryRaw); if (!result.isOK()) { return result; } scoped_ptr<PlanCacheEntry> entry(entryRaw); BSONArrayBuilder plansBuilder(bob->subarrayStart("plans")); size_t numPlans = entry->plannerData.size(); invariant(numPlans == entry->decision->stats.size()); invariant(numPlans == entry->decision->scores.size()); for (size_t i = 0; i < numPlans; ++i) { BSONObjBuilder planBob(plansBuilder.subobjStart()); // Create plan details field. // Currently, simple string representationg of // SolutionCacheData. Need to revisit format when we // need to parse user-provided plan details for planCacheAddPlan. SolutionCacheData* scd = entry->plannerData[i]; BSONObjBuilder detailsBob(planBob.subobjStart("details")); detailsBob.append("solution", scd->toString()); detailsBob.doneFast(); // reason is comprised of score and initial stats provided by // multi plan runner. BSONObjBuilder reasonBob(planBob.subobjStart("reason")); reasonBob.append("score", entry->decision->scores[i]); BSONObjBuilder statsBob(reasonBob.subobjStart("stats")); PlanStageStats* stats = entry->decision->stats.vector()[i]; if (stats) { Explain::statsToBSON(*stats, &statsBob); } statsBob.doneFast(); reasonBob.doneFast(); // BSON object for 'feedback' field is created from query executions // and shows number of executions since this cached solution was // created as well as score data (average and standard deviation). BSONObjBuilder feedbackBob(planBob.subobjStart("feedback")); if (i == 0U) { feedbackBob.append("nfeedback", int(entry->feedback.size())); feedbackBob.append("averageScore", entry->averageScore.get_value_or(0)); feedbackBob.append("stdDevScore",entry->stddevScore.get_value_or(0)); BSONArrayBuilder scoresBob(feedbackBob.subarrayStart("scores")); for (size_t i = 0; i < entry->feedback.size(); ++i) { BSONObjBuilder scoreBob(scoresBob.subobjStart()); scoreBob.append("score", entry->feedback[i]->score); } scoresBob.doneFast(); } feedbackBob.doneFast(); planBob.append("filterSet", scd->indexFilterApplied); } plansBuilder.doneFast(); return Status::OK(); }
// static Status QueryPlanner::planFromCache(const CanonicalQuery& query, const QueryPlannerParams& params, const SolutionCacheData& cacheData, QuerySolution** out) { if (SolutionCacheData::WHOLE_IXSCAN_SOLN == cacheData.solnType) { // The solution can be constructed by a scan over the entire index. QuerySolution* soln = buildWholeIXSoln(*cacheData.tree->entry, query, params, cacheData.wholeIXSolnDir); if (soln == NULL) { return Status(ErrorCodes::BadValue, "plan cache error: soln that uses index to provide sort"); } else { *out = soln; return Status::OK(); } } else if (SolutionCacheData::COLLSCAN_SOLN == cacheData.solnType) { // The cached solution is a collection scan. We don't cache collscans // with tailable==true, hence the false below. QuerySolution* soln = buildCollscanSoln(query, false, params); if (soln == NULL) { return Status(ErrorCodes::BadValue, "plan cache error: collection scan soln"); } else { *out = soln; return Status::OK(); } } // SolutionCacheData::USE_TAGS_SOLN == cacheData->solnType // If we're here then this is neither the whole index scan or collection scan // cases, and we proceed by using the PlanCacheIndexTree to tag the query tree. // Create a copy of the expression tree. We use cachedSoln to annotate this with indices. MatchExpression* clone = query.root()->shallowClone(); QLOG() << "Tagging the match expression according to cache data: " << endl << "Filter:" << endl << clone->toString() << "Cache data:" << endl << cacheData.toString(); // Map from index name to index number. // TODO: can we assume that the index numbering has the same lifetime // as the cache state? map<BSONObj, size_t> indexMap; for (size_t i = 0; i < params.indices.size(); ++i) { const IndexEntry& ie = params.indices[i]; indexMap[ie.keyPattern] = i; QLOG() << "Index " << i << ": " << ie.keyPattern.toString() << endl; } Status s = tagAccordingToCache(clone, cacheData.tree.get(), indexMap); if (!s.isOK()) { return s; } // The planner requires a defined sort order. sortUsingTags(clone); QLOG() << "Tagged tree:" << endl << clone->toString(); // Use the cached index assignments to build solnRoot. Takes ownership of clone. QuerySolutionNode* solnRoot = QueryPlannerAccess::buildIndexedDataAccess(query, clone, false, params.indices); if (NULL != solnRoot) { // Takes ownership of 'solnRoot'. QuerySolution* soln = QueryPlannerAnalysis::analyzeDataAccess(query, params, solnRoot); if (NULL != soln) { QLOG() << "Planner: solution constructed from the cache:\n" << soln->toString() << endl; *out = soln; return Status::OK(); } } return Status(ErrorCodes::BadValue, "couldn't plan from cache"); }