/** * Copyright (C) 2013 10gen Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * * As a special exception, the copyright holders give permission to link the * code of portions of this program with the OpenSSL library under certain * conditions as described in each individual source file and distribute * linked combinations including the program with the OpenSSL library. You * must comply with the GNU Affero General Public License in all respects for * all of the code used other than as permitted herein. If you modify file(s) * with this exception, you may extend this exception to your version of the * file(s), but you are not obligated to do so. If you do not wish to do so, * delete this exception statement from your version. If you delete this * exception statement from all source files in the program, then also delete * it in the license file. */ #include "mongo/db/query/planner_access.h" #include #include #include "mongo/db/matcher/expression_array.h" #include "mongo/db/matcher/expression_geo.h" #include "mongo/db/matcher/expression_text.h" #include "mongo/db/query/indexability.h" #include "mongo/db/query/index_bounds_builder.h" #include "mongo/db/query/index_tag.h" #include "mongo/db/query/qlog.h" #include "mongo/db/query/query_planner.h" #include "mongo/db/query/query_planner_common.h" namespace { using namespace mongo; /** * Text node functors. */ bool isTextNode(const QuerySolutionNode* node) { return STAGE_TEXT == node->getType(); } } // namespace namespace mongo { // static QuerySolutionNode* QueryPlannerAccess::makeCollectionScan(const CanonicalQuery& query, bool tailable, const QueryPlannerParams& params) { // Make the (only) node, a collection scan. CollectionScanNode* csn = new CollectionScanNode(); csn->name = query.ns(); csn->filter.reset(query.root()->shallowClone()); csn->tailable = tailable; csn->maxScan = query.getParsed().getMaxScan(); // If the sort is {$natural: +-1} this changes the direction of the collection scan. const BSONObj& sortObj = query.getParsed().getSort(); if (!sortObj.isEmpty()) { BSONElement natural = sortObj.getFieldDotted("$natural"); if (!natural.eoo()) { csn->direction = natural.numberInt() >= 0 ? 1 : -1; } } // The hint can specify $natural as well. if (!query.getParsed().getHint().isEmpty()) { BSONElement natural = query.getParsed().getHint().getFieldDotted("$natural"); if (!natural.eoo()) { csn->direction = natural.numberInt() >= 0 ? 1 : -1; } } // QLOG() << "Outputting collscan " << soln->toString() << endl; return csn; } // static QuerySolutionNode* QueryPlannerAccess::makeLeafNode(const CanonicalQuery& query, const IndexEntry& index, MatchExpression* expr, IndexBoundsBuilder::BoundsTightness* tightnessOut) { // QLOG() << "making leaf node for " << expr->toString() << endl; // We're guaranteed that all GEO_NEARs are first. This slightly violates the "sort index // predicates by their position in the compound index" rule but GEO_NEAR isn't an ixscan. // This saves our bacon when we have {foo: 1, bar: "2dsphere"} and the predicate on bar is a // $near. If we didn't get the GEO_NEAR first we'd create an IndexScanNode and later cast // it to a GeoNear2DSphereNode // // This should gracefully deal with the case where we have a pred over foo but no geo clause // over bar. In that case there is no GEO_NEAR to appear first and it's treated like a // straight ixscan. BSONElement elt = index.keyPattern.firstElement(); bool indexIs2D = (String == elt.type() && "2d" == elt.String()); if (MatchExpression::GEO_NEAR == expr->matchType()) { // We must not keep the expression node around. *tightnessOut = IndexBoundsBuilder::EXACT; GeoNearMatchExpression* nearExpr = static_cast(expr); // 2d geoNear requires a hard limit and as such we take it out before it gets here. If // this happens it's a bug. verify(!indexIs2D); GeoNear2DSphereNode* ret = new GeoNear2DSphereNode(); ret->indexKeyPattern = index.keyPattern; ret->nq = nearExpr->getData(); ret->baseBounds.fields.resize(index.keyPattern.nFields()); if (NULL != query.getProj()) { ret->addPointMeta = query.getProj()->wantGeoNearPoint(); ret->addDistMeta = query.getProj()->wantGeoNearDistance(); } return ret; } else if (indexIs2D) { // We must not keep the expression node around. *tightnessOut = IndexBoundsBuilder::EXACT; verify(MatchExpression::GEO == expr->matchType()); GeoMatchExpression* nearExpr = static_cast(expr); verify(indexIs2D); Geo2DNode* ret = new Geo2DNode(); ret->indexKeyPattern = index.keyPattern; ret->gq = nearExpr->getGeoQuery(); return ret; } else if (MatchExpression::TEXT == expr->matchType()) { // We must not keep the expression node around. *tightnessOut = IndexBoundsBuilder::EXACT; TextMatchExpression* textExpr = static_cast(expr); TextNode* ret = new TextNode(); ret->_indexKeyPattern = index.keyPattern; ret->_query = textExpr->getQuery(); ret->_language = textExpr->getLanguage(); return ret; } else { // QLOG() << "making ixscan for " << expr->toString() << endl; // Note that indexKeyPattern.firstElement().fieldName() may not equal expr->path() // because expr might be inside an array operator that provides a path prefix. IndexScanNode* isn = new IndexScanNode(); isn->indexKeyPattern = index.keyPattern; isn->indexIsMultiKey = index.multikey; isn->bounds.fields.resize(index.keyPattern.nFields()); isn->maxScan = query.getParsed().getMaxScan(); isn->addKeyMetadata = query.getParsed().returnKey(); IndexBoundsBuilder::translate(expr, index.keyPattern.firstElement(), &isn->bounds.fields[0], tightnessOut); // QLOG() << "bounds are " << isn->bounds.toString() << " exact " << *exact << endl; return isn; } } void QueryPlannerAccess::mergeWithLeafNode(MatchExpression* expr, const IndexEntry& index, size_t pos, IndexBoundsBuilder::BoundsTightness* tightnessOut, QuerySolutionNode* node, MatchExpression::MatchType mergeType) { const StageType type = node->getType(); verify(STAGE_GEO_NEAR_2D != type); if (STAGE_GEO_2D == type || STAGE_TEXT == type) { // XXX: 'expr' is possibly indexed by 'node'. Right now we don't take advantage // of covering here (??). *tightnessOut = IndexBoundsBuilder::INEXACT_FETCH; return; } IndexBounds* boundsToFillOut = NULL; if (STAGE_GEO_NEAR_2DSPHERE == type) { GeoNear2DSphereNode* gn = static_cast(node); boundsToFillOut = &gn->baseBounds; } else { verify(type == STAGE_IXSCAN); IndexScanNode* scan = static_cast(node); boundsToFillOut = &scan->bounds; } // Get the ixtag->pos-th element of the index key pattern. // TODO: cache this instead/with ixtag->pos? BSONObjIterator it(index.keyPattern); BSONElement keyElt = it.next(); for (size_t i = 0; i < pos; ++i) { verify(it.more()); keyElt = it.next(); } verify(!keyElt.eoo()); *tightnessOut = IndexBoundsBuilder::INEXACT_FETCH; //QLOG() << "current bounds are " << currentScan->bounds.toString() << endl; //QLOG() << "node merging in " << child->toString() << endl; //QLOG() << "merging with field " << keyElt.toString(true, true) << endl; //QLOG() << "taking advantage of compound index " //<< indices[currentIndexNumber].keyPattern.toString() << endl; verify(boundsToFillOut->fields.size() > pos); OrderedIntervalList* oil = &boundsToFillOut->fields[pos]; if (boundsToFillOut->fields[pos].name.empty()) { IndexBoundsBuilder::translate(expr, keyElt, oil, tightnessOut); } else { if (MatchExpression::AND == mergeType) { IndexBoundsBuilder::translateAndIntersect(expr, keyElt, oil, tightnessOut); } else { verify(MatchExpression::OR == mergeType); IndexBoundsBuilder::translateAndUnion(expr, keyElt, oil, tightnessOut); } } } // static void QueryPlannerAccess::alignBounds(IndexBounds* bounds, const BSONObj& kp, int scanDir) { BSONObjIterator it(kp); size_t oilIdx = 0; while (it.more()) { BSONElement elt = it.next(); int direction = (elt.numberInt() >= 0) ? 1 : -1; direction *= scanDir; if (-1 == direction) { vector& iv = bounds->fields[oilIdx].intervals; // Step 1: reverse the list. std::reverse(iv.begin(), iv.end()); // Step 2: reverse each interval. for (size_t i = 0; i < iv.size(); ++i) { QLOG() << "reversing " << iv[i].toString() << endl; iv[i].reverse(); } } ++oilIdx; } if (!bounds->isValidFor(kp, scanDir)) { QLOG() << "INVALID BOUNDS: " << bounds->toString() << endl; QLOG() << "kp = " << kp.toString() << endl; QLOG() << "scanDir = " << scanDir << endl; verify(0); } } // static void QueryPlannerAccess::finishLeafNode(QuerySolutionNode* node, const IndexEntry& index) { const StageType type = node->getType(); verify(STAGE_GEO_NEAR_2D != type); if (STAGE_GEO_2D == type || STAGE_TEXT == type) { return; } IndexBounds* bounds = NULL; if (STAGE_GEO_NEAR_2DSPHERE == type) { GeoNear2DSphereNode* gnode = static_cast(node); bounds = &gnode->baseBounds; } else { verify(type == STAGE_IXSCAN); IndexScanNode* scan = static_cast(node); bounds = &scan->bounds; } // XXX: this currently fills out minkey/maxkey bounds for near queries, fix that. just // set the field name of the near query field when starting a near scan. // Find the first field in the scan's bounds that was not filled out. // TODO: could cache this. size_t firstEmptyField = 0; for (firstEmptyField = 0; firstEmptyField < bounds->fields.size(); ++firstEmptyField) { if ("" == bounds->fields[firstEmptyField].name) { verify(bounds->fields[firstEmptyField].intervals.empty()); break; } } // All fields are filled out with bounds, nothing to do. if (firstEmptyField == bounds->fields.size()) { alignBounds(bounds, index.keyPattern); return; } // Skip ahead to the firstEmptyField-th element, where we begin filling in bounds. BSONObjIterator it(index.keyPattern); for (size_t i = 0; i < firstEmptyField; ++i) { verify(it.more()); it.next(); } // For each field in the key... while (it.more()) { BSONElement kpElt = it.next(); // There may be filled-in fields to the right of the firstEmptyField. // Example: // The index {loc:"2dsphere", x:1} // With a predicate over x and a near search over loc. if ("" == bounds->fields[firstEmptyField].name) { verify(bounds->fields[firstEmptyField].intervals.empty()); // ...build the "all values" interval. IndexBoundsBuilder::allValuesForField(kpElt, &bounds->fields[firstEmptyField]); } ++firstEmptyField; } // Make sure that the length of the key is the length of the bounds we started. verify(firstEmptyField == bounds->fields.size()); // We create bounds assuming a forward direction but can easily reverse bounds to align // according to our desired direction. alignBounds(bounds, index.keyPattern); } // static bool QueryPlannerAccess::processIndexScans(const CanonicalQuery& query, MatchExpression* root, bool inArrayOperator, const vector& indices, vector* out) { auto_ptr currentScan; size_t currentIndexNumber = IndexTag::kNoIndex; size_t curChild = 0; // This 'while' processes all IXSCANs, possibly merging scans by combining the bounds. We // can merge scans in two cases: // 1. Filling out subsequent fields in a compound index. // 2. Intersecting bounds. Currently unimplemented. while (curChild < root->numChildren()) { MatchExpression* child = root->getChild(curChild); // If there is no tag, it's not using an index. We've sorted our children such that the // children with tags are first, so we stop now. if (NULL == child->getTag()) { break; } IndexTag* ixtag = static_cast(child->getTag()); // If there's a tag it must be valid. verify(IndexTag::kNoIndex != ixtag->index); // If the child can't use an index on its own field, it's indexed by virtue of one of // its children having an index. We don't do anything special here, just add it to // the output as-is. // // NOTE: If the child is logical, it could possibly collapse into a single ixscan. we // ignore this for now. if (!Indexability::nodeCanUseIndexOnOwnField(child)) { if (!inArrayOperator) { // The logical sub-tree is responsible for fully evaluating itself. Any // required filters or fetches are already hung on it. As such, we remove the // filter branch from our tree. buildIndexedDataAccess takes ownership of the // child. root->getChildVector()->erase(root->getChildVector()->begin() + curChild); // The curChild of today is the curChild+1 of yesterday. } else { ++curChild; } // If inArrayOperator: takes ownership of child, which is OK, since we detached // child from root. QuerySolutionNode* childSolution = buildIndexedDataAccess(query, child, inArrayOperator, indices); if (NULL == childSolution) { return false; } out->push_back(childSolution); continue; } // If we're here, we now know that 'child' can use an index directly and the index is // over the child's field. // If the child we're looking at uses a different index than the current index scan, add // the current index scan to the output as we're done with it. The index scan created // by the child then becomes our new current index scan. Note that the current scan // could be NULL, in which case we don't output it. The rest of the logic is identical. // // If the child uses the same index as the current index scan, we may be able to merge // the bounds for the two scans. // // Guiding principle: must the values we're testing come from the same array in the // document? If so, we can combine bounds (via intersection or compounding). If not, // we can't. // // If the index is NOT multikey, it's always semantically correct to combine bounds, // as there are no arrays to worry about. // // If the index is multikey, there are arrays of values. There are three issues: // // 1. We can't intersect bounds even if the bounds are not on a compound index. // Example: // Let's say we have the document {a: [5, 7]}. // This document satisfies the query {$and: [ {a: 5}, {a: 7} ] } // For the index {a:1} we have the keys {"": 5} and {"": 7}. // Each child of the AND is tagged with the index {a: 1} // The interval for the {a: 5} branch is [5, 5]. It is exact. // The interval for the {a: 7} branch is [7, 7]. It is exact. // The intersection of the intervals is {}. // If we scan over {}, the intersection of the intervals, we will retrieve nothing. // // 2. If we're using a compound index, we can only specify bounds for the first field. // Example: // Let's say we have the document {a: [ {b: 3}, {c: 4} ] } // This document satisfies the query {'a.b': 3, 'a.c': 4}. // For the index {'a.b': 1, 'a.c': 1} we have the keys {"": 3, "": null} and // {"": null, "": 4}. // Let's use the aforementioned index to answer the query. // The bounds for 'a.b' are [3,3], and the bounds for 'a.c' are [4,4]. // If we combine the bounds, we would only look at keys {"": 3, "":4 }. // Therefore we wouldn't look at the document's keys in the index. // Therefore we don't combine bounds. // // 3. There is an exception to (2), and that is when we're evaluating an $elemMatch. // Example: // Our query is a: {$elemMatch: {b:3, c:4}}. // Let's say that we have the index {'a.b': 1, 'a.c': 1} as in (2). // $elemMatch requires if a.b==3 and a.c==4, the predicates must be satisfied from // the same array entry. // If those values are both present in the same array, the index key for the // aforementioned index will be {"":3, "":4} // Therefore we can intersect bounds. // TODO: we should also merge if we're in an array operator, but only when we figure out index13.js. if (NULL != currentScan.get() && (currentIndexNumber == ixtag->index) && !indices[currentIndexNumber].multikey) { // The child uses the same index we're currently building a scan for. Merge // the bounds and filters. verify(currentIndexNumber == ixtag->index); IndexBoundsBuilder::BoundsTightness tightness = IndexBoundsBuilder::INEXACT_FETCH; mergeWithLeafNode(child, indices[currentIndexNumber], ixtag->pos, &tightness, currentScan.get(), root->matchType()); if (tightness == IndexBoundsBuilder::EXACT) { root->getChildVector()->erase(root->getChildVector()->begin() + curChild); delete child; } else if (tightness == IndexBoundsBuilder::INEXACT_COVERED) { // The bounds are not exact, but the information needed to // evaluate the predicate is in the index key. Remove the // MatchExpression from its parent and attach it to the filter // of the index scan we're building. root->getChildVector()->erase(root->getChildVector()->begin() + curChild); _addFilterToSolutionNode(currentScan.get(), child, root->matchType()); } else if (root->matchType() == MatchExpression::OR) { // In the AND case, the filter can be brought above the AND node. // But in the OR case, the filter only applies to one branch, so // we must affix curChild's filter now. In order to apply the filter // to the proper OR branch, create a FETCH node with the filter whose // child is the IXSCAN. finishLeafNode(currentScan.get(), indices[currentIndexNumber]); root->getChildVector()->erase(root->getChildVector()->begin() + curChild); FetchNode* fetch = new FetchNode(); // takes ownership fetch->filter.reset(child); // takes ownership fetch->children.push_back(currentScan.release()); // takes ownership out->push_back(fetch); currentIndexNumber = IndexTag::kNoIndex; } else { // We keep curChild in the AND for affixing later. ++curChild; } } else { if (NULL != currentScan.get()) { finishLeafNode(currentScan.get(), indices[currentIndexNumber]); out->push_back(currentScan.release()); } else { verify(IndexTag::kNoIndex == currentIndexNumber); } currentIndexNumber = ixtag->index; IndexBoundsBuilder::BoundsTightness tightness = IndexBoundsBuilder::INEXACT_FETCH; currentScan.reset(makeLeafNode(query, indices[currentIndexNumber], child, &tightness)); if (tightness == IndexBoundsBuilder::EXACT && !inArrayOperator) { // The bounds answer the predicate, and we can remove the expression from the // root. NOTE(opt): Erasing entry 0, 1, 2, ... could be kind of n^2, maybe // optimize later. root->getChildVector()->erase(root->getChildVector()->begin() + curChild); delete child; // Don't increment curChild. } else if (tightness == IndexBoundsBuilder::INEXACT_COVERED) { // The bounds are not exact, but the information needed to // evaluate the predicate is in the index key. Remove the // MatchExpression from its parent and attach it to the filter // of the index scan we're building. root->getChildVector()->erase(root->getChildVector()->begin() + curChild); _addFilterToSolutionNode(currentScan.get(), child, root->matchType()); } else if (root->matchType() == MatchExpression::OR) { // In the AND case, the filter can be brought above the AND node. // But in the OR case, the filter only applies to one branch, so // we must affix curChild's filter now. In order to apply the filter // to the proper OR branch, create a FETCH node with the filter whose // child is the IXSCAN. finishLeafNode(currentScan.get(), indices[currentIndexNumber]); root->getChildVector()->erase(root->getChildVector()->begin() + curChild); FetchNode* fetch = new FetchNode(); // takes ownership fetch->filter.reset(child); // takes ownership fetch->children.push_back(currentScan.release()); // takes ownership out->push_back(fetch); currentIndexNumber = IndexTag::kNoIndex; } else { // We keep curChild in the AND for affixing later as a filter. ++curChild; } } } // Output the scan we're done with, if it exists. if (NULL != currentScan.get()) { finishLeafNode(currentScan.get(), indices[currentIndexNumber]); out->push_back(currentScan.release()); } return true; } // static QuerySolutionNode* QueryPlannerAccess::buildIndexedAnd(const CanonicalQuery& query, MatchExpression* root, bool inArrayOperator, const vector& indices) { auto_ptr autoRoot; if (!inArrayOperator) { autoRoot.reset(root); } vector ixscanNodes; if (!processIndexScans(query, root, inArrayOperator, indices, &ixscanNodes)) { return NULL; } // // Process all non-indexed predicates. We hang these above the AND with a fetch and // filter. // // This is the node we're about to return. QuerySolutionNode* andResult; // We must use an index for at least one child of the AND. We shouldn't be here if this // isn't the case. verify(ixscanNodes.size() >= 1); // Short-circuit: an AND of one child is just the child. if (ixscanNodes.size() == 1) { andResult = ixscanNodes[0]; } else { // Figure out if we want AndHashNode or AndSortedNode. bool allSortedByDiskLoc = true; for (size_t i = 0; i < ixscanNodes.size(); ++i) { if (!ixscanNodes[i]->sortedByDiskLoc()) { allSortedByDiskLoc = false; break; } } if (allSortedByDiskLoc) { AndSortedNode* asn = new AndSortedNode(); asn->children.swap(ixscanNodes); andResult = asn; } else { AndHashNode* ahn = new AndHashNode(); ahn->children.swap(ixscanNodes); andResult = ahn; // The AndHashNode provides the sort order of its last child. If any of the // possible subnodes of AndHashNode provides the sort order we care about, we put // that one last. for (size_t i = 0; i < ahn->children.size(); ++i) { ahn->children[i]->computeProperties(); const BSONObjSet& sorts = ahn->children[i]->getSort(); if (sorts.end() != sorts.find(query.getParsed().getSort())) { std::swap(ahn->children[i], ahn->children.back()); break; } } } } // Don't bother doing any kind of fetch analysis lite if we're doing it anyway above us. if (inArrayOperator) { return andResult; } // If there are any nodes still attached to the AND, we can't answer them using the // index, so we put a fetch with filter. if (root->numChildren() > 0) { FetchNode* fetch = new FetchNode(); verify(NULL != autoRoot.get()); if (autoRoot->numChildren() == 1) { // An $and of one thing is that thing. MatchExpression* child = autoRoot->getChild(0); autoRoot->getChildVector()->clear(); // Takes ownership. fetch->filter.reset(child); // 'autoRoot' will delete the empty $and. } else { // root->numChildren() > 1 // Takes ownership. fetch->filter.reset(autoRoot.release()); } // takes ownership fetch->children.push_back(andResult); andResult = fetch; } else { // root has no children, let autoRoot get rid of it when it goes out of scope. } return andResult; } // static QuerySolutionNode* QueryPlannerAccess::buildIndexedOr(const CanonicalQuery& query, MatchExpression* root, bool inArrayOperator, const vector& indices) { auto_ptr autoRoot; if (!inArrayOperator) { autoRoot.reset(root); } vector ixscanNodes; if (!processIndexScans(query, root, inArrayOperator, indices, &ixscanNodes)) { return NULL; } // Unlike an AND, an OR cannot have filters hanging off of it. We stop processing // when any of our children lack index tags. If a node lacks an index tag it cannot // be answered via an index. if (!inArrayOperator && 0 != root->numChildren()) { warning() << "planner OR error, non-indexed child of OR."; // We won't enumerate an OR without indices for each child, so this isn't an issue, even // if we have an AND with an OR child -- we won't get here unless the OR is fully // indexed. return NULL; } QuerySolutionNode* orResult = NULL; // An OR of one node is just that node. if (1 == ixscanNodes.size()) { orResult = ixscanNodes[0]; } else { bool shouldMergeSort = false; if (!query.getParsed().getSort().isEmpty()) { const BSONObj& desiredSort = query.getParsed().getSort(); // If there exists a sort order that is present in each child, we can merge them and // maintain that sort order / those sort orders. ixscanNodes[0]->computeProperties(); BSONObjSet sharedSortOrders = ixscanNodes[0]->getSort(); if (!sharedSortOrders.empty()) { for (size_t i = 1; i < ixscanNodes.size(); ++i) { ixscanNodes[i]->computeProperties(); BSONObjSet isect; set_intersection(sharedSortOrders.begin(), sharedSortOrders.end(), ixscanNodes[i]->getSort().begin(), ixscanNodes[i]->getSort().end(), std::inserter(isect, isect.end()), BSONObjCmp()); sharedSortOrders = isect; if (sharedSortOrders.empty()) { break; } } } // XXX: consider reversing? shouldMergeSort = (sharedSortOrders.end() != sharedSortOrders.find(desiredSort)); } if (shouldMergeSort) { MergeSortNode* msn = new MergeSortNode(); msn->sort = query.getParsed().getSort(); msn->children.swap(ixscanNodes); orResult = msn; } else { OrNode* orn = new OrNode(); orn->children.swap(ixscanNodes); orResult = orn; } } // Evaluate text nodes first to ensure that text scores are available. // Move text nodes to front of vector. std::stable_partition(orResult->children.begin(), orResult->children.end(), isTextNode); // OR must have an index for each child, so we should have detached all children from // 'root', and there's nothing useful to do with an empty or MatchExpression. We let it die // via autoRoot. return orResult; } // static QuerySolutionNode* QueryPlannerAccess::buildIndexedDataAccess(const CanonicalQuery& query, MatchExpression* root, bool inArrayOperator, const vector& indices) { if (root->isLogical()) { if (MatchExpression::AND == root->matchType()) { // Takes ownership of root. return buildIndexedAnd(query, root, inArrayOperator, indices); } else if (MatchExpression::OR == root->matchType()) { // Takes ownership of root. return buildIndexedOr(query, root, inArrayOperator, indices); } else { // Can't do anything with negated logical nodes index-wise. return NULL; } } else { auto_ptr autoRoot; if (!inArrayOperator) { autoRoot.reset(root); } // isArray or isLeaf is true. Either way, it's over one field, and the bounds builder // deals with it. if (NULL == root->getTag()) { // No index to use here, not in the context of logical operator, so we're SOL. return NULL; } else if (Indexability::nodeCanUseIndexOnOwnField(root)) { // Make an index scan over the tagged index #. IndexTag* tag = static_cast(root->getTag()); IndexBoundsBuilder::BoundsTightness tightness = IndexBoundsBuilder::EXACT; QuerySolutionNode* soln = makeLeafNode(query, indices[tag->index], root, &tightness); verify(NULL != soln); stringstream ss; soln->appendToString(&ss, 0); // QLOG() << "about to finish leaf node, soln " << ss.str() << endl; finishLeafNode(soln, indices[tag->index]); if (inArrayOperator) { return soln; } // If the bounds are exact, the set of documents that satisfy the predicate is // exactly equal to the set of documents that the scan provides. // // If the bounds are not exact, the set of documents returned from the scan is a // superset of documents that satisfy the predicate, and we must check the // predicate. if (tightness == IndexBoundsBuilder::EXACT) { return soln; } else if (tightness == IndexBoundsBuilder::INEXACT_COVERED) { verify(NULL == soln->filter.get()); soln->filter.reset(autoRoot.release()); return soln; } else { // tightness == IndexBoundsBuilder::INEXACT_FETCH FetchNode* fetch = new FetchNode(); verify(NULL != autoRoot.get()); fetch->filter.reset(autoRoot.release()); fetch->children.push_back(soln); return fetch; } } else if (Indexability::arrayUsesIndexOnChildren(root)) { QuerySolutionNode* solution = NULL; if (MatchExpression::ALL == root->matchType()) { // Here, we formulate an AND of all the sub-clauses. auto_ptr ahn(new AndHashNode()); for (size_t i = 0; i < root->numChildren(); ++i) { QuerySolutionNode* node = buildIndexedDataAccess(query, root->getChild(i), true, indices); if (NULL != node) { ahn->children.push_back(node); } } // No children, no point in hashing nothing. if (0 == ahn->children.size()) { return NULL; } // AND of one child is just that child. if (1 == ahn->children.size()) { solution = ahn->children[0]; ahn->children.clear(); ahn.reset(); } else { // More than one child. solution = ahn.release(); } } else { verify(MatchExpression::ELEM_MATCH_OBJECT); // The child is an AND. verify(1 == root->numChildren()); solution = buildIndexedDataAccess(query, root->getChild(0), true, indices); if (NULL == solution) { return NULL; } } // There may be an array operator above us. if (inArrayOperator) { return solution; } FetchNode* fetch = new FetchNode(); // Takes ownership of 'root'. verify(NULL != autoRoot.get()); fetch->filter.reset(autoRoot.release()); fetch->children.push_back(solution); return fetch; } } return NULL; } QuerySolutionNode* QueryPlannerAccess::scanWholeIndex(const IndexEntry& index, const CanonicalQuery& query, const QueryPlannerParams& params, int direction) { QuerySolutionNode* solnRoot = NULL; // Build an ixscan over the id index, use it, and return it. IndexScanNode* isn = new IndexScanNode(); isn->indexKeyPattern = index.keyPattern; isn->indexIsMultiKey = index.multikey; isn->bounds.fields.resize(index.keyPattern.nFields()); isn->maxScan = query.getParsed().getMaxScan(); isn->addKeyMetadata = query.getParsed().returnKey(); // TODO: can we use simple bounds with this compound idx? BSONObjIterator it(isn->indexKeyPattern); int field = 0; while (it.more()) { IndexBoundsBuilder::allValuesForField(it.next(), &isn->bounds.fields[field]); ++field; } alignBounds(&isn->bounds, isn->indexKeyPattern); if (-1 == direction) { QueryPlannerCommon::reverseScans(isn); isn->direction = -1; } MatchExpression* filter = query.root()->shallowClone(); // If it's find({}) remove the no-op root. if (MatchExpression::AND == filter->matchType() && (0 == filter->numChildren())) { // XXX wasteful fix delete filter; solnRoot = isn; } else { // TODO: We may not need to do the fetch if the predicates in root are covered. But // for now it's safe (though *maybe* slower). FetchNode* fetch = new FetchNode(); fetch->filter.reset(filter); fetch->children.push_back(isn); solnRoot = fetch; } return solnRoot; } // static void QueryPlannerAccess::_addFilterToSolutionNode(QuerySolutionNode* node, MatchExpression* match, MatchExpression::MatchType type) { if (NULL == node->filter) { node->filter.reset(match); } // The 'node' already has either an AND or OR filter that matches // 'type'. Add 'match' as another branch of the filter. else if (type == node->filter->matchType()) { ListOfMatchExpression* listFilter = static_cast(node->filter.get()); listFilter->add(match); } // The 'node' already has a filter that does not match // 'type'. If 'type' is AND, then combine 'match' with // the existing filter by adding an AND. If 'type' is OR, // combine by adding an OR node. else { ListOfMatchExpression* listFilter; if (MatchExpression::AND == type) { listFilter = new AndMatchExpression(); } else { verify(MatchExpression::OR == type); listFilter = new OrMatchExpression(); } MatchExpression* oldFilter = node->filter->shallowClone(); listFilter->add(oldFilter); listFilter->add(match); node->filter.reset(listFilter); } } QuerySolutionNode* QueryPlannerAccess::makeIndexScan(const IndexEntry& index, const CanonicalQuery& query, const QueryPlannerParams& params, const BSONObj& startKey, const BSONObj& endKey) { QuerySolutionNode* solnRoot = NULL; // Build an ixscan over the id index, use it, and return it. IndexScanNode* isn = new IndexScanNode(); isn->indexKeyPattern = index.keyPattern; isn->indexIsMultiKey = index.multikey; isn->direction = 1; isn->maxScan = query.getParsed().getMaxScan(); isn->addKeyMetadata = query.getParsed().returnKey(); isn->bounds.isSimpleRange = true; isn->bounds.startKey = startKey; isn->bounds.endKey = endKey; isn->bounds.endKeyInclusive = false; MatchExpression* filter = query.root()->shallowClone(); // If it's find({}) remove the no-op root. if (MatchExpression::AND == filter->matchType() && (0 == filter->numChildren())) { // XXX wasteful fix delete filter; solnRoot = isn; } else { // TODO: We may not need to do the fetch if the predicates in root are covered. But // for now it's safe (though *maybe* slower). FetchNode* fetch = new FetchNode(); fetch->filter.reset(filter); fetch->children.push_back(isn); solnRoot = fetch; } return solnRoot; } } // namespace mongo