diff options
Diffstat (limited to 'src/mongo/db/query')
-rw-r--r-- | src/mongo/db/query/SConscript | 2 | ||||
-rw-r--r-- | src/mongo/db/query/explain_plan.cpp | 2 | ||||
-rw-r--r-- | src/mongo/db/query/index_bounds_builder_test.cpp | 44 | ||||
-rw-r--r-- | src/mongo/db/query/index_entry.h | 53 | ||||
-rw-r--r-- | src/mongo/db/query/plan_enumerator.cpp | 2 | ||||
-rw-r--r-- | src/mongo/db/query/planner_access.cpp | 153 | ||||
-rw-r--r-- | src/mongo/db/query/planner_access.h | 3 | ||||
-rw-r--r-- | src/mongo/db/query/planner_ixselect.cpp | 52 | ||||
-rw-r--r-- | src/mongo/db/query/query_planner_test_lib.cpp | 15 | ||||
-rw-r--r-- | src/mongo/db/query/query_planner_text_test.cpp | 22 | ||||
-rw-r--r-- | src/mongo/db/query/query_solution.cpp | 8 | ||||
-rw-r--r-- | src/mongo/db/query/query_solution.h | 11 | ||||
-rw-r--r-- | src/mongo/db/query/stage_builder.cpp | 15 |
13 files changed, 288 insertions, 94 deletions
diff --git a/src/mongo/db/query/SConscript b/src/mongo/db/query/SConscript index 34bb000f47e..dce9ce90dff 100644 --- a/src/mongo/db/query/SConscript +++ b/src/mongo/db/query/SConscript @@ -22,6 +22,7 @@ env.Library( "lite_parsed_query", "$BUILD_DIR/mongo/bson", "$BUILD_DIR/mongo/expressions", + "$BUILD_DIR/mongo/index_names", ], ) @@ -74,6 +75,7 @@ env.Library( "query_logger", "$BUILD_DIR/mongo/bson", "$BUILD_DIR/mongo/expressions_geo", + "$BUILD_DIR/mongo/index_names", "$BUILD_DIR/mongo/mongohasher", ], ) diff --git a/src/mongo/db/query/explain_plan.cpp b/src/mongo/db/query/explain_plan.cpp index 066a6540a3e..3eae9126c93 100644 --- a/src/mongo/db/query/explain_plan.cpp +++ b/src/mongo/db/query/explain_plan.cpp @@ -489,7 +489,7 @@ namespace mongo { } else if (STAGE_TEXT == node->getType()) { const TextNode* textNode = static_cast<const TextNode*>(node); - leafInfo << " " << textNode->_indexKeyPattern; + leafInfo << " " << textNode->indexKeyPattern; } leaves.push_back(leafInfo); diff --git a/src/mongo/db/query/index_bounds_builder_test.cpp b/src/mongo/db/query/index_bounds_builder_test.cpp index b236c8d7847..83ab037bac8 100644 --- a/src/mongo/db/query/index_bounds_builder_test.cpp +++ b/src/mongo/db/query/index_bounds_builder_test.cpp @@ -49,8 +49,6 @@ namespace { double negativeInfinity = -numeric_limits<double>::infinity(); double positiveInfinity = numeric_limits<double>::infinity(); - IndexEntry testIndex = IndexEntry(BSONObj()); - /** * Utility function to create MatchExpression */ @@ -67,6 +65,9 @@ namespace { */ void testTranslateAndUnion(const vector<BSONObj>& toUnion, OrderedIntervalList* oilOut, IndexBoundsBuilder::BoundsTightness* tightnessOut) { + + IndexEntry testIndex = IndexEntry(BSONObj()); + for (vector<BSONObj>::const_iterator it = toUnion.begin(); it != toUnion.end(); ++it) { @@ -87,6 +88,9 @@ namespace { */ void testTranslateAndIntersect(const vector<BSONObj>& toIntersect, OrderedIntervalList* oilOut, IndexBoundsBuilder::BoundsTightness* tightnessOut) { + + IndexEntry testIndex = IndexEntry(BSONObj()); + for (vector<BSONObj>::const_iterator it = toIntersect.begin(); it != toIntersect.end(); ++it) { @@ -111,6 +115,9 @@ namespace { void testTranslate(const vector< std::pair<BSONObj, bool> >& constraints, OrderedIntervalList* oilOut, IndexBoundsBuilder::BoundsTightness* tightnessOut) { + + IndexEntry testIndex = IndexEntry(BSONObj()); + for (vector< std::pair<BSONObj, bool> >::const_iterator it = constraints.begin(); it != constraints.end(); ++it) { @@ -136,6 +143,7 @@ namespace { // TEST(IndexBoundsBuilderTest, TranslateElemMatchValue) { + IndexEntry testIndex = IndexEntry(BSONObj()); // Bounds generated should be the same as the embedded expression // except for the tightness. BSONObj obj = fromjson("{a: {$elemMatch: {$gt: 2}}}"); @@ -156,6 +164,7 @@ namespace { // TEST(IndexBoundsBuilderTest, TranslateLteNumber) { + IndexEntry testIndex = IndexEntry(BSONObj()); BSONObj obj = fromjson("{a: {$lte: 1}}"); auto_ptr<MatchExpression> expr(parseMatchExpression(obj)); BSONElement elt = obj.firstElement(); @@ -170,6 +179,7 @@ namespace { } TEST(IndexBoundsBuilderTest, TranslateLteNumberMin) { + IndexEntry testIndex = IndexEntry(BSONObj()); BSONObj obj = BSON("a" << BSON("$lte" << numberMin)); auto_ptr<MatchExpression> expr(parseMatchExpression(obj)); BSONElement elt = obj.firstElement(); @@ -184,6 +194,7 @@ namespace { } TEST(IndexBoundsBuilderTest, TranslateLteNegativeInfinity) { + IndexEntry testIndex = IndexEntry(BSONObj()); BSONObj obj = fromjson("{a: {$lte: -Infinity}}"); auto_ptr<MatchExpression> expr(parseMatchExpression(obj)); BSONElement elt = obj.firstElement(); @@ -198,6 +209,7 @@ namespace { } TEST(IndexBoundsBuilderTest, TranslateLtNumber) { + IndexEntry testIndex = IndexEntry(BSONObj()); BSONObj obj = fromjson("{a: {$lt: 1}}"); auto_ptr<MatchExpression> expr(parseMatchExpression(obj)); BSONElement elt = obj.firstElement(); @@ -212,6 +224,7 @@ namespace { } TEST(IndexBoundsBuilderTest, TranslateLtNumberMin) { + IndexEntry testIndex = IndexEntry(BSONObj()); BSONObj obj = BSON("a" << BSON("$lt" << numberMin)); auto_ptr<MatchExpression> expr(parseMatchExpression(obj)); BSONElement elt = obj.firstElement(); @@ -226,6 +239,7 @@ namespace { } TEST(IndexBoundsBuilderTest, TranslateLtNegativeInfinity) { + IndexEntry testIndex = IndexEntry(BSONObj()); BSONObj obj = fromjson("{a: {$lt: -Infinity}}"); auto_ptr<MatchExpression> expr(parseMatchExpression(obj)); BSONElement elt = obj.firstElement(); @@ -238,6 +252,7 @@ namespace { } TEST(IndexBoundsBuilderTest, TranslateLtDate) { + IndexEntry testIndex = IndexEntry(BSONObj()); BSONObj obj = BSON("a" << LT << Date_t(5000)); auto_ptr<MatchExpression> expr(parseMatchExpression(obj)); BSONElement elt = obj.firstElement(); @@ -252,6 +267,7 @@ namespace { } TEST(IndexBoundsBuilderTest, TranslateGtNumber) { + IndexEntry testIndex = IndexEntry(BSONObj()); BSONObj obj = fromjson("{a: {$gt: 1}}"); auto_ptr<MatchExpression> expr(parseMatchExpression(obj)); BSONElement elt = obj.firstElement(); @@ -266,6 +282,7 @@ namespace { } TEST(IndexBoundsBuilderTest, TranslateGtNumberMax) { + IndexEntry testIndex = IndexEntry(BSONObj()); BSONObj obj = BSON("a" << BSON("$gt" << numberMax)); auto_ptr<MatchExpression> expr(parseMatchExpression(obj)); BSONElement elt = obj.firstElement(); @@ -280,6 +297,7 @@ namespace { } TEST(IndexBoundsBuilderTest, TranslateGtPositiveInfinity) { + IndexEntry testIndex = IndexEntry(BSONObj()); BSONObj obj = fromjson("{a: {$gt: Infinity}}"); auto_ptr<MatchExpression> expr(parseMatchExpression(obj)); BSONElement elt = obj.firstElement(); @@ -292,6 +310,7 @@ namespace { } TEST(IndexBoundsBuilderTest, TranslateGteNumber) { + IndexEntry testIndex = IndexEntry(BSONObj()); BSONObj obj = fromjson("{a: {$gte: 1}}"); auto_ptr<MatchExpression> expr(parseMatchExpression(obj)); BSONElement elt = obj.firstElement(); @@ -306,6 +325,7 @@ namespace { } TEST(IndexBoundsBuilderTest, TranslateGteNumberMax) { + IndexEntry testIndex = IndexEntry(BSONObj()); BSONObj obj = BSON("a" << BSON("$gte" << numberMax)); auto_ptr<MatchExpression> expr(parseMatchExpression(obj)); BSONElement elt = obj.firstElement(); @@ -320,6 +340,7 @@ namespace { } TEST(IndexBoundsBuilderTest, TranslateGtePositiveInfinity) { + IndexEntry testIndex = IndexEntry(BSONObj()); BSONObj obj = fromjson("{a: {$gte: Infinity}}"); auto_ptr<MatchExpression> expr(parseMatchExpression(obj)); BSONElement elt = obj.firstElement(); @@ -334,6 +355,7 @@ namespace { } TEST(IndexBoundsBuilderTest, TranslateGtString) { + IndexEntry testIndex = IndexEntry(BSONObj()); BSONObj obj = fromjson("{a: {$gt: 'abc'}}"); auto_ptr<MatchExpression> expr(parseMatchExpression(obj)); BSONElement elt = obj.firstElement(); @@ -348,6 +370,7 @@ namespace { } TEST(IndexBoundsBuilderTest, TranslateEqual) { + IndexEntry testIndex = IndexEntry(BSONObj()); BSONObj obj = BSON("a" << 4); auto_ptr<MatchExpression> expr(parseMatchExpression(obj)); BSONElement elt = obj.firstElement(); @@ -362,6 +385,7 @@ namespace { } TEST(IndexBoundsBuilderTest, TranslateArrayEqualBasic) { + IndexEntry testIndex = IndexEntry(BSONObj()); BSONObj obj = fromjson("{a: [1, 2, 3]}"); auto_ptr<MatchExpression> expr(parseMatchExpression(obj)); BSONElement elt = obj.firstElement(); @@ -378,6 +402,7 @@ namespace { } TEST(IndexBoundsBuilderTest, TranslateIn) { + IndexEntry testIndex = IndexEntry(BSONObj()); BSONObj obj = fromjson("{a: {$in: [8, 44, -1, -3]}}"); auto_ptr<MatchExpression> expr(parseMatchExpression(obj)); BSONElement elt = obj.firstElement(); @@ -398,6 +423,7 @@ namespace { } TEST(IndexBoundsBuilderTest, TranslateInArray) { + IndexEntry testIndex = IndexEntry(BSONObj()); BSONObj obj = fromjson("{a: {$in: [[1], 2]}}"); auto_ptr<MatchExpression> expr(parseMatchExpression(obj)); BSONElement elt = obj.firstElement(); @@ -420,6 +446,7 @@ namespace { // TEST(IndexBoundsBuilderTest, UnionTwoLt) { + IndexEntry testIndex = IndexEntry(BSONObj()); vector<BSONObj> toUnion; toUnion.push_back(fromjson("{a: {$lt: 1}}")); toUnion.push_back(fromjson("{a: {$lt: 5}}")); @@ -434,6 +461,7 @@ namespace { } TEST(IndexBoundsBuilderTest, UnionDupEq) { + IndexEntry testIndex = IndexEntry(BSONObj()); vector<BSONObj> toUnion; toUnion.push_back(fromjson("{a: 1}")); toUnion.push_back(fromjson("{a: 5}")); @@ -451,6 +479,7 @@ namespace { } TEST(IndexBoundsBuilderTest, UnionGtLt) { + IndexEntry testIndex = IndexEntry(BSONObj()); vector<BSONObj> toUnion; toUnion.push_back(fromjson("{a: {$gt: 1}}")); toUnion.push_back(fromjson("{a: {$lt: 3}}")); @@ -465,6 +494,7 @@ namespace { } TEST(IndexBoundsBuilderTest, UnionTwoEmptyRanges) { + IndexEntry testIndex = IndexEntry(BSONObj()); vector< std::pair<BSONObj, bool> > constraints; constraints.push_back(std::make_pair(fromjson("{a: {$gt: 1}}"), true)); constraints.push_back(std::make_pair(fromjson("{a: {$lte: 0}}"), true)); @@ -481,6 +511,7 @@ namespace { // TEST(IndexBoundsBuilderTest, IntersectTwoLt) { + IndexEntry testIndex = IndexEntry(BSONObj()); vector<BSONObj> toIntersect; toIntersect.push_back(fromjson("{a: {$lt: 1}}")); toIntersect.push_back(fromjson("{a: {$lt: 5}}")); @@ -495,6 +526,7 @@ namespace { } TEST(IndexBoundsBuilderTest, IntersectEqGte) { + IndexEntry testIndex = IndexEntry(BSONObj()); vector<BSONObj> toIntersect; toIntersect.push_back(fromjson("{a: 1}}")); toIntersect.push_back(fromjson("{a: {$gte: 1}}")); @@ -509,6 +541,7 @@ namespace { } TEST(IndexBoundsBuilderTest, IntersectGtLte) { + IndexEntry testIndex = IndexEntry(BSONObj()); vector<BSONObj> toIntersect; toIntersect.push_back(fromjson("{a: {$gt: 0}}")); toIntersect.push_back(fromjson("{a: {$lte: 10}}")); @@ -523,6 +556,7 @@ namespace { } TEST(IndexBoundsBuilderTest, IntersectGtIn) { + IndexEntry testIndex = IndexEntry(BSONObj()); vector<BSONObj> toIntersect; toIntersect.push_back(fromjson("{a: {$gt: 4}}")); toIntersect.push_back(fromjson("{a: {$in: [1,2,3,4,5,6]}}")); @@ -539,6 +573,7 @@ namespace { } TEST(IndexBoundsBuilderTest, IntersectionIsPointInterval) { + IndexEntry testIndex = IndexEntry(BSONObj()); vector<BSONObj> toIntersect; toIntersect.push_back(fromjson("{a: {$gte: 1}}")); toIntersect.push_back(fromjson("{a: {$lte: 1}}")); @@ -553,6 +588,7 @@ namespace { } TEST(IndexBoundsBuilderTest, IntersectFullyContained) { + IndexEntry testIndex = IndexEntry(BSONObj()); vector<BSONObj> toIntersect; toIntersect.push_back(fromjson("{a: {$gt: 5}}")); toIntersect.push_back(fromjson("{a: {$lt: 15}}")); @@ -569,6 +605,7 @@ namespace { } TEST(IndexBoundsBuilderTest, EmptyIntersection) { + IndexEntry testIndex = IndexEntry(BSONObj()); vector<BSONObj> toIntersect; toIntersect.push_back(fromjson("{a: 1}}")); toIntersect.push_back(fromjson("{a: {$gte: 2}}")); @@ -584,6 +621,7 @@ namespace { // TEST(IndexBoundsBuilderTest, TranslateMod) { + IndexEntry testIndex = IndexEntry(BSONObj()); BSONObj obj = fromjson("{a: {$mod: [2, 0]}}"); auto_ptr<MatchExpression> expr(parseMatchExpression(obj)); BSONElement elt = obj.firstElement(); @@ -719,6 +757,7 @@ namespace { // TEST(IndexBoundsBuilderTest, SimpleNonPrefixRegex) { + IndexEntry testIndex = IndexEntry(BSONObj()); BSONObj obj = fromjson("{a: /foo/}"); auto_ptr<MatchExpression> expr(parseMatchExpression(obj)); BSONElement elt = obj.firstElement(); @@ -734,6 +773,7 @@ namespace { } TEST(IndexBoundsBuilderTest, SimplePrefixRegex) { + IndexEntry testIndex = IndexEntry(BSONObj()); BSONObj obj = fromjson("{a: /^foo/}"); auto_ptr<MatchExpression> expr(parseMatchExpression(obj)); BSONElement elt = obj.firstElement(); diff --git a/src/mongo/db/query/index_entry.h b/src/mongo/db/query/index_entry.h index 04e60e9d999..06eee6c3508 100644 --- a/src/mongo/db/query/index_entry.h +++ b/src/mongo/db/query/index_entry.h @@ -30,11 +30,25 @@ #include <string> +#include "mongo/db/index_names.h" #include "mongo/db/jsobj.h" namespace mongo { /** + * We need to know what 'type' an index is in order to plan correctly. + * Rather than look this up repeatedly we figure it out once. + */ + enum IndexType { + INDEX_BTREE, + INDEX_2D, + INDEX_HAYSTACK, + INDEX_2DSPHERE, + INDEX_TEXT, + INDEX_HASHED, + }; + + /** * This name sucks, but every name involving 'index' is used somewhere. */ struct IndexEntry { @@ -47,7 +61,40 @@ namespace mongo { multikey(mk), sparse(sp), name(n), - infoObj(io) { } + infoObj(io) { + + // XXX: This is the wrong way to set this and this is dangerous. We need to check in + // the catalog to see if we should override the plugin name instead of just grabbing it + // directly from the key pattern. Move this a level higher to wherever IndexEntry is + // created. + // + // An example of the Bad Thing That We Must Avoid: + // 1. Create a 2dsphere index in 2.4, insert some docs. + // 2. Downgrade to 2.2. Insert some more docs into the collection w/the 2dsphere + // index. 2.2 treats the index as a normal btree index and creates keys accordingly. + // 3. Using the 2dsphere index in 2.4 gives wrong results or assert-fails or crashes as + // the data isn't what we expect. + string typeStr = IndexNames::findPluginName(keyPattern); + + if (IndexNames::GEO_2D == typeStr) { + type = INDEX_2D; + } + else if (IndexNames::GEO_HAYSTACK == typeStr) { + type = INDEX_HAYSTACK; + } + else if (IndexNames::GEO_2DSPHERE == typeStr) { + type = INDEX_2DSPHERE; + } + else if (IndexNames::TEXT == typeStr) { + type = INDEX_TEXT; + } + else if (IndexNames::HASHED == typeStr) { + type = INDEX_HASHED; + } + else { + type = INDEX_BTREE; + } + } BSONObj keyPattern; @@ -60,6 +107,10 @@ namespace mongo { // Geo indices have extra parameters. We need those available to plan correctly. BSONObj infoObj; + // What type of index is this? (What access method can we use on the index described + // by the keyPattern?) + IndexType type; + std::string toString() const { mongoutils::str::stream ss; ss << "kp: " << keyPattern.toString(); diff --git a/src/mongo/db/query/plan_enumerator.cpp b/src/mongo/db/query/plan_enumerator.cpp index 5058340abd1..9e8798db014 100644 --- a/src/mongo/db/query/plan_enumerator.cpp +++ b/src/mongo/db/query/plan_enumerator.cpp @@ -327,7 +327,7 @@ namespace mongo { // If the index is multikey, we only assign one pred to it. We also skip // compounding. TODO: is this also true for 2d and 2dsphere indices? can they be // multikey but still compoundable? (How do we get covering for them?) - if (thisIndex.multikey) { + if (thisIndex.multikey && (INDEX_TEXT != thisIndex.type)) { indexAssign.preds.push_back(it->second[0]); indexAssign.positions.push_back(0); } diff --git a/src/mongo/db/query/planner_access.cpp b/src/mongo/db/query/planner_access.cpp index 6929f520f12..b357a8c6031 100644 --- a/src/mongo/db/query/planner_access.cpp +++ b/src/mongo/db/query/planner_access.cpp @@ -142,9 +142,9 @@ namespace mongo { *tightnessOut = IndexBoundsBuilder::EXACT; TextMatchExpression* textExpr = static_cast<TextMatchExpression*>(expr); TextNode* ret = new TextNode(); - ret->_indexKeyPattern = index.keyPattern; - ret->_query = textExpr->getQuery(); - ret->_language = textExpr->getLanguage(); + ret->indexKeyPattern = index.keyPattern; + ret->query = textExpr->getQuery(); + ret->language = textExpr->getLanguage(); return ret; } else { @@ -218,20 +218,28 @@ namespace mongo { } - void QueryPlannerAccess::mergeWithLeafNode(MatchExpression* expr, const IndexEntry& index, - size_t pos, IndexBoundsBuilder::BoundsTightness* tightnessOut, - QuerySolutionNode* node, MatchExpression::MatchType mergeType) { + 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 (??). + if (STAGE_GEO_2D == type) { *tightnessOut = IndexBoundsBuilder::INEXACT_FETCH; return; } + // Text data is covered, but not exactly. Text covering is unlike any other covering + // so we deal with it in _addFilterToSolutionNode. + if (STAGE_TEXT == type) { + *tightnessOut = IndexBoundsBuilder::INEXACT_COVERED; + return; + } + IndexBounds* boundsToFillOut = NULL; if (STAGE_GEO_NEAR_2DSPHERE == type) { @@ -255,12 +263,6 @@ namespace mongo { 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]; @@ -280,11 +282,115 @@ namespace mongo { } // static + void QueryPlannerAccess::finishTextNode(QuerySolutionNode* node, const IndexEntry& index) { + TextNode* tn = static_cast<TextNode*>(node); + + // Figure out what positions are prefix positions. We build an index key prefix from + // the predicates over the text index prefix keys. + // For example, say keyPattern = { a: 1, _fts: "text", _ftsx: 1, b: 1 } + // prefixEnd should be 1. + size_t prefixEnd = 0; + BSONObjIterator it(tn->indexKeyPattern); + // Count how many prefix terms we have. + while (it.more()) { + // We know that the only key pattern with a type of String is the _fts field + // which is immediately after all prefix fields. + if (String == it.next().type()) { + break; + } + ++prefixEnd; + } + + // If there's no prefix, the filter is already on the node and the index prefix is null. + // We can just return. + if (!prefixEnd) { + return; + } + + // We can't create a text stage if there aren't EQ predicates on its prefix terms. So + // if we've made it this far, we should have collected the prefix predicates in the + // filter. + invariant(NULL != tn->filter.get()); + MatchExpression* textFilterMe = tn->filter.get(); + + BSONObjBuilder prefixBob; + + if (MatchExpression::AND != textFilterMe->matchType()) { + // Only one prefix term. + invariant(1 == prefixEnd); + // Sanity check: must be an EQ. + invariant(MatchExpression::EQ == textFilterMe->matchType()); + + EqualityMatchExpression* eqExpr = static_cast<EqualityMatchExpression*>(textFilterMe); + prefixBob.append(eqExpr->getData()); + tn->filter.reset(); + } + else { + invariant(MatchExpression::AND == textFilterMe->matchType()); + + // Indexed by the keyPattern position index assignment. We want to add + // prefixes in order but we must order them first. + vector<MatchExpression*> prefixExprs(prefixEnd, NULL); + + AndMatchExpression* amExpr = static_cast<AndMatchExpression*>(textFilterMe); + invariant(amExpr->numChildren() >= prefixEnd); + + // Look through the AND children. The prefix children we want to + // stash in prefixExprs. + size_t curChild = 0; + while (curChild < amExpr->numChildren()) { + MatchExpression* child = amExpr->getChild(curChild); + IndexTag* ixtag = static_cast<IndexTag*>(child->getTag()); + invariant(NULL != ixtag); + // Only want prefixes. + if (ixtag->pos >= prefixEnd) { + ++curChild; + continue; + } + prefixExprs[ixtag->pos] = child; + amExpr->getChildVector()->erase(amExpr->getChildVector()->begin() + curChild); + // Don't increment curChild. + } + + // Go through the prefix equalities in order and create an index prefix out of them. + for (size_t i = 0; i < prefixExprs.size(); ++i) { + MatchExpression* prefixMe = prefixExprs[i]; + invariant(NULL != prefixMe); + invariant(MatchExpression::EQ == prefixMe->matchType()); + EqualityMatchExpression* eqExpr = static_cast<EqualityMatchExpression*>(prefixMe); + prefixBob.append(eqExpr->getData()); + // We removed this from the AND expression that owned it, so we must clean it + // up ourselves. + delete prefixMe; + } + + // Clear out an empty $and. + if (0 == amExpr->numChildren()) { + tn->filter.reset(); + } + else if (1 == amExpr->numChildren()) { + // Clear out unsightly only child of $and + MatchExpression* child = amExpr->getChild(0); + amExpr->getChildVector()->clear(); + // Deletes current filter which is amExpr. + tn->filter.reset(child); + } + } + + tn->indexPrefix = prefixBob.obj(); + } + + // 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) { + if (STAGE_GEO_2D == type) { + return; + } + + if (STAGE_TEXT == type) { + finishTextNode(node, index); return; } @@ -548,7 +654,8 @@ namespace mongo { delete child; } else if (tightness == IndexBoundsBuilder::INEXACT_COVERED - && !indices[currentIndexNumber].multikey) { + && (INDEX_TEXT == indices[currentIndexNumber].type + || !indices[currentIndexNumber].multikey)) { // 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 @@ -1016,25 +1123,23 @@ namespace mongo { } // 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()) { + // The 'node' already has either an AND or OR filter that matches 'type'. Add 'match' as + // another branch of the filter. ListOfMatchExpression* listFilter = static_cast<ListOfMatchExpression*>(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 { + // 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. ListOfMatchExpression* listFilter; if (MatchExpression::AND == type) { listFilter = new AndMatchExpression(); diff --git a/src/mongo/db/query/planner_access.h b/src/mongo/db/query/planner_access.h index 1a2756ea434..f2d002ae370 100644 --- a/src/mongo/db/query/planner_access.h +++ b/src/mongo/db/query/planner_access.h @@ -239,9 +239,12 @@ namespace mongo { * 'node' with the "all values for this field" interval. * * If geo, do nothing. + * If text, punt to finishTextNode. */ static void finishLeafNode(QuerySolutionNode* node, const IndexEntry& index); + static void finishTextNode(QuerySolutionNode* node, const IndexEntry& index); + private: /** * Add the filter 'match' to the query solution node 'node'. Takes diff --git a/src/mongo/db/query/planner_ixselect.cpp b/src/mongo/db/query/planner_ixselect.cpp index b4618b07b88..1430724f917 100644 --- a/src/mongo/db/query/planner_ixselect.cpp +++ b/src/mongo/db/query/planner_ixselect.cpp @@ -32,6 +32,7 @@ #include "mongo/db/geo/core.h" #include "mongo/db/geo/hash.h" +#include "mongo/db/index_names.h" #include "mongo/db/matcher/expression_array.h" #include "mongo/db/matcher/expression_geo.h" #include "mongo/db/matcher/expression_text.h" @@ -49,22 +50,6 @@ namespace mongo { } /** - * XXX: we should figure this kind of data out once and put it in - * the IndexEntry. - */ - static bool isTextIndex(const BSONObj& pattern) { - BSONObjIterator it(pattern); - while (it.more()) { - BSONElement e = it.next(); - if (String == e.type() && str::equals("text", e.valuestr())) { - return true; - } - } - return false; - } - - - /** * 2d indices don't handle wrapping so we can't use them for queries that wrap. */ static bool twoDWontWrap(const Circle& circle, const IndexEntry& index) { @@ -142,20 +127,26 @@ namespace mongo { bool QueryPlannerIXSelect::compatible(const BSONElement& elt, const IndexEntry& index, MatchExpression* node) { - // XXX: CatalogHack::getAccessMethodName: do we have to worry about this? when? - string ixtype; - if (String != elt.type()) { - ixtype = ""; + // Historically one could create indices with any particular value for the index spec, + // including values that now indicate a special index. As such we have to make sure the + // index type wasn't overridden before we pay attention to the string in the index key + // pattern element. + // + // e.g. long ago we could have created an index {a: "2dsphere"} and it would + // be treated as a btree index by an ancient version of MongoDB. To try to run + // 2dsphere queries over it would be folly. + string indexedFieldType; + if (String != elt.type() || (INDEX_BTREE == index.type)) { + indexedFieldType = ""; } else { - ixtype = elt.String(); + indexedFieldType = elt.String(); } // We know elt.fieldname() == node->path(). MatchExpression::MatchType exprtype = node->matchType(); - // TODO: use indexnames - if ("" == ixtype) { + if (indexedFieldType.empty()) { // Can't check for null w/a sparse index. if (exprtype == MatchExpression::EQ && index.sparse) { const EqualityMatchExpression* expr @@ -178,8 +169,7 @@ namespace mongo { // - Allowed: node = {a: 7} // - Not allowed: node = {a: {$gt: 7}} - // TODO: Cache isTextIndex somewhere. - if (!isTextIndex(index.keyPattern)) { + if (INDEX_TEXT != index.type) { return true; } @@ -213,10 +203,10 @@ namespace mongo { invariant(0); return true; } - else if ("hashed" == ixtype) { + else if (IndexNames::HASHED == indexedFieldType) { return exprtype == MatchExpression::MATCH_IN || exprtype == MatchExpression::EQ; } - else if ("2dsphere" == ixtype) { + else if (IndexNames::GEO_2DSPHERE == indexedFieldType) { if (exprtype == MatchExpression::GEO) { // within or intersect. GeoMatchExpression* gme = static_cast<GeoMatchExpression*>(node); @@ -233,7 +223,7 @@ namespace mongo { } return false; } - else if ("2d" == ixtype) { + else if (IndexNames::GEO_2D == indexedFieldType) { if (exprtype == MatchExpression::GEO_NEAR) { GeoNearMatchExpression* gnme = static_cast<GeoNearMatchExpression*>(node); return gnme->getData().centroid.crs == FLAT; @@ -266,10 +256,10 @@ namespace mongo { } return false; } - else if ("text" == ixtype) { + else if (IndexNames::TEXT == indexedFieldType) { return (exprtype == MatchExpression::TEXT); } - else if ("geoHaystack" == ixtype) { + else if (IndexNames::GEO_HAYSTACK == indexedFieldType) { return false; } else { @@ -452,7 +442,7 @@ namespace mongo { const IndexEntry& index = indices[i]; // We only care about text indices. - if (!isTextIndex(index.keyPattern)) { + if (INDEX_TEXT != index.type) { continue; } diff --git a/src/mongo/db/query/query_planner_test_lib.cpp b/src/mongo/db/query/query_planner_test_lib.cpp index bff98d7c373..176582bbb01 100644 --- a/src/mongo/db/query/query_planner_test_lib.cpp +++ b/src/mongo/db/query/query_planner_test_lib.cpp @@ -278,14 +278,25 @@ namespace mongo { BSONElement searchElt = textObj["search"]; if (!searchElt.eoo()) { - if (searchElt.String() != node->_query) { + if (searchElt.String() != node->query) { return false; } } BSONElement languageElt = textObj["language"]; if (!languageElt.eoo()) { - if (languageElt.String() != node->_language) { + if (languageElt.String() != node->language) { + return false; + } + } + + BSONElement indexPrefix = textObj["prefix"]; + if (!indexPrefix.eoo()) { + if (!indexPrefix.isABSONObj()) { + return false; + } + + if (0 != indexPrefix.Obj().woCompare(node->indexPrefix)) { return false; } } diff --git a/src/mongo/db/query/query_planner_text_test.cpp b/src/mongo/db/query/query_planner_text_test.cpp index 40aacc28d37..fe17fea5ca2 100644 --- a/src/mongo/db/query/query_planner_text_test.cpp +++ b/src/mongo/db/query/query_planner_text_test.cpp @@ -332,7 +332,7 @@ namespace { runQuery(fromjson("{a:1, $text:{$search: 'blah'}}")); assertNumSolutions(1); - assertSolutionExists("{fetch: {filter: {a:1}, node: {text: {search: 'blah'}}}}"); + assertSolutionExists("{text: {prefix: {a:1}, search: 'blah'}}}}"); // TODO: Do we want to $or a collection scan with a text search? // runQuery(fromjson("{$or: [{b:1}, {a:1, $text: {$search: 'blah'}}]}")); @@ -363,8 +363,7 @@ namespace { // Both points. runQuery(fromjson("{a:1, b:1, $text:{$search: 'blah'}}")); - // XXX: when text is covered there should be no filter and can check bounds. - assertSolutionExists("{fetch: {filter: {a:1, b:1}, node:{text: {search: 'blah'}}}}"); + assertSolutionExists("{text: {prefix: {a:1, b:1}, search: 'blah'}}"); assertNumSolutions(1); // Missing a. @@ -391,11 +390,10 @@ namespace { runQuery(fromjson("{a:1, $text:{$search: 'blah'}}")); assertNumSolutions(1); - // XXX: when text is covered there can check bounds, filter exists but can grab - // data from key for suffix 'b'. - assertSolutionExists("{fetch: {filter: {a:1}, node: {text: {search: 'blah'}}}}"); + assertSolutionExists("{text: {prefix: {a:1}, search: 'blah'}}}}"); runQuery(fromjson("{a:1, b:{$gt: 7}, $text:{$search: 'blah'}}")); + assertSolutionExists("{text: {prefix: {a:1}, filter: {b: {$gt: 7}}, search: 'blah'}}}}"); assertNumSolutions(1); } @@ -406,14 +404,12 @@ namespace { runQuery(fromjson("{a:1, $or: [{a:1}, {b:7}], $text:{$search: 'blah'}}")); assertNumSolutions(1); - // XXX: when text is covered there should be no filter and can check bounds. - assertSolutionExists("{fetch: {node: {text: {search: 'blah'}}}}"); + assertSolutionExists("{fetch: {filter: {$or:[{a:1},{b:7}]}," + "node: {text: {prefix: {a:1}, search: 'blah'}}}}"); } // Text is quite often multikey. None of the prefixes can be arrays, and suffixes are indexed // as-is, so we should compound even if it's multikey. - // TODO: Uncomment when SERVER-12144 is done. - /* TEST_F(QueryPlannerTest, CompoundPrefixEvenIfMultikey) { params.options = QueryPlannerParams::NO_TABLE_SCAN; addIndex(BSON("a" << 1 << "b" << 1 << "_fts" << "text" << "_ftsx" << 1), true); @@ -421,12 +417,8 @@ namespace { // Both points. runQuery(fromjson("{a:1, b:1, $text:{$search: 'blah'}}")); assertNumSolutions(1); - - // XXX: we should test the bounds and also assert that there is no filter since it's - // covered. - assertSolutionExists("{text: {search: 'blah'}}"); + assertSolutionExists("{text: {prefix: {a:1, b:1}, search: 'blah'}}"); } - */ TEST_F(QueryPlannerTest, IndexOnOwnFieldButNotLeafPrefix) { params.options = QueryPlannerParams::NO_TABLE_SCAN; diff --git a/src/mongo/db/query/query_solution.cpp b/src/mongo/db/query/query_solution.cpp index dfcb69e2362..80b1182b0f7 100644 --- a/src/mongo/db/query/query_solution.cpp +++ b/src/mongo/db/query/query_solution.cpp @@ -65,11 +65,13 @@ namespace mongo { addIndent(ss, indent); *ss << "TEXT\n"; addIndent(ss, indent + 1); - *ss << "keyPattern = " << _indexKeyPattern.toString() << '\n'; + *ss << "keyPattern = " << indexKeyPattern.toString() << '\n'; + addIndent(ss, indent + 1); + *ss << "query = " << query << '\n'; addIndent(ss, indent + 1); - *ss << "query = " << _query << '\n'; + *ss << "language = " << language << '\n'; addIndent(ss, indent + 1); - *ss << "language = " << _language << '\n'; + *ss << "indexPrefix = " << indexPrefix.toString() << '\n'; if (NULL != filter) { addIndent(ss, indent + 1); *ss << " filter = " << filter->toString(); diff --git a/src/mongo/db/query/query_solution.h b/src/mongo/db/query/query_solution.h index 579ef99bb90..d28cf4a6766 100644 --- a/src/mongo/db/query/query_solution.h +++ b/src/mongo/db/query/query_solution.h @@ -204,9 +204,14 @@ namespace mongo { BSONObjSet _sort; - BSONObj _indexKeyPattern; - std::string _query; - std::string _language; + BSONObj indexKeyPattern; + std::string query; + std::string language; + + // "Prefix" fields of a text index can handle equality predicates. We group them with the + // text node while creating the text leaf node and convert them into a BSONObj index prefix + // when we finish the text leaf node. + BSONObj indexPrefix; }; struct CollectionScanNode : public QuerySolutionNode { diff --git a/src/mongo/db/query/stage_builder.cpp b/src/mongo/db/query/stage_builder.cpp index d5762db824d..21ce26e2bf1 100644 --- a/src/mongo/db/query/stage_builder.cpp +++ b/src/mongo/db/query/stage_builder.cpp @@ -222,24 +222,17 @@ namespace mongo { params.ns = qsol.ns; params.index = index; params.spec = fam->getSpec(); - // XXX change getIndexPrefix to not look at BSONObj - Status s = fam->getSpec().getIndexPrefix(qsol.filterData, ¶ms.indexPrefix); - if (!s.isOK()) { - warning() << "can't get text index prefix??"; - return NULL; - } + params.indexPrefix = node->indexPrefix; - const std::string& language = ("" == node->_language + const std::string& language = ("" == node->language ? fam->getSpec().defaultLanguage().str() - : node->_language); + : node->language); - FTSQuery ftsq; - Status parseStatus = ftsq.parse(node->_query, language); + Status parseStatus = params.query.parse(node->query, language); if (!parseStatus.isOK()) { warning() << "cant parse fts query"; return NULL; } - params.query = ftsq; return new TextStage(params, ws, node->filter.get()); } |