summaryrefslogtreecommitdiff
path: root/src/mongo/db/query
diff options
context:
space:
mode:
authorHari Khalsa <hkhalsa@10gen.com>2014-02-10 23:32:24 -0500
committerHari Khalsa <hkhalsa@10gen.com>2014-02-13 15:01:54 -0500
commitabc8fd203e7f3e031bc991e27cf36128e9f5792a (patch)
tree3d85280aa5ebbd3b371368504f2aba448baedff4 /src/mongo/db/query
parent61b71b0c7b0c4f2a7dbe72d7cf773a5ce1fbf44a (diff)
downloadmongo-abc8fd203e7f3e031bc991e27cf36128e9f5792a.tar.gz
SERVER-12354 SERVER-12144 plan compound text correctly
Diffstat (limited to 'src/mongo/db/query')
-rw-r--r--src/mongo/db/query/SConscript2
-rw-r--r--src/mongo/db/query/explain_plan.cpp2
-rw-r--r--src/mongo/db/query/index_bounds_builder_test.cpp44
-rw-r--r--src/mongo/db/query/index_entry.h53
-rw-r--r--src/mongo/db/query/plan_enumerator.cpp2
-rw-r--r--src/mongo/db/query/planner_access.cpp153
-rw-r--r--src/mongo/db/query/planner_access.h3
-rw-r--r--src/mongo/db/query/planner_ixselect.cpp52
-rw-r--r--src/mongo/db/query/query_planner_test_lib.cpp15
-rw-r--r--src/mongo/db/query/query_planner_text_test.cpp22
-rw-r--r--src/mongo/db/query/query_solution.cpp8
-rw-r--r--src/mongo/db/query/query_solution.h11
-rw-r--r--src/mongo/db/query/stage_builder.cpp15
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, &params.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());
}