diff options
Diffstat (limited to 'src/mongo/db/query/query_planner.cpp')
-rw-r--r-- | src/mongo/db/query/query_planner.cpp | 1457 |
1 files changed, 716 insertions, 741 deletions
diff --git a/src/mongo/db/query/query_planner.cpp b/src/mongo/db/query/query_planner.cpp index 852b705c532..6b98e0fae79 100644 --- a/src/mongo/db/query/query_planner.cpp +++ b/src/mongo/db/query/query_planner.cpp @@ -34,7 +34,7 @@ #include <vector> -#include "mongo/client/dbclientinterface.h" // For QueryOption_foobar +#include "mongo/client/dbclientinterface.h" // For QueryOption_foobar #include "mongo/db/matcher/expression_geo.h" #include "mongo/db/matcher/expression_text.h" #include "mongo/db/query/canonical_query.h" @@ -49,880 +49,855 @@ namespace mongo { - using std::unique_ptr; - using std::numeric_limits; - - // Copied verbatim from db/index.h - static bool isIdIndex( const BSONObj &pattern ) { - BSONObjIterator i(pattern); - BSONElement e = i.next(); - //_id index must have form exactly {_id : 1} or {_id : -1}. - //Allows an index of form {_id : "hashed"} to exist but - //do not consider it to be the primary _id index - if(! ( strcmp(e.fieldName(), "_id") == 0 - && (e.numberInt() == 1 || e.numberInt() == -1))) - return false; - return i.next().eoo(); - } +using std::unique_ptr; +using std::numeric_limits; + +// Copied verbatim from db/index.h +static bool isIdIndex(const BSONObj& pattern) { + BSONObjIterator i(pattern); + BSONElement e = i.next(); + //_id index must have form exactly {_id : 1} or {_id : -1}. + // Allows an index of form {_id : "hashed"} to exist but + // do not consider it to be the primary _id index + if (!(strcmp(e.fieldName(), "_id") == 0 && (e.numberInt() == 1 || e.numberInt() == -1))) + return false; + return i.next().eoo(); +} - static bool is2DIndex(const BSONObj& pattern) { - BSONObjIterator it(pattern); - while (it.more()) { - BSONElement e = it.next(); - if (String == e.type() && str::equals("2d", e.valuestr())) { - return true; - } +static bool is2DIndex(const BSONObj& pattern) { + BSONObjIterator it(pattern); + while (it.more()) { + BSONElement e = it.next(); + if (String == e.type() && str::equals("2d", e.valuestr())) { + return true; } - return false; } + return false; +} - string optionString(size_t options) { - mongoutils::str::stream ss; +string optionString(size_t options) { + mongoutils::str::stream ss; - // These options are all currently mutually exclusive. - if (QueryPlannerParams::DEFAULT == options) { - ss << "DEFAULT "; - } - if (options & QueryPlannerParams::NO_TABLE_SCAN) { - ss << "NO_TABLE_SCAN "; - } - if (options & QueryPlannerParams::INCLUDE_COLLSCAN) { - ss << "INCLUDE_COLLSCAN "; - } - if (options & QueryPlannerParams::INCLUDE_SHARD_FILTER) { - ss << "INCLUDE_SHARD_FILTER "; - } - if (options & QueryPlannerParams::NO_BLOCKING_SORT) { - ss << "NO_BLOCKING_SORT "; - } - if (options & QueryPlannerParams::INDEX_INTERSECTION) { - ss << "INDEX_INTERSECTION "; - } - if (options & QueryPlannerParams::KEEP_MUTATIONS) { - ss << "KEEP_MUTATIONS"; - } - - return ss; + // These options are all currently mutually exclusive. + if (QueryPlannerParams::DEFAULT == options) { + ss << "DEFAULT "; } - - static BSONObj getKeyFromQuery(const BSONObj& keyPattern, const BSONObj& query) { - return query.extractFieldsUnDotted(keyPattern); + if (options & QueryPlannerParams::NO_TABLE_SCAN) { + ss << "NO_TABLE_SCAN "; + } + if (options & QueryPlannerParams::INCLUDE_COLLSCAN) { + ss << "INCLUDE_COLLSCAN "; + } + if (options & QueryPlannerParams::INCLUDE_SHARD_FILTER) { + ss << "INCLUDE_SHARD_FILTER "; + } + if (options & QueryPlannerParams::NO_BLOCKING_SORT) { + ss << "NO_BLOCKING_SORT "; + } + if (options & QueryPlannerParams::INDEX_INTERSECTION) { + ss << "INDEX_INTERSECTION "; + } + if (options & QueryPlannerParams::KEEP_MUTATIONS) { + ss << "KEEP_MUTATIONS"; } - static bool indexCompatibleMaxMin(const BSONObj& obj, const BSONObj& keyPattern) { - BSONObjIterator kpIt(keyPattern); - BSONObjIterator objIt(obj); + return ss; +} - for (;;) { - // Every element up to this point has matched so the KP matches - if (!kpIt.more() && !objIt.more()) { - return true; - } +static BSONObj getKeyFromQuery(const BSONObj& keyPattern, const BSONObj& query) { + return query.extractFieldsUnDotted(keyPattern); +} - // If only one iterator is done, it's not a match. - if (!kpIt.more() || !objIt.more()) { - return false; - } +static bool indexCompatibleMaxMin(const BSONObj& obj, const BSONObj& keyPattern) { + BSONObjIterator kpIt(keyPattern); + BSONObjIterator objIt(obj); - // Field names must match and be in the same order. - BSONElement kpElt = kpIt.next(); - BSONElement objElt = objIt.next(); - if (!mongoutils::str::equals(kpElt.fieldName(), objElt.fieldName())) { - return false; - } + for (;;) { + // Every element up to this point has matched so the KP matches + if (!kpIt.more() && !objIt.more()) { + return true; } - } - static BSONObj stripFieldNames(const BSONObj& obj) { - BSONObjIterator it(obj); - BSONObjBuilder bob; - while (it.more()) { - bob.appendAs(it.next(), ""); - } - return bob.obj(); - } - - /** - * "Finishes" the min object for the $min query option by filling in an empty object with - * MinKey/MaxKey and stripping field names. - * - * In the case that 'minObj' is empty, we "finish" it by filling in either MinKey or MaxKey - * instead. Choosing whether to use MinKey or MaxKey is done by comparing against 'maxObj'. - * For instance, suppose 'minObj' is empty, 'maxObj' is { a: 3 }, and the key pattern is - * { a: -1 }. According to the key pattern ordering, { a: 3 } < MinKey. This means that the - * proper resulting bounds are - * - * start: { '': MaxKey }, end: { '': 3 } - * - * as opposed to - * - * start: { '': MinKey }, end: { '': 3 } - * - * Suppose instead that the key pattern is { a: 1 }, with the same 'minObj' and 'maxObj' - * (that is, an empty object and { a: 3 } respectively). In this case, { a: 3 } > MinKey, - * which means that we use range [{'': MinKey}, {'': 3}]. The proper 'minObj' in this case is - * MinKey, whereas in the previous example it was MaxKey. - * - * If 'minObj' is non-empty, then all we do is strip its field names (because index keys always - * have empty field names). - */ - static BSONObj finishMinObj(const BSONObj& kp, const BSONObj& minObj, const BSONObj& maxObj) { - BSONObjBuilder bob; - bob.appendMinKey(""); - BSONObj minKey = bob.obj(); - - if (minObj.isEmpty()) { - if (0 > minKey.woCompare(maxObj, kp, false)) { - BSONObjBuilder minKeyBuilder; - minKeyBuilder.appendMinKey(""); - return minKeyBuilder.obj(); - } - else { - BSONObjBuilder maxKeyBuilder; - maxKeyBuilder.appendMaxKey(""); - return maxKeyBuilder.obj(); - } - } - else { - return stripFieldNames(minObj); + // If only one iterator is done, it's not a match. + if (!kpIt.more() || !objIt.more()) { + return false; } - } - /** - * "Finishes" the max object for the $max query option by filling in an empty object with - * MinKey/MaxKey and stripping field names. - * - * See comment for finishMinObj() for why we need both 'minObj' and 'maxObj'. - */ - static BSONObj finishMaxObj(const BSONObj& kp, const BSONObj& minObj, const BSONObj& maxObj) { - BSONObjBuilder bob; - bob.appendMaxKey(""); - BSONObj maxKey = bob.obj(); - - if (maxObj.isEmpty()) { - if (0 < maxKey.woCompare(minObj, kp, false)) { - BSONObjBuilder maxKeyBuilder; - maxKeyBuilder.appendMaxKey(""); - return maxKeyBuilder.obj(); - } - else { - BSONObjBuilder minKeyBuilder; - minKeyBuilder.appendMinKey(""); - return minKeyBuilder.obj(); - } - } - else { - return stripFieldNames(maxObj); + // Field names must match and be in the same order. + BSONElement kpElt = kpIt.next(); + BSONElement objElt = objIt.next(); + if (!mongoutils::str::equals(kpElt.fieldName(), objElt.fieldName())) { + return false; } } +} - QuerySolution* buildCollscanSoln(const CanonicalQuery& query, - bool tailable, - const QueryPlannerParams& params) { - - QuerySolutionNode* solnRoot = QueryPlannerAccess::makeCollectionScan(query, tailable, params); - return QueryPlannerAnalysis::analyzeDataAccess(query, params, solnRoot); +static BSONObj stripFieldNames(const BSONObj& obj) { + BSONObjIterator it(obj); + BSONObjBuilder bob; + while (it.more()) { + bob.appendAs(it.next(), ""); } + return bob.obj(); +} - QuerySolution* buildWholeIXSoln(const IndexEntry& index, - const CanonicalQuery& query, - const QueryPlannerParams& params, - int direction = 1) { - - QuerySolutionNode* solnRoot = QueryPlannerAccess::scanWholeIndex(index, query, params, direction); - return QueryPlannerAnalysis::analyzeDataAccess(query, params, solnRoot); +/** + * "Finishes" the min object for the $min query option by filling in an empty object with + * MinKey/MaxKey and stripping field names. + * + * In the case that 'minObj' is empty, we "finish" it by filling in either MinKey or MaxKey + * instead. Choosing whether to use MinKey or MaxKey is done by comparing against 'maxObj'. + * For instance, suppose 'minObj' is empty, 'maxObj' is { a: 3 }, and the key pattern is + * { a: -1 }. According to the key pattern ordering, { a: 3 } < MinKey. This means that the + * proper resulting bounds are + * + * start: { '': MaxKey }, end: { '': 3 } + * + * as opposed to + * + * start: { '': MinKey }, end: { '': 3 } + * + * Suppose instead that the key pattern is { a: 1 }, with the same 'minObj' and 'maxObj' + * (that is, an empty object and { a: 3 } respectively). In this case, { a: 3 } > MinKey, + * which means that we use range [{'': MinKey}, {'': 3}]. The proper 'minObj' in this case is + * MinKey, whereas in the previous example it was MaxKey. + * + * If 'minObj' is non-empty, then all we do is strip its field names (because index keys always + * have empty field names). + */ +static BSONObj finishMinObj(const BSONObj& kp, const BSONObj& minObj, const BSONObj& maxObj) { + BSONObjBuilder bob; + bob.appendMinKey(""); + BSONObj minKey = bob.obj(); + + if (minObj.isEmpty()) { + if (0 > minKey.woCompare(maxObj, kp, false)) { + BSONObjBuilder minKeyBuilder; + minKeyBuilder.appendMinKey(""); + return minKeyBuilder.obj(); + } else { + BSONObjBuilder maxKeyBuilder; + maxKeyBuilder.appendMaxKey(""); + return maxKeyBuilder.obj(); + } + } else { + return stripFieldNames(minObj); } +} - bool providesSort(const CanonicalQuery& query, const BSONObj& kp) { - return query.getParsed().getSort().isPrefixOf(kp); +/** + * "Finishes" the max object for the $max query option by filling in an empty object with + * MinKey/MaxKey and stripping field names. + * + * See comment for finishMinObj() for why we need both 'minObj' and 'maxObj'. + */ +static BSONObj finishMaxObj(const BSONObj& kp, const BSONObj& minObj, const BSONObj& maxObj) { + BSONObjBuilder bob; + bob.appendMaxKey(""); + BSONObj maxKey = bob.obj(); + + if (maxObj.isEmpty()) { + if (0 < maxKey.woCompare(minObj, kp, false)) { + BSONObjBuilder maxKeyBuilder; + maxKeyBuilder.appendMaxKey(""); + return maxKeyBuilder.obj(); + } else { + BSONObjBuilder minKeyBuilder; + minKeyBuilder.appendMinKey(""); + return minKeyBuilder.obj(); + } + } else { + return stripFieldNames(maxObj); + } +} + +QuerySolution* buildCollscanSoln(const CanonicalQuery& query, + bool tailable, + const QueryPlannerParams& params) { + QuerySolutionNode* solnRoot = QueryPlannerAccess::makeCollectionScan(query, tailable, params); + return QueryPlannerAnalysis::analyzeDataAccess(query, params, solnRoot); +} + +QuerySolution* buildWholeIXSoln(const IndexEntry& index, + const CanonicalQuery& query, + const QueryPlannerParams& params, + int direction = 1) { + QuerySolutionNode* solnRoot = + QueryPlannerAccess::scanWholeIndex(index, query, params, direction); + return QueryPlannerAnalysis::analyzeDataAccess(query, params, solnRoot); +} + +bool providesSort(const CanonicalQuery& query, const BSONObj& kp) { + return query.getParsed().getSort().isPrefixOf(kp); +} + +// static +const int QueryPlanner::kPlannerVersion = 1; + +Status QueryPlanner::cacheDataFromTaggedTree(const MatchExpression* const taggedTree, + const vector<IndexEntry>& relevantIndices, + PlanCacheIndexTree** out) { + // On any early return, the out-parameter must contain NULL. + *out = NULL; + + if (NULL == taggedTree) { + return Status(ErrorCodes::BadValue, "Cannot produce cache data: tree is NULL."); } - // static - const int QueryPlanner::kPlannerVersion = 1; + unique_ptr<PlanCacheIndexTree> indexTree(new PlanCacheIndexTree()); - Status QueryPlanner::cacheDataFromTaggedTree(const MatchExpression* const taggedTree, - const vector<IndexEntry>& relevantIndices, - PlanCacheIndexTree** out) { - // On any early return, the out-parameter must contain NULL. - *out = NULL; + if (NULL != taggedTree->getTag()) { + IndexTag* itag = static_cast<IndexTag*>(taggedTree->getTag()); + if (itag->index >= relevantIndices.size()) { + mongoutils::str::stream ss; + ss << "Index number is " << itag->index << " but there are only " + << relevantIndices.size() << " relevant indices."; + return Status(ErrorCodes::BadValue, ss); + } - if (NULL == taggedTree) { - return Status(ErrorCodes::BadValue, "Cannot produce cache data: tree is NULL."); + // Make sure not to cache solutions which use '2d' indices. + // A 2d index that doesn't wrap on one query may wrap on another, so we have to + // check that the index is OK with the predicate. The only thing we have to do + // this for is 2d. For now it's easier to move ahead if we don't cache 2d. + // + // TODO: revisit with a post-cached-index-assignment compatibility check + if (is2DIndex(relevantIndices[itag->index].keyPattern)) { + return Status(ErrorCodes::BadValue, "can't cache '2d' index"); } - unique_ptr<PlanCacheIndexTree> indexTree(new PlanCacheIndexTree()); + IndexEntry* ientry = new IndexEntry(relevantIndices[itag->index]); + indexTree->entry.reset(ientry); + indexTree->index_pos = itag->pos; + } - if (NULL != taggedTree->getTag()) { - IndexTag* itag = static_cast<IndexTag*>(taggedTree->getTag()); - if (itag->index >= relevantIndices.size()) { - mongoutils::str::stream ss; - ss << "Index number is " << itag->index - << " but there are only " << relevantIndices.size() - << " relevant indices."; - return Status(ErrorCodes::BadValue, ss); - } + for (size_t i = 0; i < taggedTree->numChildren(); ++i) { + MatchExpression* taggedChild = taggedTree->getChild(i); + PlanCacheIndexTree* indexTreeChild; + Status s = cacheDataFromTaggedTree(taggedChild, relevantIndices, &indexTreeChild); + if (!s.isOK()) { + return s; + } + indexTree->children.push_back(indexTreeChild); + } - // Make sure not to cache solutions which use '2d' indices. - // A 2d index that doesn't wrap on one query may wrap on another, so we have to - // check that the index is OK with the predicate. The only thing we have to do - // this for is 2d. For now it's easier to move ahead if we don't cache 2d. - // - // TODO: revisit with a post-cached-index-assignment compatibility check - if (is2DIndex(relevantIndices[itag->index].keyPattern)) { - return Status(ErrorCodes::BadValue, "can't cache '2d' index"); - } + *out = indexTree.release(); + return Status::OK(); +} - IndexEntry* ientry = new IndexEntry(relevantIndices[itag->index]); - indexTree->entry.reset(ientry); - indexTree->index_pos = itag->pos; - } +// 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."); + } - for (size_t i = 0; i < taggedTree->numChildren(); ++i) { - MatchExpression* taggedChild = taggedTree->getChild(i); - PlanCacheIndexTree* indexTreeChild; - Status s = cacheDataFromTaggedTree(taggedChild, relevantIndices, &indexTreeChild); - if (!s.isOK()) { - return s; - } - indexTree->children.push_back(indexTreeChild); - } + // We're tagging the tree here, so it shouldn't have + // any tags hanging off yet. + verify(NULL == filter->getTag()); - *out = indexTree.release(); - return Status::OK(); + 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); } - // 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."); + // 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; } + } - // 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()) { + 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 << "Cache topology and query did not match: " - << "query has " << filter->numChildren() << " children " - << "and cache has " << indexTree->children.size() << " children."; + ss << "Did not find index with keyPattern: " << indexTree->entry->keyPattern.toString(); 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(); + filter->setTag(new IndexTag(got->second, indexTree->index_pos)); } - // static - Status QueryPlanner::planFromCache(const CanonicalQuery& query, - const QueryPlannerParams& params, - const CachedSolution& cachedSoln, - QuerySolution** out) { - invariant(!cachedSoln.plannerData.empty()); - invariant(out); + return Status::OK(); +} - // A query not suitable for caching should not have made its way into the cache. - invariant(PlanCache::shouldCacheQuery(query)); +// static +Status QueryPlanner::planFromCache(const CanonicalQuery& query, + const QueryPlannerParams& params, + const CachedSolution& cachedSoln, + QuerySolution** out) { + invariant(!cachedSoln.plannerData.empty()); + invariant(out); - // Look up winning solution in cached solution's array. - const SolutionCacheData& winnerCacheData = *cachedSoln.plannerData[0]; + // A query not suitable for caching should not have made its way into the cache. + invariant(PlanCache::shouldCacheQuery(query)); - if (SolutionCacheData::WHOLE_IXSCAN_SOLN == winnerCacheData.solnType) { - // The solution can be constructed by a scan over the entire index. - QuerySolution* soln = buildWholeIXSoln(*winnerCacheData.tree->entry, - query, - params, - winnerCacheData.wholeIXSolnDir); - if (soln == NULL) { - return Status(ErrorCodes::BadValue, - "plan cache error: soln that uses index to provide sort"); - } - else { - *out = soln; - return Status::OK(); - } + // 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. + QuerySolution* soln = buildWholeIXSoln( + *winnerCacheData.tree->entry, query, params, winnerCacheData.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 == winnerCacheData.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(); - } + } 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. + 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. + // 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(); + + LOG(5) << "Tagging the match expression according to cache data: " << endl + << "Filter:" << endl + << clone->toString() << "Cache data:" << endl + << 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<BSONObj, size_t> indexMap; + for (size_t i = 0; i < params.indices.size(); ++i) { + const IndexEntry& ie = params.indices[i]; + indexMap[ie.keyPattern] = i; + LOG(5) << "Index " << i << ": " << ie.keyPattern.toString() << endl; + } - // Create a copy of the expression tree. We use cachedSoln to annotate this with indices. - MatchExpression* clone = query.root()->shallowClone(); + Status s = tagAccordingToCache(clone, winnerCacheData.tree.get(), indexMap); + if (!s.isOK()) { + return s; + } - LOG(5) << "Tagging the match expression according to cache data: " << endl - << "Filter:" << endl << clone->toString() - << "Cache data:" << endl << winnerCacheData.toString(); + // The planner requires a defined sort order. + sortUsingTags(clone); - // 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; - LOG(5) << "Index " << i << ": " << ie.keyPattern.toString() << endl; - } + LOG(5) << "Tagged tree:" << endl + << clone->toString(); - Status s = tagAccordingToCache(clone, winnerCacheData.tree.get(), indexMap); - if (!s.isOK()) { - return s; - } + // Use the cached index assignments to build solnRoot. Takes ownership of clone. + QuerySolutionNode* solnRoot = + QueryPlannerAccess::buildIndexedDataAccess(query, clone, false, params.indices, params); - // The planner requires a defined sort order. - sortUsingTags(clone); + if (!solnRoot) { + return Status(ErrorCodes::BadValue, + str::stream() << "Failed to create data access plan from cache. Query: " + << query.toStringShort()); + } - LOG(5) << "Tagged tree:" << endl << clone->toString(); + // Takes ownership of 'solnRoot'. + QuerySolution* soln = QueryPlannerAnalysis::analyzeDataAccess(query, params, solnRoot); + if (!soln) { + return Status(ErrorCodes::BadValue, + str::stream() + << "Failed to analyze plan from cache. Query: " << query.toStringShort()); + } - // Use the cached index assignments to build solnRoot. Takes ownership of clone. - QuerySolutionNode* solnRoot = - QueryPlannerAccess::buildIndexedDataAccess(query, clone, false, params.indices, params); + LOG(5) << "Planner: solution constructed from the cache:\n" << soln->toString(); + *out = soln; + return Status::OK(); +} + +// static +Status QueryPlanner::plan(const CanonicalQuery& query, + const QueryPlannerParams& params, + std::vector<QuerySolution*>* out) { + LOG(5) << "Beginning planning..." << endl + << "=============================" << endl + << "Options = " << optionString(params.options) << endl + << "Canonical query:" << endl + << query.toString() << "=============================" << endl; + + for (size_t i = 0; i < params.indices.size(); ++i) { + LOG(5) << "Index " << i << " is " << params.indices[i].toString() << endl; + } - if (!solnRoot) { - return Status(ErrorCodes::BadValue, - str::stream() << "Failed to create data access plan from cache. Query: " - << query.toStringShort()); - } + bool canTableScan = !(params.options & QueryPlannerParams::NO_TABLE_SCAN); - // Takes ownership of 'solnRoot'. - QuerySolution* soln = QueryPlannerAnalysis::analyzeDataAccess(query, params, solnRoot); - if (!soln) { - return Status(ErrorCodes::BadValue, - str::stream() << "Failed to analyze plan from cache. Query: " - << query.toStringShort()); + // If the query requests a tailable cursor, the only solution is a collscan + filter with + // tailable set on the collscan. TODO: This is a policy departure. Previously I think you + // could ask for a tailable cursor and it just tried to give you one. Now, we fail if we + // can't provide one. Is this what we want? + if (query.getParsed().isTailable()) { + if (!QueryPlannerCommon::hasNode(query.root(), MatchExpression::GEO_NEAR) && canTableScan) { + QuerySolution* soln = buildCollscanSoln(query, true, params); + if (NULL != soln) { + out->push_back(soln); + } } - - LOG(5) << "Planner: solution constructed from the cache:\n" << soln->toString(); - *out = soln; return Status::OK(); } - // static - Status QueryPlanner::plan(const CanonicalQuery& query, - const QueryPlannerParams& params, - std::vector<QuerySolution*>* out) { - - LOG(5) << "Beginning planning..." << endl - << "=============================" << endl - << "Options = " << optionString(params.options) << endl - << "Canonical query:" << endl << query.toString() - << "=============================" << endl; - - for (size_t i = 0; i < params.indices.size(); ++i) { - LOG(5) << "Index " << i << " is " << params.indices[i].toString() << endl; - } - - bool canTableScan = !(params.options & QueryPlannerParams::NO_TABLE_SCAN); - - // If the query requests a tailable cursor, the only solution is a collscan + filter with - // tailable set on the collscan. TODO: This is a policy departure. Previously I think you - // could ask for a tailable cursor and it just tried to give you one. Now, we fail if we - // can't provide one. Is this what we want? - if (query.getParsed().isTailable()) { - if (!QueryPlannerCommon::hasNode(query.root(), MatchExpression::GEO_NEAR) - && canTableScan) { - QuerySolution* soln = buildCollscanSoln(query, true, params); + // The hint or sort can be $natural: 1. If this happens, output a collscan. If both + // a $natural hint and a $natural sort are specified, then the direction of the collscan + // is determined by the sign of the sort (not the sign of the hint). + if (!query.getParsed().getHint().isEmpty() || !query.getParsed().getSort().isEmpty()) { + BSONObj hintObj = query.getParsed().getHint(); + BSONObj sortObj = query.getParsed().getSort(); + BSONElement naturalHint = hintObj.getFieldDotted("$natural"); + BSONElement naturalSort = sortObj.getFieldDotted("$natural"); + + // A hint overrides a $natural sort. This means that we don't force a table + // scan if there is a $natural sort with a non-$natural hint. + if (!naturalHint.eoo() || (!naturalSort.eoo() && hintObj.isEmpty())) { + LOG(5) << "Forcing a table scan due to hinted $natural\n"; + // min/max are incompatible with $natural. + if (canTableScan && query.getParsed().getMin().isEmpty() && + query.getParsed().getMax().isEmpty()) { + QuerySolution* soln = buildCollscanSoln(query, false, params); if (NULL != soln) { out->push_back(soln); } } return Status::OK(); } + } - // The hint or sort can be $natural: 1. If this happens, output a collscan. If both - // a $natural hint and a $natural sort are specified, then the direction of the collscan - // is determined by the sign of the sort (not the sign of the hint). - if (!query.getParsed().getHint().isEmpty() || !query.getParsed().getSort().isEmpty()) { - BSONObj hintObj = query.getParsed().getHint(); - BSONObj sortObj = query.getParsed().getSort(); - BSONElement naturalHint = hintObj.getFieldDotted("$natural"); - BSONElement naturalSort = sortObj.getFieldDotted("$natural"); - - // A hint overrides a $natural sort. This means that we don't force a table - // scan if there is a $natural sort with a non-$natural hint. - if (!naturalHint.eoo() || (!naturalSort.eoo() && hintObj.isEmpty())) { - LOG(5) << "Forcing a table scan due to hinted $natural\n"; - // min/max are incompatible with $natural. - if (canTableScan && query.getParsed().getMin().isEmpty() - && query.getParsed().getMax().isEmpty()) { - QuerySolution* soln = buildCollscanSoln(query, false, params); - if (NULL != soln) { - out->push_back(soln); - } - } - return Status::OK(); - } - } + // Figure out what fields we care about. + unordered_set<string> fields; + QueryPlannerIXSelect::getFields(query.root(), "", &fields); - // Figure out what fields we care about. - unordered_set<string> fields; - QueryPlannerIXSelect::getFields(query.root(), "", &fields); + for (unordered_set<string>::const_iterator it = fields.begin(); it != fields.end(); ++it) { + LOG(5) << "Predicate over field '" << *it << "'" << endl; + } - for (unordered_set<string>::const_iterator it = fields.begin(); it != fields.end(); ++it) { - LOG(5) << "Predicate over field '" << *it << "'" << endl; - } + // Filter our indices so we only look at indices that are over our predicates. + vector<IndexEntry> relevantIndices; - // Filter our indices so we only look at indices that are over our predicates. - vector<IndexEntry> relevantIndices; + // Hints require us to only consider the hinted index. + // If index filters in the query settings were used to override + // the allowed indices for planning, we should not use the hinted index + // requested in the query. + BSONObj hintIndex; + if (!params.indexFiltersApplied) { + hintIndex = query.getParsed().getHint(); + } - // Hints require us to only consider the hinted index. - // If index filters in the query settings were used to override - // the allowed indices for planning, we should not use the hinted index - // requested in the query. - BSONObj hintIndex; - if (!params.indexFiltersApplied) { - hintIndex = query.getParsed().getHint(); + // Snapshot is a form of a hint. If snapshot is set, try to use _id index to make a real + // plan. If that fails, just scan the _id index. + if (query.getParsed().isSnapshot()) { + // Find the ID index in indexKeyPatterns. It's our hint. + for (size_t i = 0; i < params.indices.size(); ++i) { + if (isIdIndex(params.indices[i].keyPattern)) { + hintIndex = params.indices[i].keyPattern; + break; + } } + } - // Snapshot is a form of a hint. If snapshot is set, try to use _id index to make a real - // plan. If that fails, just scan the _id index. - if (query.getParsed().isSnapshot()) { - // Find the ID index in indexKeyPatterns. It's our hint. + size_t hintIndexNumber = numeric_limits<size_t>::max(); + + if (hintIndex.isEmpty()) { + QueryPlannerIXSelect::findRelevantIndices(fields, params.indices, &relevantIndices); + } else { + // Sigh. If the hint is specified it might be using the index name. + BSONElement firstHintElt = hintIndex.firstElement(); + if (str::equals("$hint", firstHintElt.fieldName()) && String == firstHintElt.type()) { + string hintName = firstHintElt.String(); for (size_t i = 0; i < params.indices.size(); ++i) { - if (isIdIndex(params.indices[i].keyPattern)) { + if (params.indices[i].name == hintName) { + LOG(5) << "Hint by name specified, restricting indices to " + << params.indices[i].keyPattern.toString() << endl; + relevantIndices.clear(); + relevantIndices.push_back(params.indices[i]); + hintIndexNumber = i; hintIndex = params.indices[i].keyPattern; break; } } - } - - size_t hintIndexNumber = numeric_limits<size_t>::max(); - - if (hintIndex.isEmpty()) { - QueryPlannerIXSelect::findRelevantIndices(fields, params.indices, &relevantIndices); - } - else { - // Sigh. If the hint is specified it might be using the index name. - BSONElement firstHintElt = hintIndex.firstElement(); - if (str::equals("$hint", firstHintElt.fieldName()) && String == firstHintElt.type()) { - string hintName = firstHintElt.String(); - for (size_t i = 0; i < params.indices.size(); ++i) { - if (params.indices[i].name == hintName) { - LOG(5) << "Hint by name specified, restricting indices to " - << params.indices[i].keyPattern.toString() << endl; - relevantIndices.clear(); - relevantIndices.push_back(params.indices[i]); - hintIndexNumber = i; - hintIndex = params.indices[i].keyPattern; - break; - } - } - } - else { - for (size_t i = 0; i < params.indices.size(); ++i) { - if (0 == params.indices[i].keyPattern.woCompare(hintIndex)) { - relevantIndices.clear(); - relevantIndices.push_back(params.indices[i]); - LOG(5) << "Hint specified, restricting indices to " << hintIndex.toString() - << endl; - hintIndexNumber = i; - break; - } + } else { + for (size_t i = 0; i < params.indices.size(); ++i) { + if (0 == params.indices[i].keyPattern.woCompare(hintIndex)) { + relevantIndices.clear(); + relevantIndices.push_back(params.indices[i]); + LOG(5) << "Hint specified, restricting indices to " << hintIndex.toString() + << endl; + hintIndexNumber = i; + break; } } - - if (hintIndexNumber == numeric_limits<size_t>::max()) { - return Status(ErrorCodes::BadValue, "bad hint"); - } } - // Deal with the .min() and .max() query options. If either exist we can only use an index - // that matches the object inside. - if (!query.getParsed().getMin().isEmpty() || !query.getParsed().getMax().isEmpty()) { - BSONObj minObj = query.getParsed().getMin(); - BSONObj maxObj = query.getParsed().getMax(); - - // The unfinished siblings of these objects may not be proper index keys because they - // may be empty objects or have field names. When an index is picked to use for the - // min/max query, these "finished" objects will always be valid index keys for the - // index's key pattern. - BSONObj finishedMinObj; - BSONObj finishedMaxObj; - - // This is the index into params.indices[...] that we use. - size_t idxNo = numeric_limits<size_t>::max(); - - // If there's an index hinted we need to be able to use it. - if (!hintIndex.isEmpty()) { - if (!minObj.isEmpty() && !indexCompatibleMaxMin(minObj, hintIndex)) { - LOG(5) << "Minobj doesn't work with hint"; - return Status(ErrorCodes::BadValue, - "hint provided does not work with min query"); - } + if (hintIndexNumber == numeric_limits<size_t>::max()) { + return Status(ErrorCodes::BadValue, "bad hint"); + } + } - if (!maxObj.isEmpty() && !indexCompatibleMaxMin(maxObj, hintIndex)) { - LOG(5) << "Maxobj doesn't work with hint"; - return Status(ErrorCodes::BadValue, - "hint provided does not work with max query"); - } + // Deal with the .min() and .max() query options. If either exist we can only use an index + // that matches the object inside. + if (!query.getParsed().getMin().isEmpty() || !query.getParsed().getMax().isEmpty()) { + BSONObj minObj = query.getParsed().getMin(); + BSONObj maxObj = query.getParsed().getMax(); - const BSONObj& kp = params.indices[hintIndexNumber].keyPattern; - finishedMinObj = finishMinObj(kp, minObj, maxObj); - finishedMaxObj = finishMaxObj(kp, minObj, maxObj); + // The unfinished siblings of these objects may not be proper index keys because they + // may be empty objects or have field names. When an index is picked to use for the + // min/max query, these "finished" objects will always be valid index keys for the + // index's key pattern. + BSONObj finishedMinObj; + BSONObj finishedMaxObj; - // The min must be less than the max for the hinted index ordering. - if (0 <= finishedMinObj.woCompare(finishedMaxObj, kp, false)) { - LOG(5) << "Minobj/Maxobj don't work with hint"; - return Status(ErrorCodes::BadValue, - "hint provided does not work with min/max query"); - } + // This is the index into params.indices[...] that we use. + size_t idxNo = numeric_limits<size_t>::max(); - idxNo = hintIndexNumber; + // If there's an index hinted we need to be able to use it. + if (!hintIndex.isEmpty()) { + if (!minObj.isEmpty() && !indexCompatibleMaxMin(minObj, hintIndex)) { + LOG(5) << "Minobj doesn't work with hint"; + return Status(ErrorCodes::BadValue, "hint provided does not work with min query"); } - else { - // No hinted index, look for one that is compatible (has same field names and - // ordering thereof). - for (size_t i = 0; i < params.indices.size(); ++i) { - const BSONObj& kp = params.indices[i].keyPattern; - - BSONObj toUse = minObj.isEmpty() ? maxObj : minObj; - if (indexCompatibleMaxMin(toUse, kp)) { - // In order to be fully compatible, the min has to be less than the max - // according to the index key pattern ordering. The first step in verifying - // this is "finish" the min and max by replacing empty objects and stripping - // field names. - finishedMinObj = finishMinObj(kp, minObj, maxObj); - finishedMaxObj = finishMaxObj(kp, minObj, maxObj); - - // Now we have the final min and max. This index is only relevant for - // the min/max query if min < max. - if (0 >= finishedMinObj.woCompare(finishedMaxObj, kp, false)) { - // Found a relevant index. - idxNo = i; - break; - } - - // This index is not relevant; move on to the next. - } - } + + if (!maxObj.isEmpty() && !indexCompatibleMaxMin(maxObj, hintIndex)) { + LOG(5) << "Maxobj doesn't work with hint"; + return Status(ErrorCodes::BadValue, "hint provided does not work with max query"); } - if (idxNo == numeric_limits<size_t>::max()) { - LOG(5) << "Can't find relevant index to use for max/min query"; - // Can't find an index to use, bail out. + const BSONObj& kp = params.indices[hintIndexNumber].keyPattern; + finishedMinObj = finishMinObj(kp, minObj, maxObj); + finishedMaxObj = finishMaxObj(kp, minObj, maxObj); + + // The min must be less than the max for the hinted index ordering. + if (0 <= finishedMinObj.woCompare(finishedMaxObj, kp, false)) { + LOG(5) << "Minobj/Maxobj don't work with hint"; return Status(ErrorCodes::BadValue, - "unable to find relevant index for max/min query"); + "hint provided does not work with min/max query"); } - LOG(5) << "Max/min query using index " << params.indices[idxNo].toString() << endl; - - // Make our scan and output. - QuerySolutionNode* solnRoot = QueryPlannerAccess::makeIndexScan(params.indices[idxNo], - query, - params, - finishedMinObj, - finishedMaxObj); + idxNo = hintIndexNumber; + } else { + // No hinted index, look for one that is compatible (has same field names and + // ordering thereof). + for (size_t i = 0; i < params.indices.size(); ++i) { + const BSONObj& kp = params.indices[i].keyPattern; + + BSONObj toUse = minObj.isEmpty() ? maxObj : minObj; + if (indexCompatibleMaxMin(toUse, kp)) { + // In order to be fully compatible, the min has to be less than the max + // according to the index key pattern ordering. The first step in verifying + // this is "finish" the min and max by replacing empty objects and stripping + // field names. + finishedMinObj = finishMinObj(kp, minObj, maxObj); + finishedMaxObj = finishMaxObj(kp, minObj, maxObj); + + // Now we have the final min and max. This index is only relevant for + // the min/max query if min < max. + if (0 >= finishedMinObj.woCompare(finishedMaxObj, kp, false)) { + // Found a relevant index. + idxNo = i; + break; + } - QuerySolution* soln = QueryPlannerAnalysis::analyzeDataAccess(query, params, solnRoot); - if (NULL != soln) { - out->push_back(soln); + // This index is not relevant; move on to the next. + } } + } - return Status::OK(); + if (idxNo == numeric_limits<size_t>::max()) { + LOG(5) << "Can't find relevant index to use for max/min query"; + // Can't find an index to use, bail out. + return Status(ErrorCodes::BadValue, "unable to find relevant index for max/min query"); } - for (size_t i = 0; i < relevantIndices.size(); ++i) { - LOG(2) << "Relevant index " << i << " is " << relevantIndices[i].toString() << endl; + LOG(5) << "Max/min query using index " << params.indices[idxNo].toString() << endl; + + // Make our scan and output. + QuerySolutionNode* solnRoot = QueryPlannerAccess::makeIndexScan( + params.indices[idxNo], query, params, finishedMinObj, finishedMaxObj); + + QuerySolution* soln = QueryPlannerAnalysis::analyzeDataAccess(query, params, solnRoot); + if (NULL != soln) { + out->push_back(soln); } - // Figure out how useful each index is to each predicate. - QueryPlannerIXSelect::rateIndices(query.root(), "", relevantIndices); - QueryPlannerIXSelect::stripInvalidAssignments(query.root(), relevantIndices); + return Status::OK(); + } + + for (size_t i = 0; i < relevantIndices.size(); ++i) { + LOG(2) << "Relevant index " << i << " is " << relevantIndices[i].toString() << endl; + } - // Unless we have GEO_NEAR, TEXT, or a projection, we may be able to apply an optimization - // in which we strip unnecessary index assignments. - // - // Disallowed with projection because assignment to a non-unique index can allow the plan - // to be covered. - // - // TEXT and GEO_NEAR are special because they require the use of a text/geo index in order - // to be evaluated correctly. Stripping these "mandatory assignments" is therefore invalid. - if (query.getParsed().getProj().isEmpty() - && !QueryPlannerCommon::hasNode(query.root(), MatchExpression::GEO_NEAR) - && !QueryPlannerCommon::hasNode(query.root(), MatchExpression::TEXT)) { - QueryPlannerIXSelect::stripUnneededAssignments(query.root(), relevantIndices); + // Figure out how useful each index is to each predicate. + QueryPlannerIXSelect::rateIndices(query.root(), "", relevantIndices); + QueryPlannerIXSelect::stripInvalidAssignments(query.root(), relevantIndices); + + // Unless we have GEO_NEAR, TEXT, or a projection, we may be able to apply an optimization + // in which we strip unnecessary index assignments. + // + // Disallowed with projection because assignment to a non-unique index can allow the plan + // to be covered. + // + // TEXT and GEO_NEAR are special because they require the use of a text/geo index in order + // to be evaluated correctly. Stripping these "mandatory assignments" is therefore invalid. + if (query.getParsed().getProj().isEmpty() && + !QueryPlannerCommon::hasNode(query.root(), MatchExpression::GEO_NEAR) && + !QueryPlannerCommon::hasNode(query.root(), MatchExpression::TEXT)) { + QueryPlannerIXSelect::stripUnneededAssignments(query.root(), relevantIndices); + } + + // query.root() is now annotated with RelevantTag(s). + LOG(5) << "Rated tree:" << endl + << query.root()->toString(); + + // If there is a GEO_NEAR it must have an index it can use directly. + MatchExpression* gnNode = NULL; + if (QueryPlannerCommon::hasNode(query.root(), MatchExpression::GEO_NEAR, &gnNode)) { + // No index for GEO_NEAR? No query. + RelevantTag* tag = static_cast<RelevantTag*>(gnNode->getTag()); + if (0 == tag->first.size() && 0 == tag->notFirst.size()) { + LOG(5) << "Unable to find index for $geoNear query." << endl; + // Don't leave tags on query tree. + query.root()->resetTag(); + return Status(ErrorCodes::BadValue, "unable to find index for $geoNear query"); } - // query.root() is now annotated with RelevantTag(s). - LOG(5) << "Rated tree:" << endl << query.root()->toString(); - - // If there is a GEO_NEAR it must have an index it can use directly. - MatchExpression* gnNode = NULL; - if (QueryPlannerCommon::hasNode(query.root(), MatchExpression::GEO_NEAR, &gnNode)) { - // No index for GEO_NEAR? No query. - RelevantTag* tag = static_cast<RelevantTag*>(gnNode->getTag()); - if (0 == tag->first.size() && 0 == tag->notFirst.size()) { - LOG(5) << "Unable to find index for $geoNear query." << endl; - // Don't leave tags on query tree. - query.root()->resetTag(); - return Status(ErrorCodes::BadValue, "unable to find index for $geoNear query"); + LOG(5) << "Rated tree after geonear processing:" << query.root()->toString(); + } + + // Likewise, if there is a TEXT it must have an index it can use directly. + MatchExpression* textNode = NULL; + if (QueryPlannerCommon::hasNode(query.root(), MatchExpression::TEXT, &textNode)) { + RelevantTag* tag = static_cast<RelevantTag*>(textNode->getTag()); + + // Exactly one text index required for TEXT. We need to check this explicitly because + // the text stage can't be built if no text index exists or there is an ambiguity as to + // which one to use. + size_t textIndexCount = 0; + for (size_t i = 0; i < params.indices.size(); i++) { + if (INDEX_TEXT == params.indices[i].type) { + textIndexCount++; } + } + if (textIndexCount != 1) { + // Don't leave tags on query tree. + query.root()->resetTag(); + return Status(ErrorCodes::BadValue, "need exactly one text index for $text query"); + } - LOG(5) << "Rated tree after geonear processing:" << query.root()->toString(); + // Error if the text node is tagged with zero indices. + if (0 == tag->first.size() && 0 == tag->notFirst.size()) { + // Don't leave tags on query tree. + query.root()->resetTag(); + return Status(ErrorCodes::BadValue, + "failed to use text index to satisfy $text query (if text index is " + "compound, are equality predicates given for all prefix fields?)"); } - // Likewise, if there is a TEXT it must have an index it can use directly. - MatchExpression* textNode = NULL; - if (QueryPlannerCommon::hasNode(query.root(), MatchExpression::TEXT, &textNode)) { - RelevantTag* tag = static_cast<RelevantTag*>(textNode->getTag()); - - // Exactly one text index required for TEXT. We need to check this explicitly because - // the text stage can't be built if no text index exists or there is an ambiguity as to - // which one to use. - size_t textIndexCount = 0; - for (size_t i = 0; i < params.indices.size(); i++) { - if (INDEX_TEXT == params.indices[i].type) { - textIndexCount++; - } - } - if (textIndexCount != 1) { - // Don't leave tags on query tree. - query.root()->resetTag(); - return Status(ErrorCodes::BadValue, "need exactly one text index for $text query"); - } + // At this point, we know that there is only one text index and that the TEXT node is + // assigned to it. + invariant(1 == tag->first.size() + tag->notFirst.size()); - // Error if the text node is tagged with zero indices. - if (0 == tag->first.size() && 0 == tag->notFirst.size()) { - // Don't leave tags on query tree. - query.root()->resetTag(); - return Status(ErrorCodes::BadValue, - "failed to use text index to satisfy $text query (if text index is " - "compound, are equality predicates given for all prefix fields?)"); - } + LOG(5) << "Rated tree after text processing:" << query.root()->toString(); + } - // At this point, we know that there is only one text index and that the TEXT node is - // assigned to it. - invariant(1 == tag->first.size() + tag->notFirst.size()); + // If we have any relevant indices, we try to create indexed plans. + if (0 < relevantIndices.size()) { + // The enumerator spits out trees tagged with IndexTag(s). + PlanEnumeratorParams enumParams; + enumParams.intersect = params.options & QueryPlannerParams::INDEX_INTERSECTION; + enumParams.root = query.root(); + enumParams.indices = &relevantIndices; - LOG(5) << "Rated tree after text processing:" << query.root()->toString(); - } + PlanEnumerator isp(enumParams); + isp.init(); - // If we have any relevant indices, we try to create indexed plans. - if (0 < relevantIndices.size()) { - // The enumerator spits out trees tagged with IndexTag(s). - PlanEnumeratorParams enumParams; - enumParams.intersect = params.options & QueryPlannerParams::INDEX_INTERSECTION; - enumParams.root = query.root(); - enumParams.indices = &relevantIndices; - - PlanEnumerator isp(enumParams); - isp.init(); - - MatchExpression* rawTree; - while (isp.getNext(&rawTree) && (out->size() < params.maxIndexedSolutions)) { - LOG(5) << "About to build solntree from tagged tree:" << endl - << rawTree->toString(); - - // The tagged tree produced by the plan enumerator is not guaranteed - // to be canonically sorted. In order to be compatible with the cached - // data, sort the tagged tree according to CanonicalQuery ordering. - std::unique_ptr<MatchExpression> clone(rawTree->shallowClone()); - CanonicalQuery::sortTree(clone.get()); - - PlanCacheIndexTree* cacheData; - Status indexTreeStatus = cacheDataFromTaggedTree(clone.get(), relevantIndices, &cacheData); - if (!indexTreeStatus.isOK()) { - LOG(5) << "Query is not cachable: " << indexTreeStatus.reason() << endl; - } - unique_ptr<PlanCacheIndexTree> autoData(cacheData); + MatchExpression* rawTree; + while (isp.getNext(&rawTree) && (out->size() < params.maxIndexedSolutions)) { + LOG(5) << "About to build solntree from tagged tree:" << endl + << rawTree->toString(); - // This can fail if enumeration makes a mistake. - QuerySolutionNode* solnRoot = - QueryPlannerAccess::buildIndexedDataAccess(query, rawTree, false, - relevantIndices, params); + // The tagged tree produced by the plan enumerator is not guaranteed + // to be canonically sorted. In order to be compatible with the cached + // data, sort the tagged tree according to CanonicalQuery ordering. + std::unique_ptr<MatchExpression> clone(rawTree->shallowClone()); + CanonicalQuery::sortTree(clone.get()); - if (NULL == solnRoot) { continue; } + PlanCacheIndexTree* cacheData; + Status indexTreeStatus = + cacheDataFromTaggedTree(clone.get(), relevantIndices, &cacheData); + if (!indexTreeStatus.isOK()) { + LOG(5) << "Query is not cachable: " << indexTreeStatus.reason() << endl; + } + unique_ptr<PlanCacheIndexTree> autoData(cacheData); - QuerySolution* soln = QueryPlannerAnalysis::analyzeDataAccess(query, - params, - solnRoot); - if (NULL != soln) { - LOG(5) << "Planner: adding solution:" << endl << soln->toString(); - if (indexTreeStatus.isOK()) { - SolutionCacheData* scd = new SolutionCacheData(); - scd->tree.reset(autoData.release()); - soln->cacheData.reset(scd); - } - out->push_back(soln); + // This can fail if enumeration makes a mistake. + QuerySolutionNode* solnRoot = QueryPlannerAccess::buildIndexedDataAccess( + query, rawTree, false, relevantIndices, params); + + if (NULL == solnRoot) { + continue; + } + + QuerySolution* soln = QueryPlannerAnalysis::analyzeDataAccess(query, params, solnRoot); + if (NULL != soln) { + LOG(5) << "Planner: adding solution:" << endl + << soln->toString(); + if (indexTreeStatus.isOK()) { + SolutionCacheData* scd = new SolutionCacheData(); + scd->tree.reset(autoData.release()); + soln->cacheData.reset(scd); } + out->push_back(soln); } } + } - // Don't leave tags on query tree. - query.root()->resetTag(); - - LOG(5) << "Planner: outputted " << out->size() << " indexed solutions.\n"; - - // Produce legible error message for failed OR planning with a TEXT child. - // TODO: support collection scan for non-TEXT children of OR. - if (out->size() == 0 && textNode != NULL && - MatchExpression::OR == query.root()->matchType()) { - MatchExpression* root = query.root(); - for (size_t i = 0; i < root->numChildren(); ++i) { - if (textNode == root->getChild(i)) { - return Status(ErrorCodes::BadValue, - "Failed to produce a solution for TEXT under OR - " - "other non-TEXT clauses under OR have to be indexed as well."); - } + // Don't leave tags on query tree. + query.root()->resetTag(); + + LOG(5) << "Planner: outputted " << out->size() << " indexed solutions.\n"; + + // Produce legible error message for failed OR planning with a TEXT child. + // TODO: support collection scan for non-TEXT children of OR. + if (out->size() == 0 && textNode != NULL && MatchExpression::OR == query.root()->matchType()) { + MatchExpression* root = query.root(); + for (size_t i = 0; i < root->numChildren(); ++i) { + if (textNode == root->getChild(i)) { + return Status(ErrorCodes::BadValue, + "Failed to produce a solution for TEXT under OR - " + "other non-TEXT clauses under OR have to be indexed as well."); } } + } - // An index was hinted. If there are any solutions, they use the hinted index. If not, we - // scan the entire index to provide results and output that as our plan. This is the - // desired behavior when an index is hinted that is not relevant to the query. - if (!hintIndex.isEmpty()) { - if (0 == out->size()) { - QuerySolution* soln = buildWholeIXSoln(params.indices[hintIndexNumber], - query, params); - verify(NULL != soln); - LOG(5) << "Planner: outputting soln that uses hinted index as scan." << endl; - out->push_back(soln); + // An index was hinted. If there are any solutions, they use the hinted index. If not, we + // scan the entire index to provide results and output that as our plan. This is the + // desired behavior when an index is hinted that is not relevant to the query. + if (!hintIndex.isEmpty()) { + if (0 == out->size()) { + QuerySolution* soln = buildWholeIXSoln(params.indices[hintIndexNumber], query, params); + verify(NULL != soln); + LOG(5) << "Planner: outputting soln that uses hinted index as scan." << endl; + out->push_back(soln); + } + return Status::OK(); + } + + // If a sort order is requested, there may be an index that provides it, even if that + // index is not over any predicates in the query. + // + if (!query.getParsed().getSort().isEmpty() && + !QueryPlannerCommon::hasNode(query.root(), MatchExpression::GEO_NEAR) && + !QueryPlannerCommon::hasNode(query.root(), MatchExpression::TEXT)) { + // See if we have a sort provided from an index already. + // This is implied by the presence of a non-blocking solution. + bool usingIndexToSort = false; + for (size_t i = 0; i < out->size(); ++i) { + QuerySolution* soln = (*out)[i]; + if (!soln->hasBlockingStage) { + usingIndexToSort = true; + break; } - return Status::OK(); } - // If a sort order is requested, there may be an index that provides it, even if that - // index is not over any predicates in the query. - // - if (!query.getParsed().getSort().isEmpty() - && !QueryPlannerCommon::hasNode(query.root(), MatchExpression::GEO_NEAR) - && !QueryPlannerCommon::hasNode(query.root(), MatchExpression::TEXT)) { - - // See if we have a sort provided from an index already. - // This is implied by the presence of a non-blocking solution. - bool usingIndexToSort = false; - for (size_t i = 0; i < out->size(); ++i) { - QuerySolution* soln = (*out)[i]; - if (!soln->hasBlockingStage) { - usingIndexToSort = true; - break; + if (!usingIndexToSort) { + for (size_t i = 0; i < params.indices.size(); ++i) { + const IndexEntry& index = params.indices[i]; + // Only regular (non-plugin) indexes can be used to provide a sort. + if (index.type != INDEX_BTREE) { + continue; + } + // Only non-sparse indexes can be used to provide a sort. + if (index.sparse) { + continue; } - } - if (!usingIndexToSort) { - for (size_t i = 0; i < params.indices.size(); ++i) { - const IndexEntry& index = params.indices[i]; - // Only regular (non-plugin) indexes can be used to provide a sort. - if (index.type != INDEX_BTREE) { - continue; - } - // Only non-sparse indexes can be used to provide a sort. - if (index.sparse) { - continue; - } + // TODO: Sparse indexes can't normally provide a sort, because non-indexed + // documents could potentially be missing from the result set. However, if the + // query predicate can be used to guarantee that all documents to be returned + // are indexed, then the index should be able to provide the sort. + // + // For example: + // - Sparse index {a: 1, b: 1} should be able to provide a sort for + // find({b: 1}).sort({a: 1}). SERVER-13908. + // - Index {a: 1, b: "2dsphere"} (which is "geo-sparse", if + // 2dsphereIndexVersion=2) should be able to provide a sort for + // find({b: GEO}).sort({a:1}). SERVER-10801. + + const BSONObj kp = QueryPlannerAnalysis::getSortPattern(index.keyPattern); + if (providesSort(query, kp)) { + LOG(5) << "Planner: outputting soln that uses index to provide sort." << endl; + QuerySolution* soln = buildWholeIXSoln(params.indices[i], query, params); + if (NULL != soln) { + PlanCacheIndexTree* indexTree = new PlanCacheIndexTree(); + indexTree->setIndexEntry(params.indices[i]); + SolutionCacheData* scd = new SolutionCacheData(); + scd->tree.reset(indexTree); + scd->solnType = SolutionCacheData::WHOLE_IXSCAN_SOLN; + scd->wholeIXSolnDir = 1; - // TODO: Sparse indexes can't normally provide a sort, because non-indexed - // documents could potentially be missing from the result set. However, if the - // query predicate can be used to guarantee that all documents to be returned - // are indexed, then the index should be able to provide the sort. - // - // For example: - // - Sparse index {a: 1, b: 1} should be able to provide a sort for - // find({b: 1}).sort({a: 1}). SERVER-13908. - // - Index {a: 1, b: "2dsphere"} (which is "geo-sparse", if - // 2dsphereIndexVersion=2) should be able to provide a sort for - // find({b: GEO}).sort({a:1}). SERVER-10801. - - const BSONObj kp = QueryPlannerAnalysis::getSortPattern(index.keyPattern); - if (providesSort(query, kp)) { - LOG(5) << "Planner: outputting soln that uses index to provide sort." - << endl; - QuerySolution* soln = buildWholeIXSoln(params.indices[i], - query, params); - if (NULL != soln) { - PlanCacheIndexTree* indexTree = new PlanCacheIndexTree(); - indexTree->setIndexEntry(params.indices[i]); - SolutionCacheData* scd = new SolutionCacheData(); - scd->tree.reset(indexTree); - scd->solnType = SolutionCacheData::WHOLE_IXSCAN_SOLN; - scd->wholeIXSolnDir = 1; - - soln->cacheData.reset(scd); - out->push_back(soln); - break; - } + soln->cacheData.reset(scd); + out->push_back(soln); + break; } - if (providesSort(query, QueryPlannerCommon::reverseSortObj(kp))) { - LOG(5) << "Planner: outputting soln that uses (reverse) index " - << "to provide sort." << endl; - QuerySolution* soln = buildWholeIXSoln(params.indices[i], query, - params, -1); - if (NULL != soln) { - PlanCacheIndexTree* indexTree = new PlanCacheIndexTree(); - indexTree->setIndexEntry(params.indices[i]); - SolutionCacheData* scd = new SolutionCacheData(); - scd->tree.reset(indexTree); - scd->solnType = SolutionCacheData::WHOLE_IXSCAN_SOLN; - scd->wholeIXSolnDir = -1; - - soln->cacheData.reset(scd); - out->push_back(soln); - break; - } + } + if (providesSort(query, QueryPlannerCommon::reverseSortObj(kp))) { + LOG(5) << "Planner: outputting soln that uses (reverse) index " + << "to provide sort." << endl; + QuerySolution* soln = buildWholeIXSoln(params.indices[i], query, params, -1); + if (NULL != soln) { + PlanCacheIndexTree* indexTree = new PlanCacheIndexTree(); + indexTree->setIndexEntry(params.indices[i]); + SolutionCacheData* scd = new SolutionCacheData(); + scd->tree.reset(indexTree); + scd->solnType = SolutionCacheData::WHOLE_IXSCAN_SOLN; + scd->wholeIXSolnDir = -1; + + soln->cacheData.reset(scd); + out->push_back(soln); + break; } } } } + } - // geoNear and text queries *require* an index. - // Also, if a hint is specified it indicates that we MUST use it. - bool possibleToCollscan = !QueryPlannerCommon::hasNode(query.root(), MatchExpression::GEO_NEAR) - && !QueryPlannerCommon::hasNode(query.root(), MatchExpression::TEXT) - && hintIndex.isEmpty(); - - // The caller can explicitly ask for a collscan. - bool collscanRequested = (params.options & QueryPlannerParams::INCLUDE_COLLSCAN); - - // No indexed plans? We must provide a collscan if possible or else we can't run the query. - bool collscanNeeded = (0 == out->size() && canTableScan); - - if (possibleToCollscan && (collscanRequested || collscanNeeded)) { - QuerySolution* collscan = buildCollscanSoln(query, false, params); - if (NULL != collscan) { - SolutionCacheData* scd = new SolutionCacheData(); - scd->solnType = SolutionCacheData::COLLSCAN_SOLN; - collscan->cacheData.reset(scd); - out->push_back(collscan); - LOG(5) << "Planner: outputting a collscan:" << endl - << collscan->toString(); - } - } + // geoNear and text queries *require* an index. + // Also, if a hint is specified it indicates that we MUST use it. + bool possibleToCollscan = + !QueryPlannerCommon::hasNode(query.root(), MatchExpression::GEO_NEAR) && + !QueryPlannerCommon::hasNode(query.root(), MatchExpression::TEXT) && hintIndex.isEmpty(); - return Status::OK(); + // The caller can explicitly ask for a collscan. + bool collscanRequested = (params.options & QueryPlannerParams::INCLUDE_COLLSCAN); + + // No indexed plans? We must provide a collscan if possible or else we can't run the query. + bool collscanNeeded = (0 == out->size() && canTableScan); + + if (possibleToCollscan && (collscanRequested || collscanNeeded)) { + QuerySolution* collscan = buildCollscanSoln(query, false, params); + if (NULL != collscan) { + SolutionCacheData* scd = new SolutionCacheData(); + scd->solnType = SolutionCacheData::COLLSCAN_SOLN; + collscan->cacheData.reset(scd); + out->push_back(collscan); + LOG(5) << "Planner: outputting a collscan:" << endl + << collscan->toString(); + } } + return Status::OK(); +} + } // namespace mongo |