Skip to content

Commit d747204

Browse files
committed
SERVER-23112 Assign predicates to 2dsphere indexes using multikey paths.
The metadata in the IndexEntry struct indicates what prefixes of the indexed fields cause the index to be multikey. This information is used to get tighter bounds by assigning additional predicates to the index.
1 parent 4454864 commit d747204

File tree

4 files changed

+497
-12
lines changed

4 files changed

+497
-12
lines changed

src/mongo/db/query/plan_enumerator.cpp

Lines changed: 61 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -615,7 +615,58 @@ bool PlanEnumerator::enumerateMandatoryIndex(const IndexToPredMap& idxToFirst,
615615

616616
const vector<MatchExpression*>& predsOverLeadingField = it->second;
617617

618-
if (thisIndex.multikey) {
618+
if (thisIndex.multikey && thisIndex.multikeyPaths) {
619+
// 2dsphere indexes are the only special index type that should ever have path-level
620+
// multikey information.
621+
invariant(INDEX_2DSPHERE == thisIndex.type);
622+
623+
if (predsOverLeadingField.end() != std::find(predsOverLeadingField.begin(),
624+
predsOverLeadingField.end(),
625+
mandatoryPred)) {
626+
// The mandatory predicate is on the leading field of 'thisIndex'. We assign it to
627+
// 'thisIndex' and skip assigning any other predicates on the leading field to
628+
// 'thisIndex' because no additional predicate on the leading field will generate a
629+
// more efficient data access plan.
630+
indexAssign.preds.push_back(mandatoryPred);
631+
indexAssign.positions.push_back(0);
632+
633+
auto compIt = idxToNotFirst.find(indexAssign.index);
634+
if (compIt != idxToNotFirst.end()) {
635+
// Assign any predicates on the non-leading index fields to 'indexAssign' that
636+
// don't violate the intersecting or compounding rules for multikey indexes.
637+
assignMultikeySafePredicates(compIt->second, &indexAssign);
638+
}
639+
} else {
640+
// Assign any predicates on the leading index field to 'indexAssign' that don't
641+
// violate the intersecting rules for multikey indexes.
642+
assignMultikeySafePredicates(predsOverLeadingField, &indexAssign);
643+
644+
// Assign the mandatory predicate to 'thisIndex'. Due to how keys are generated for
645+
// 2dsphere indexes, it is always safe to assign a predicate on a distinct path to
646+
// 'thisIndex' and compound bounds; an index entry is produced for each combination
647+
// of unique values along all of the indexed fields, even if they are in separate
648+
// array elements. See SERVER-23533 for more details.
649+
compound({mandatoryPred}, thisIndex, &indexAssign);
650+
651+
auto compIt = idxToNotFirst.find(indexAssign.index);
652+
if (compIt != idxToNotFirst.end()) {
653+
// Copy the predicates on the non-leading index fields and remove
654+
// 'mandatoryPred' to avoid assigning it twice to 'thisIndex'.
655+
vector<MatchExpression*> predsOverNonLeadingFields = compIt->second;
656+
657+
auto mandIt = std::find(predsOverNonLeadingFields.begin(),
658+
predsOverNonLeadingFields.end(),
659+
mandatoryPred);
660+
invariant(mandIt != predsOverNonLeadingFields.end());
661+
662+
predsOverNonLeadingFields.erase(mandIt);
663+
664+
// Assign any predicates on the non-leading index fields to 'indexAssign' that
665+
// don't violate the intersecting or compounding rules for multikey indexes.
666+
assignMultikeySafePredicates(predsOverNonLeadingFields, &indexAssign);
667+
}
668+
}
669+
} else if (thisIndex.multikey) {
619670
// Special handling for multikey mandatory indices.
620671
if (predsOverLeadingField.end() != std::find(predsOverLeadingField.begin(),
621672
predsOverLeadingField.end(),
@@ -1255,16 +1306,20 @@ void PlanEnumerator::assignMultikeySafePredicates(const std::vector<MatchExpress
12551306
const auto* assignedPred = indexAssignment->preds[i];
12561307
const auto posInIdx = indexAssignment->positions[i];
12571308

1258-
// enumerateOneIndex() should have only already assigned predicates to 'thisIndex' that on
1259-
// the leading index field.
1260-
invariant(posInIdx == 0);
1261-
12621309
invariant(assignedPred->getTag());
12631310
RelevantTag* rt = static_cast<RelevantTag*>(assignedPred->getTag());
12641311

12651312
// 'assignedPred' has already been assigned to 'thisIndex', so canAssignPredToIndex() ought
12661313
// to return true.
1267-
invariant(canAssignPredToIndex(rt, multikeyPaths[posInIdx], &used));
1314+
const bool shouldHaveAssigned = canAssignPredToIndex(rt, multikeyPaths[posInIdx], &used);
1315+
if (!shouldHaveAssigned) {
1316+
// However, there are cases with multikey 2dsphere indexes where the mandatory predicate
1317+
// is still safe to compound with, even though a prefix of it that causes the index to
1318+
// be multikey can be shared with the leading index field. The predicates cannot
1319+
// possibly be joined by an $elemMatch because $near predicates must be specified at the
1320+
// top-level of the query.
1321+
invariant(assignedPred->matchType() == MatchExpression::GEO_NEAR);
1322+
}
12681323
}
12691324

12701325
size_t posInIdx = 0;
@@ -1280,10 +1335,6 @@ void PlanEnumerator::assignMultikeySafePredicates(const std::vector<MatchExpress
12801335
continue;
12811336
}
12821337

1283-
// enumerateOneIndex() should only attempt to assign additional predicates to
1284-
// 'thisIndex' that are on the non-leading index fields.
1285-
invariant(posInIdx > 0);
1286-
12871338
if (multikeyPaths[posInIdx].empty()) {
12881339
// We can always intersect or compound the bounds when no prefix of the queried path
12891340
// causes the index to be multikey.

0 commit comments

Comments
 (0)