// static Status QueryPlanner::tagAccordingToCache(MatchExpression* filter, const PlanCacheIndexTree* const indexTree, const map<BSONObj, size_t>& indexMap) { if (NULL == filter) { return Status(ErrorCodes::BadValue, "Cannot tag tree: filter is NULL."); } if (NULL == indexTree) { return Status(ErrorCodes::BadValue, "Cannot tag tree: indexTree is NULL."); } // We're tagging the tree here, so it shouldn't have // any tags hanging off yet. verify(NULL == filter->getTag()); if (filter->numChildren() != indexTree->children.size()) { mongoutils::str::stream ss; ss << "Cache topology and query did not match: " << "query has " << filter->numChildren() << " children " << "and cache has " << indexTree->children.size() << " children."; return Status(ErrorCodes::BadValue, ss); } // Continue the depth-first tree traversal. for (size_t i = 0; i < filter->numChildren(); ++i) { Status s = tagAccordingToCache(filter->getChild(i), indexTree->children[i], indexMap); if (!s.isOK()) { return s; } } if (NULL != indexTree->entry.get()) { map<BSONObj, size_t>::const_iterator got = indexMap.find(indexTree->entry->keyPattern); if (got == indexMap.end()) { mongoutils::str::stream ss; ss << "Did not find index with keyPattern: " << indexTree->entry->keyPattern.toString(); return Status(ErrorCodes::BadValue, ss); } filter->setTag(new IndexTag(got->second, indexTree->index_pos)); } return Status::OK(); }
StatusWith<std::unique_ptr<QuerySolution>> QueryPlanner::planFromCache( const CanonicalQuery& query, const QueryPlannerParams& params, const CachedSolution& cachedSoln) { invariant(!cachedSoln.plannerData.empty()); // A query not suitable for caching should not have made its way into the cache. invariant(PlanCache::shouldCacheQuery(query)); // Look up winning solution in cached solution's array. const SolutionCacheData& winnerCacheData = *cachedSoln.plannerData[0]; if (SolutionCacheData::WHOLE_IXSCAN_SOLN == winnerCacheData.solnType) { // The solution can be constructed by a scan over the entire index. auto soln = buildWholeIXSoln( *winnerCacheData.tree->entry, query, params, winnerCacheData.wholeIXSolnDir); if (!soln) { return Status(ErrorCodes::BadValue, "plan cache error: soln that uses index to provide sort"); } else { return {std::move(soln)}; } } else if (SolutionCacheData::COLLSCAN_SOLN == winnerCacheData.solnType) { // The cached solution is a collection scan. We don't cache collscans // with tailable==true, hence the false below. auto soln = buildCollscanSoln(query, false, params); if (!soln) { return Status(ErrorCodes::BadValue, "plan cache error: collection scan soln"); } else { return {std::move(soln)}; } } // 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. unique_ptr<MatchExpression> clone = query.root()->shallowClone(); LOG(5) << "Tagging the match expression according to cache data: " << endl << "Filter:" << endl << redact(clone->toString()) << "Cache data:" << endl << redact(winnerCacheData.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<StringData, size_t> indexMap; for (size_t i = 0; i < params.indices.size(); ++i) { const IndexEntry& ie = params.indices[i]; indexMap[ie.name] = i; LOG(5) << "Index " << i << ": " << ie.name; } Status s = tagAccordingToCache(clone.get(), winnerCacheData.tree.get(), indexMap); if (!s.isOK()) { return s; } // The MatchExpression tree is in canonical order. We must order the nodes for access planning. prepareForAccessPlanning(clone.get()); LOG(5) << "Tagged tree:" << endl << redact(clone->toString()); // Use the cached index assignments to build solnRoot. std::unique_ptr<QuerySolutionNode> solnRoot(QueryPlannerAccess::buildIndexedDataAccess( query, clone.release(), false, params.indices, params)); if (!solnRoot) { return Status(ErrorCodes::BadValue, str::stream() << "Failed to create data access plan from cache. Query: " << query.toStringShort()); } auto soln = QueryPlannerAnalysis::analyzeDataAccess(query, params, std::move(solnRoot)); if (!soln) { return Status(ErrorCodes::BadValue, str::stream() << "Failed to analyze plan from cache. Query: " << query.toStringShort()); } LOG(5) << "Planner: solution constructed from the cache:\n" << redact(soln->toString()); return {std::move(soln)}; }
// static Status QueryPlanner::tagAccordingToCache(MatchExpression* filter, const PlanCacheIndexTree* const indexTree, const map<StringData, size_t>& indexMap) { if (NULL == filter) { return Status(ErrorCodes::BadValue, "Cannot tag tree: filter is NULL."); } if (NULL == indexTree) { return Status(ErrorCodes::BadValue, "Cannot tag tree: indexTree is NULL."); } // We're tagging the tree here, so it shouldn't have // any tags hanging off yet. verify(NULL == filter->getTag()); if (filter->numChildren() != indexTree->children.size()) { mongoutils::str::stream ss; ss << "Cache topology and query did not match: " << "query has " << filter->numChildren() << " children " << "and cache has " << indexTree->children.size() << " children."; return Status(ErrorCodes::BadValue, ss); } // Continue the depth-first tree traversal. for (size_t i = 0; i < filter->numChildren(); ++i) { Status s = tagAccordingToCache(filter->getChild(i), indexTree->children[i], indexMap); if (!s.isOK()) { return s; } } if (!indexTree->orPushdowns.empty()) { filter->setTag(new OrPushdownTag()); OrPushdownTag* orPushdownTag = static_cast<OrPushdownTag*>(filter->getTag()); for (const auto& orPushdown : indexTree->orPushdowns) { auto index = indexMap.find(orPushdown.indexName); if (index == indexMap.end()) { return Status(ErrorCodes::BadValue, str::stream() << "Did not find index with name: " << orPushdown.indexName); } OrPushdownTag::Destination dest; dest.route = orPushdown.route; dest.tagData = stdx::make_unique<IndexTag>( index->second, orPushdown.position, orPushdown.canCombineBounds); orPushdownTag->addDestination(std::move(dest)); } } if (indexTree->entry.get()) { map<StringData, size_t>::const_iterator got = indexMap.find(indexTree->entry->name); if (got == indexMap.end()) { mongoutils::str::stream ss; ss << "Did not find index with name: " << indexTree->entry->name; return Status(ErrorCodes::BadValue, ss); } if (filter->getTag()) { OrPushdownTag* orPushdownTag = static_cast<OrPushdownTag*>(filter->getTag()); orPushdownTag->setIndexTag( new IndexTag(got->second, indexTree->index_pos, indexTree->canCombineBounds)); } else { filter->setTag( new IndexTag(got->second, indexTree->index_pos, indexTree->canCombineBounds)); } } 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"); }