summaryrefslogtreecommitdiff
path: root/src/mongo/db/query/plan_enumerator.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/db/query/plan_enumerator.cpp')
-rw-r--r--src/mongo/db/query/plan_enumerator.cpp76
1 files changed, 56 insertions, 20 deletions
diff --git a/src/mongo/db/query/plan_enumerator.cpp b/src/mongo/db/query/plan_enumerator.cpp
index 7016d57b043..04bc0d93ec8 100644
--- a/src/mongo/db/query/plan_enumerator.cpp
+++ b/src/mongo/db/query/plan_enumerator.cpp
@@ -396,8 +396,24 @@ bool PlanEnumerator::prepMemo(MatchExpression* node, PrepMemoContext context) {
// Extend the path through the indexed ORs of each outside predicate.
auto childContextCopy = childContext;
- for (auto& pred : childContextCopy.outsidePreds) {
- pred.second.push_back(i);
+ for (auto it = childContextCopy.outsidePreds.begin();
+ it != childContextCopy.outsidePreds.end();) {
+ // If the route has already traversed through an $elemMatch object, then we cannot
+ // push down through this OR. Here we remove such routes from our context object.
+ //
+ // For example, suppose we have index {a: 1, "b.c": 1} and the following query:
+ //
+ // {a: 1, b: {$elemMatch: {$or: [{c: 2}, {c: 3}]}}}
+ //
+ // It is not correct to push the 'a' predicate down such that it is a sibling of
+ // either of the predicates on 'c', since this would change the predicate's meaning
+ // from a==1 to "b.a"==1.
+ if (it->second.traversedThroughElemMatchObj) {
+ it = childContextCopy.outsidePreds.erase(it);
+ } else {
+ it->second.route.push_back(i);
+ ++it;
+ }
}
if (!prepMemo(node->getChild(i), childContextCopy)) {
@@ -423,6 +439,7 @@ bool PlanEnumerator::prepMemo(MatchExpression* node, PrepMemoContext context) {
if (MatchExpression::ELEM_MATCH_OBJECT == node->matchType()) {
childContext.elemMatchExpr = node;
+ markTraversedThroughElemMatchObj(&childContext);
}
// For an OR to be indexed, all its children must be indexed.
@@ -462,23 +479,22 @@ bool PlanEnumerator::prepMemo(MatchExpression* node, PrepMemoContext context) {
// (e.g. an OR which contains a TEXT child).
vector<MemoID> mandatorySubnodes;
- // A list of predicates contained in the subtree rooted at 'node'
- // obtained by traversing deeply through $and and $elemMatch children.
- vector<MatchExpression*> indexedPreds;
+ // A list of predicates contained in the subtree rooted at 'node' obtained by traversing
+ // deeply through $and and $elemMatch children.
+ std::vector<MatchExpression*> indexedPreds;
- // Partition the childen into the children that aren't predicates which may or may
- // not be indexed ('subnodes'), children that aren't predicates which must use the
- // index ('mandatorySubnodes'). and children that are predicates ('indexedPreds').
+ // Partition the childen into the children that aren't predicates which may or may not be
+ // indexed ('subnodes'), children that aren't predicates which must use the index
+ // ('mandatorySubnodes'). and children that are predicates ('indexedPreds').
//
- // We have to get the subnodes with mandatory assignments rather than adding the
- // mandatory preds to 'indexedPreds'. Adding the mandatory preds directly to
- // 'indexedPreds' would lead to problems such as pulling a predicate beneath an OR
- // into a set joined by an AND.
+ // We have to get the subnodes with mandatory assignments rather than adding the mandatory
+ // preds to 'indexedPreds'. Adding the mandatory preds directly to 'indexedPreds' would lead
+ // to problems such as pulling a predicate beneath an OR into a set joined by an AND.
getIndexedPreds(node, childContext, &indexedPreds);
// Pass in the indexed predicates as outside predicates when prepping the subnodes.
auto childContextCopy = childContext;
for (auto pred : indexedPreds) {
- childContextCopy.outsidePreds[pred] = std::deque<size_t>();
+ childContextCopy.outsidePreds[pred] = OutsidePredRoute{};
}
if (!prepSubNodes(node, childContextCopy, &subnodes, &mandatorySubnodes)) {
return false;
@@ -667,14 +683,14 @@ bool PlanEnumerator::enumerateMandatoryIndex(const IndexToPredMap& idxToFirst,
// Assign any predicates on the non-leading index fields to 'indexAssign' that
// don't violate the intersecting or compounding rules for multikey indexes.
// We do not currently try to assign outside predicates to mandatory indexes.
- const unordered_map<MatchExpression*, std::deque<size_t>> outsidePreds{};
+ const unordered_map<MatchExpression*, OutsidePredRoute> outsidePreds{};
assignMultikeySafePredicates(compIt->second, outsidePreds, &indexAssign);
}
} else {
// Assign any predicates on the leading index field to 'indexAssign' that don't
// violate the intersecting rules for multikey indexes.
// We do not currently try to assign outside predicates to mandatory indexes.
- const unordered_map<MatchExpression*, std::deque<size_t>> outsidePreds{};
+ const unordered_map<MatchExpression*, OutsidePredRoute> outsidePreds{};
assignMultikeySafePredicates(predsOverLeadingField, outsidePreds, &indexAssign);
// Assign the mandatory predicate to 'thisIndex'. Due to how keys are generated for
@@ -778,13 +794,13 @@ bool PlanEnumerator::enumerateMandatoryIndex(const IndexToPredMap& idxToFirst,
}
void PlanEnumerator::assignPredicate(
- const unordered_map<MatchExpression*, std::deque<size_t>>& outsidePreds,
+ const unordered_map<MatchExpression*, OutsidePredRoute>& outsidePreds,
MatchExpression* pred,
size_t position,
OneIndexAssignment* indexAssignment) {
if (outsidePreds.find(pred) != outsidePreds.end()) {
OrPushdownTag::Destination dest;
- dest.route = outsidePreds.at(pred);
+ dest.route = outsidePreds.at(pred).route;
// This method should only be called if we can combine bounds.
const bool canCombineBounds = true;
@@ -797,11 +813,30 @@ void PlanEnumerator::assignPredicate(
}
}
+void PlanEnumerator::markTraversedThroughElemMatchObj(PrepMemoContext* context) {
+ invariant(context);
+ for (auto&& pred : context->outsidePreds) {
+ auto relevantTag = static_cast<RelevantTag*>(pred.first->getTag());
+ // Only indexed predicates should ever be considered as outside predicates eligible for
+ // pushdown.
+ invariant(relevantTag);
+
+ // Check whether the current $elemMatch through which we are traversing is the same as the
+ // outside predicate's $elemMatch context. If so, then that outside predicate hasn't
+ // actually traversed through an $elemMatch (it has simply been promoted by
+ // getIndexedPreds() into the set of AND-related indexed predicates). If not, then the OR
+ // pushdown route descends through an $elemMatch object node, and must be marked as such.
+ if (relevantTag->elemMatchExpr != context->elemMatchExpr) {
+ pred.second.traversedThroughElemMatchObj = true;
+ }
+ }
+}
+
void PlanEnumerator::enumerateOneIndex(
IndexToPredMap idxToFirst,
IndexToPredMap idxToNotFirst,
const vector<MemoID>& subnodes,
- const unordered_map<MatchExpression*, std::deque<size_t>>& outsidePreds,
+ const unordered_map<MatchExpression*, OutsidePredRoute>& outsidePreds,
AndAssignment* andAssignment) {
// Each choice in the 'andAssignment' will consist of a single subnode to index (an OR or array
// operator) or a OneIndexAssignment. When creating a OneIndexAssignment, we ensure that at
@@ -1203,7 +1238,7 @@ void PlanEnumerator::enumerateAndIntersect(const IndexToPredMap& idxToFirst,
void PlanEnumerator::getIndexedPreds(MatchExpression* node,
PrepMemoContext context,
- vector<MatchExpression*>* indexedPreds) {
+ std::vector<MatchExpression*>* indexedPreds) {
if (Indexability::nodeCanUseIndexOnOwnField(node)) {
RelevantTag* rt = static_cast<RelevantTag*>(node->getTag());
if (context.elemMatchExpr) {
@@ -1261,6 +1296,7 @@ bool PlanEnumerator::prepSubNodes(MatchExpression* node,
PrepMemoContext childContext;
childContext.elemMatchExpr = child;
childContext.outsidePreds = context.outsidePreds;
+ markTraversedThroughElemMatchObj(&childContext);
prepSubNodes(child, childContext, subnodesOut, mandatorySubnodes);
} else if (MatchExpression::AND == child->matchType()) {
prepSubNodes(child, context, subnodesOut, mandatorySubnodes);
@@ -1351,7 +1387,7 @@ void PlanEnumerator::getMultikeyCompoundablePreds(const vector<MatchExpression*>
void PlanEnumerator::assignMultikeySafePredicates(
const std::vector<MatchExpression*>& couldAssign,
- const unordered_map<MatchExpression*, std::deque<size_t>>& outsidePreds,
+ const unordered_map<MatchExpression*, OutsidePredRoute>& outsidePreds,
OneIndexAssignment* indexAssignment) {
invariant(indexAssignment);
invariant(indexAssignment->preds.size() == indexAssignment->positions.size());