@@ -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