summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMax Hirschhorn <max.hirschhorn@mongodb.com>2016-04-19 11:06:33 -0400
committerMax Hirschhorn <max.hirschhorn@mongodb.com>2016-04-19 11:06:33 -0400
commitd74720487db590a1ff1b39296601806e8d0067d5 (patch)
tree2754104d28938a3ace0b5be6e4301de0335b0496
parent4454864060be01d7e5a1cdb81617822f7bd9e813 (diff)
downloadmongo-d74720487db590a1ff1b39296601806e8d0067d5.tar.gz
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.
-rw-r--r--src/mongo/db/query/plan_enumerator.cpp71
-rw-r--r--src/mongo/db/query/query_planner_geo_test.cpp434
-rw-r--r--src/mongo/db/query/query_planner_test_fixture.cpp2
-rw-r--r--src/mongo/db/query/query_planner_test_fixture.h2
4 files changed, 497 insertions, 12 deletions
diff --git a/src/mongo/db/query/plan_enumerator.cpp b/src/mongo/db/query/plan_enumerator.cpp
index db9d19a1665..8a135e89c5d 100644
--- a/src/mongo/db/query/plan_enumerator.cpp
+++ b/src/mongo/db/query/plan_enumerator.cpp
@@ -615,7 +615,58 @@ bool PlanEnumerator::enumerateMandatoryIndex(const IndexToPredMap& idxToFirst,
const vector<MatchExpression*>& predsOverLeadingField = it->second;
- if (thisIndex.multikey) {
+ if (thisIndex.multikey && thisIndex.multikeyPaths) {
+ // 2dsphere indexes are the only special index type that should ever have path-level
+ // multikey information.
+ invariant(INDEX_2DSPHERE == thisIndex.type);
+
+ if (predsOverLeadingField.end() != std::find(predsOverLeadingField.begin(),
+ predsOverLeadingField.end(),
+ mandatoryPred)) {
+ // The mandatory predicate is on the leading field of 'thisIndex'. We assign it to
+ // 'thisIndex' and skip assigning any other predicates on the leading field to
+ // 'thisIndex' because no additional predicate on the leading field will generate a
+ // more efficient data access plan.
+ indexAssign.preds.push_back(mandatoryPred);
+ indexAssign.positions.push_back(0);
+
+ auto compIt = idxToNotFirst.find(indexAssign.index);
+ if (compIt != idxToNotFirst.end()) {
+ // Assign any predicates on the non-leading index fields to 'indexAssign' that
+ // don't violate the intersecting or compounding rules for multikey indexes.
+ assignMultikeySafePredicates(compIt->second, &indexAssign);
+ }
+ } else {
+ // Assign any predicates on the leading index field to 'indexAssign' that don't
+ // violate the intersecting rules for multikey indexes.
+ assignMultikeySafePredicates(predsOverLeadingField, &indexAssign);
+
+ // Assign the mandatory predicate to 'thisIndex'. Due to how keys are generated for
+ // 2dsphere indexes, it is always safe to assign a predicate on a distinct path to
+ // 'thisIndex' and compound bounds; an index entry is produced for each combination
+ // of unique values along all of the indexed fields, even if they are in separate
+ // array elements. See SERVER-23533 for more details.
+ compound({mandatoryPred}, thisIndex, &indexAssign);
+
+ auto compIt = idxToNotFirst.find(indexAssign.index);
+ if (compIt != idxToNotFirst.end()) {
+ // Copy the predicates on the non-leading index fields and remove
+ // 'mandatoryPred' to avoid assigning it twice to 'thisIndex'.
+ vector<MatchExpression*> predsOverNonLeadingFields = compIt->second;
+
+ auto mandIt = std::find(predsOverNonLeadingFields.begin(),
+ predsOverNonLeadingFields.end(),
+ mandatoryPred);
+ invariant(mandIt != predsOverNonLeadingFields.end());
+
+ predsOverNonLeadingFields.erase(mandIt);
+
+ // Assign any predicates on the non-leading index fields to 'indexAssign' that
+ // don't violate the intersecting or compounding rules for multikey indexes.
+ assignMultikeySafePredicates(predsOverNonLeadingFields, &indexAssign);
+ }
+ }
+ } else if (thisIndex.multikey) {
// Special handling for multikey mandatory indices.
if (predsOverLeadingField.end() != std::find(predsOverLeadingField.begin(),
predsOverLeadingField.end(),
@@ -1255,16 +1306,20 @@ void PlanEnumerator::assignMultikeySafePredicates(const std::vector<MatchExpress
const auto* assignedPred = indexAssignment->preds[i];
const auto posInIdx = indexAssignment->positions[i];
- // enumerateOneIndex() should have only already assigned predicates to 'thisIndex' that on
- // the leading index field.
- invariant(posInIdx == 0);
-
invariant(assignedPred->getTag());
RelevantTag* rt = static_cast<RelevantTag*>(assignedPred->getTag());
// 'assignedPred' has already been assigned to 'thisIndex', so canAssignPredToIndex() ought
// to return true.
- invariant(canAssignPredToIndex(rt, multikeyPaths[posInIdx], &used));
+ const bool shouldHaveAssigned = canAssignPredToIndex(rt, multikeyPaths[posInIdx], &used);
+ if (!shouldHaveAssigned) {
+ // However, there are cases with multikey 2dsphere indexes where the mandatory predicate
+ // is still safe to compound with, even though a prefix of it that causes the index to
+ // be multikey can be shared with the leading index field. The predicates cannot
+ // possibly be joined by an $elemMatch because $near predicates must be specified at the
+ // top-level of the query.
+ invariant(assignedPred->matchType() == MatchExpression::GEO_NEAR);
+ }
}
size_t posInIdx = 0;
@@ -1280,10 +1335,6 @@ void PlanEnumerator::assignMultikeySafePredicates(const std::vector<MatchExpress
continue;
}
- // enumerateOneIndex() should only attempt to assign additional predicates to
- // 'thisIndex' that are on the non-leading index fields.
- invariant(posInIdx > 0);
-
if (multikeyPaths[posInIdx].empty()) {
// We can always intersect or compound the bounds when no prefix of the queried path
// causes the index to be multikey.
diff --git a/src/mongo/db/query/query_planner_geo_test.cpp b/src/mongo/db/query/query_planner_geo_test.cpp
index c21be913806..b75e2377079 100644
--- a/src/mongo/db/query/query_planner_geo_test.cpp
+++ b/src/mongo/db/query/query_planner_geo_test.cpp
@@ -383,6 +383,22 @@ TEST_F(QueryPlannerTest, And2DWith2DNearSameField) {
assertSolutionExists("{fetch: { node : { geoNear2d: {a: '2d'} } } }");
}
+TEST_F(QueryPlannerTest, And2DWith2DNearSameFieldMultikey) {
+ const bool multikey = true;
+ addIndex(BSON("geo"
+ << "2d"),
+ multikey);
+ runQuery(fromjson(
+ "{$and: [{geo: {$near: [0, 0]}}, "
+ "{geo: {$within: {$polygon: [[0, 0], [1, 0], [1, 1]]}}}]}"));
+
+ // GEO_NEAR must use the index, and GEO predicate becomes a filter.
+ assertNumSolutions(1U);
+ assertSolutionExists(
+ "{fetch: {filter: {geo: {$within: {$polygon: [[0, 0], [1, 0], [1, 1]]}}}, "
+ "node: {geoNear2d: {geo: '2d'}}}}}");
+}
+
TEST_F(QueryPlannerTest, And2DSphereSameFieldNonNear) {
addIndex(BSON("a"
<< "2dsphere"));
@@ -802,6 +818,424 @@ TEST_F(QueryPlannerTest, Negation2DSphereGeoNearMultikey) {
}
//
+// Tests for intersecting and compounding bounds on multikey 2dsphere indexes when path-level
+// multikey information is available.
+//
+using QueryPlannerGeo2dsphereTest = QueryPlannerTest;
+
+TEST_F(QueryPlannerGeo2dsphereTest, CanIntersectBoundsWhenFirstFieldIsNotMultikey) {
+ MultikeyPaths multikeyPaths{std::set<size_t>{}, {0U}, std::set<size_t>{}};
+ addIndex(BSON("a" << 1 << "b" << 1 << "geo"
+ << "2dsphere"),
+ multikeyPaths);
+ runQuery(fromjson("{a: {$gte: 0, $lt: 10}, b: 2, geo: {$nearSphere: [0, 0]}}"));
+
+ assertNumSolutions(1U);
+ assertSolutionExists(
+ "{geoNear2dsphere: {pattern: {a: 1, b: 1, geo: '2dsphere'}, "
+ "bounds: {a: [[0, 10, true, false]], b: [[2, 2, true, true]], "
+ "geo: [['MinKey', 'MaxKey', true, true]]}}}");
+}
+
+TEST_F(QueryPlannerGeo2dsphereTest,
+ CanIntersectBoundsOnFirstFieldWhenItAndSharedPrefixAreNotMultikey) {
+ MultikeyPaths multikeyPaths{std::set<size_t>{}, {1U}, std::set<size_t>{}};
+ addIndex(BSON("a.b" << 1 << "a.c" << 1 << "a.geo"
+ << "2dsphere"),
+ multikeyPaths);
+ runQuery(fromjson("{'a.b': {$gte: 0, $lt: 10}, 'a.c': 2, 'a.geo': {$nearSphere: [0, 0]}}"));
+
+ assertNumSolutions(1U);
+ assertSolutionExists(
+ "{geoNear2dsphere: {pattern: {'a.b': 1, 'a.c': 1, 'a.geo': '2dsphere'}, "
+ "bounds: {'a.b': [[0, 10, true, false]], 'a.c': [[2, 2, true, true]], "
+ "'a.geo': [['MinKey', 'MaxKey', true, true]]}}}");
+}
+
+TEST_F(QueryPlannerGeo2dsphereTest, CannotIntersectBoundsWhenFirstFieldIsMultikey) {
+ MultikeyPaths multikeyPaths{{0U}, std::set<size_t>{}, std::set<size_t>{}};
+ addIndex(BSON("a" << 1 << "b" << 1 << "geo"
+ << "2dsphere"),
+ multikeyPaths);
+ runQuery(fromjson("{a: {$gte: 0, $lt: 10}, b: 2, geo: {$nearSphere: [0, 0]}}"));
+
+ assertNumSolutions(1U);
+ assertSolutionExists(
+ "{fetch: {node: {geoNear2dsphere: {pattern: {a: 1, b: 1, geo: '2dsphere'}, "
+ "bounds: {a: [[-Infinity, 10, true, false]], b: [[2, 2, true, true]], "
+ "geo: [['MinKey', 'MaxKey', true, true]]}}}}}");
+}
+
+TEST_F(QueryPlannerGeo2dsphereTest, CanIntersectBoundsWhenFirstFieldIsMultikeyButHasElemMatch) {
+ MultikeyPaths multikeyPaths{{0U}, std::set<size_t>{}, std::set<size_t>{}};
+ addIndex(BSON("a" << 1 << "b" << 1 << "geo"
+ << "2dsphere"),
+ multikeyPaths);
+ runQuery(fromjson("{a: {$elemMatch: {$gte: 0, $lt: 10}}, b: 2, geo: {$nearSphere: [0, 0]}}"));
+
+ assertNumSolutions(1U);
+ assertSolutionExists(
+ "{fetch: {node: {geoNear2dsphere: {pattern: {a: 1, b: 1, geo: '2dsphere'}, "
+ "bounds: {a: [[0, 10, true, false]], b: [[2, 2, true, true]], "
+ "geo: [['MinKey', 'MaxKey', true, true]]}}}}}");
+}
+
+TEST_F(QueryPlannerGeo2dsphereTest,
+ CannotComplementBoundsOnFirstFieldWhenItIsMultikeyAndHasNotEqualExpr) {
+ MultikeyPaths multikeyPaths{{0U}, std::set<size_t>{}, std::set<size_t>{}};
+ addIndex(BSON("a" << 1 << "b" << 1 << "geo"
+ << "2dsphere"),
+ multikeyPaths);
+ runQuery(fromjson("{a: {$ne: 3}, b: 2, geo: {$nearSphere: [0, 0]}}"));
+
+ assertNumSolutions(0U);
+}
+
+TEST_F(QueryPlannerGeo2dsphereTest,
+ CanIntersectBoundsWhenFirstFieldIsMultikeyAndHasNotInsideElemMatch) {
+ MultikeyPaths multikeyPaths{{0U}, std::set<size_t>{}, std::set<size_t>{}};
+ addIndex(BSON("a" << 1 << "b" << 1 << "geo"
+ << "2dsphere"),
+ multikeyPaths);
+ runQuery(fromjson(
+ "{a: {$elemMatch: {$not: {$gte: 10}, $gte: 0}}, b: 2, geo: {$nearSphere: [0, 0]}}"));
+
+ assertNumSolutions(1U);
+ assertSolutionExists(
+ "{fetch: {node: {geoNear2dsphere: {pattern: {a: 1, b: 1, geo: '2dsphere'}, "
+ "bounds: {a: [[0, 10, true, false]], b: [[2, 2, true, true]], "
+ "geo: [['MinKey', 'MaxKey', true, true]]}}}}}");
+}
+
+TEST_F(QueryPlannerGeo2dsphereTest,
+ CanIntersectBoundsOnFirstFieldWhenSharedPrefixIsMultikeyButHasElemMatch) {
+ MultikeyPaths multikeyPaths{{0U}, {0U}, {0U}};
+ addIndex(BSON("a.b" << 1 << "a.c" << 1 << "a.geo"
+ << "2dsphere"),
+ multikeyPaths);
+ runQuery(fromjson(
+ "{a: {$elemMatch: {b: {$gte: 0, $lt: 10}, c: 2}}, 'a.geo': {$nearSphere: [0, 0]}}"));
+
+ assertNumSolutions(1U);
+ assertSolutionExists(
+ "{fetch: {node: {geoNear2dsphere: {pattern: {'a.b': 1, 'a.c': 1, 'a.geo': '2dsphere'}, "
+ "bounds: {'a.b': [[0, 10, true, false]], 'a.c': [[2, 2, true, true]], "
+ "'a.geo': [['MinKey', 'MaxKey', true, true]]}}}}}");
+}
+
+TEST_F(QueryPlannerGeo2dsphereTest,
+ CannotIntersectBoundsOnFirstFieldWhenItAndSharedPrefixAreMultikey) {
+ MultikeyPaths multikeyPaths{{0U, 1U}, {0U}, {0U}};
+ addIndex(BSON("a.b" << 1 << "a.c" << 1 << "a.geo"
+ << "2dsphere"),
+ multikeyPaths);
+ runQuery(fromjson(
+ "{a: {$elemMatch: {b: {$gte: 0, $lt: 10}, c: 2}}, 'a.geo': {$nearSphere: [0, 0]}}"));
+
+ assertNumSolutions(1U);
+ assertSolutionExists(
+ "{fetch: {node: {geoNear2dsphere: {pattern: {'a.b': 1, 'a.c': 1, 'a.geo': '2dsphere'}, "
+ "bounds: {'a.b': [[-Infinity, 10, true, false]], 'a.c': [[2, 2, true, true]], "
+ "'a.geo': [['MinKey', 'MaxKey', true, true]]}}}}}");
+}
+
+TEST_F(QueryPlannerGeo2dsphereTest, CanIntersectBoundsWhenSecondFieldIsNotMultikey) {
+ MultikeyPaths multikeyPaths{{0U}, std::set<size_t>{}, std::set<size_t>{}};
+ addIndex(BSON("a" << 1 << "b" << 1 << "geo"
+ << "2dsphere"),
+ multikeyPaths);
+ runQuery(fromjson("{a: 2, b: {$gte: 0, $lt: 10}, geo: {$nearSphere: [0, 0]}}"));
+
+ assertNumSolutions(1U);
+ assertSolutionExists(
+ "{geoNear2dsphere: {pattern: {a: 1, b: 1, geo: '2dsphere'}, "
+ "bounds: {a: [[2, 2, true, true]], b: [[0, 10, true, false]], "
+ "geo: [['MinKey', 'MaxKey', true, true]]}}}");
+}
+
+TEST_F(QueryPlannerGeo2dsphereTest,
+ CanIntersectBoundsOnSecondFieldWhenItAndSharedPrefixAreNotMultikey) {
+ MultikeyPaths multikeyPaths{{1U}, std::set<size_t>{}, std::set<size_t>{}};
+ addIndex(BSON("a.b" << 1 << "a.c" << 1 << "a.geo"
+ << "2dsphere"),
+ multikeyPaths);
+ runQuery(fromjson("{'a.b': 2, 'a.c': {$gte: 0, $lt: 10}, 'a.geo': {$nearSphere: [0, 0]}}"));
+
+ assertNumSolutions(1U);
+ assertSolutionExists(
+ "{geoNear2dsphere: {pattern: {'a.b': 1, 'a.c': 1, 'a.geo': '2dsphere'}, "
+ "bounds: {'a.b': [[2, 2, true, true]], 'a.c': [[0, 10, true, false]], "
+ "'a.geo': [['MinKey', 'MaxKey', true, true]]}}}");
+}
+
+TEST_F(QueryPlannerGeo2dsphereTest, CannotIntersectBoundsWhenSecondFieldIsMultikey) {
+ MultikeyPaths multikeyPaths{std::set<size_t>{}, {0U}, std::set<size_t>{}};
+ addIndex(BSON("a" << 1 << "b" << 1 << "geo"
+ << "2dsphere"),
+ multikeyPaths);
+ runQuery(fromjson("{a: 2, b: {$gte: 0, $lt: 10}, geo: {$nearSphere: [0, 0]}}"));
+
+ assertNumSolutions(1U);
+ assertSolutionExists(
+ "{fetch: {node: {geoNear2dsphere: {pattern: {a: 1, b: 1, geo: '2dsphere'}, "
+ "bounds: {a: [[2, 2, true, true]], b: [[-Infinity, 10, true, false]], "
+ "geo: [['MinKey', 'MaxKey', true, true]]}}}}}");
+}
+
+TEST_F(QueryPlannerGeo2dsphereTest, CanIntersectBoundsWhenSecondFieldIsMultikeyButHasElemMatch) {
+ MultikeyPaths multikeyPaths{std::set<size_t>{}, {0U}, std::set<size_t>{}};
+ addIndex(BSON("a" << 1 << "b" << 1 << "geo"
+ << "2dsphere"),
+ multikeyPaths);
+ runQuery(fromjson("{a: 2, b: {$elemMatch: {$gte: 0, $lt: 10}}, geo: {$nearSphere: [0, 0]}}"));
+
+ assertNumSolutions(1U);
+ assertSolutionExists(
+ "{fetch: {node: {geoNear2dsphere: {pattern: {a: 1, b: 1, geo: '2dsphere'}, "
+ "bounds: {a: [[2, 2, true, true]], b: [[0, 10, true, false]], "
+ "geo: [['MinKey', 'MaxKey', true, true]]}}}}}");
+}
+
+TEST_F(QueryPlannerGeo2dsphereTest,
+ CannotComplementBoundsOnSecondFieldWhenItIsMultikeyAndHasNotEqualExpr) {
+ MultikeyPaths multikeyPaths{std::set<size_t>{}, {0U}, std::set<size_t>{}};
+ addIndex(BSON("a" << 1 << "b" << 1 << "geo"
+ << "2dsphere"),
+ multikeyPaths);
+ runQuery(fromjson("{a: 2, b: {$ne: 3}, geo: {$nearSphere: [0, 0]}}"));
+
+ assertNumSolutions(1U);
+ assertSolutionExists(
+ "{fetch: {node: {geoNear2dsphere: {pattern: {a: 1, b: 1, geo: '2dsphere'}, "
+ "bounds: {a: [[2, 2, true, true]], b: [['MinKey', 'MaxKey', true, true]], "
+ "geo: [['MinKey', 'MaxKey', true, true]]}}}}}");
+}
+
+TEST_F(QueryPlannerGeo2dsphereTest,
+ CanIntersectBoundsWhenSecondFieldIsMultikeyAndHasNotInsideElemMatch) {
+ MultikeyPaths multikeyPaths{std::set<size_t>{}, {0U}, std::set<size_t>{}};
+ addIndex(BSON("a" << 1 << "b" << 1 << "geo"
+ << "2dsphere"),
+ multikeyPaths);
+ runQuery(fromjson(
+ "{a: 2, b: {$elemMatch: {$not: {$gte: 10}, $gte: 0}}, geo: {$nearSphere: [0, 0]}}"));
+
+ assertNumSolutions(1U);
+ assertSolutionExists(
+ "{fetch: {node: {geoNear2dsphere: {pattern: {a: 1, b: 1, geo: '2dsphere'}, "
+ "bounds: {a: [[2, 2, true, true]], b: [[0, 10, true, false]], "
+ "geo: [['MinKey', 'MaxKey', true, true]]}}}}}");
+}
+
+TEST_F(QueryPlannerGeo2dsphereTest,
+ CanIntersectBoundsOnSecondFieldWhenSharedPrefixIsMultikeyButHasElemMatch) {
+ MultikeyPaths multikeyPaths{{0U}, {0U}, {0U}};
+ addIndex(BSON("a.b" << 1 << "a.c" << 1 << "a.geo"
+ << "2dsphere"),
+ multikeyPaths);
+ runQuery(fromjson(
+ "{a: {$elemMatch: {b: 2, c: {$gte: 0, $lt: 10}}}, 'a.geo': {$nearSphere: [0, 0]}}"));
+
+ assertNumSolutions(1U);
+ assertSolutionExists(
+ "{fetch: {node: {geoNear2dsphere: {pattern: {'a.b': 1, 'a.c': 1, 'a.geo': '2dsphere'}, "
+ "bounds: {'a.b': [[2, 2, true, true]], 'a.c': [[0, 10, true, false]], "
+ "'a.geo': [['MinKey', 'MaxKey', true, true]]}}}}}");
+}
+
+TEST_F(QueryPlannerGeo2dsphereTest,
+ CannotIntersectBoundsOnSecondFieldWhenItAndSharedPrefixAreMultikey) {
+ MultikeyPaths multikeyPaths{{0U}, {0U, 1U}, {0U}};
+ addIndex(BSON("a.b" << 1 << "a.c" << 1 << "a.geo"
+ << "2dsphere"),
+ multikeyPaths);
+ runQuery(fromjson(
+ "{a: {$elemMatch: {b: 2, c: {$gte: 0, $lt: 10}}}, 'a.geo': {$nearSphere: [0, 0]}}"));
+
+ assertNumSolutions(1U);
+ assertSolutionExists(
+ "{fetch: {node: {geoNear2dsphere: {pattern: {'a.b': 1, 'a.c': 1, 'a.geo': '2dsphere'}, "
+ "bounds: {'a.b': [[2, 2, true, true]], 'a.c': [[-Infinity, 10, true, false]], "
+ "'a.geo': [['MinKey', 'MaxKey', true, true]]}}}}}");
+}
+
+TEST_F(QueryPlannerGeo2dsphereTest, CannotIntersectBoundsOfTwoSeparateElemMatches) {
+ MultikeyPaths multikeyPaths{{0U}, {0U}, {0U}};
+ addIndex(BSON("a.b" << 1 << "a.c" << 1 << "a.geo"
+ << "2dsphere"),
+ multikeyPaths);
+
+ runQuery(fromjson(
+ "{$and: [{a: {$elemMatch: {b: {$gte: 0}, c: {$lt: 20}}}}, "
+ "{a: {$elemMatch: {b: {$lt: 10}, c: {$gte: 5}}}}, "
+ "{'a.geo': {$nearSphere: [0, 0]}}]}"));
+
+ assertNumSolutions(1U);
+ assertSolutionExists(
+ "{fetch: {node: {geoNear2dsphere: {pattern: {'a.b': 1, 'a.c': 1, 'a.geo': '2dsphere'}, "
+ "bounds: {'a.b': [[-Infinity, 10, true, false]], 'a.c': [[5, Infinity, true, true]], "
+ "'a.geo': [['MinKey', 'MaxKey', true, true]]}}}}}");
+}
+
+TEST_F(QueryPlannerGeo2dsphereTest, CanCompoundBoundsWhenSharedPrefixIsNotMultikey) {
+ MultikeyPaths multikeyPaths{{1U}, {1U}, {1U}};
+ addIndex(BSON("a.b" << 1 << "a.c" << 1 << "a.geo"
+ << "2dsphere"),
+ multikeyPaths);
+ runQuery(fromjson("{'a.b': 2, 'a.c': 3, 'a.geo': {$nearSphere: [0, 0]}}"));
+
+ assertNumSolutions(1U);
+ assertSolutionExists(
+ "{geoNear2dsphere: {pattern: {'a.b': 1, 'a.c': 1, 'a.geo': '2dsphere'}, "
+ "bounds: {'a.b': [[2, 2, true, true]], 'a.c': [[3, 3, true, true]], "
+ "'a.geo': [['MinKey', 'MaxKey', true, true]]}}}");
+}
+
+TEST_F(QueryPlannerGeo2dsphereTest,
+ CanCompoundBoundsWhenSharedPrefixIsNotMultikeyAndFirstFieldIsGeo) {
+ MultikeyPaths multikeyPaths{{1U}, {1U}, {1U}};
+ addIndex(BSON("a.geo"
+ << "2dsphere"
+ << "a.b" << 1 << "a.c" << 1),
+ multikeyPaths);
+ runQuery(fromjson("{'a.geo': {$nearSphere: [0, 0]}, 'a.b': 2, 'a.c': 3}"));
+
+ assertNumSolutions(1U);
+ assertSolutionExists(
+ "{geoNear2dsphere: {pattern: {'a.geo': '2dsphere', 'a.b': 1, 'a.c': 1}, "
+ "bounds: {'a.geo': [['MinKey', 'MaxKey', true, true]], 'a.b': [[2, 2, true, true]], "
+ "'a.c': [[3, 3, true, true]]}}}");
+}
+
+TEST_F(QueryPlannerGeo2dsphereTest, CannotCompoundBoundsWhenSharedPrefixIsMultikey) {
+ MultikeyPaths multikeyPaths{{0U}, {0U}, {0U}};
+ addIndex(BSON("a.b" << 1 << "a.c" << 1 << "a.geo"
+ << "2dsphere"),
+ multikeyPaths);
+ runQuery(fromjson("{'a.b': 2, 'a.c': 3, 'a.geo': {$nearSphere: [0, 0]}}"));
+
+ assertNumSolutions(1U);
+ assertSolutionExists(
+ "{fetch: {node: {geoNear2dsphere: {pattern: {'a.b': 1, 'a.c': 1, 'a.geo': '2dsphere'}, "
+ "bounds: {'a.b': [[2, 2, true, true]], 'a.c': [['MinKey', 'MaxKey', true, true]], "
+ "'a.geo': [['MinKey', 'MaxKey', true, true]]}}}}}");
+}
+
+TEST_F(QueryPlannerGeo2dsphereTest,
+ CannotCompoundBoundsWhenSharedPrefixIsMultikeyAndFirstFieldIsGeo) {
+ MultikeyPaths multikeyPaths{{0U}, {0U}, {0U}};
+ addIndex(BSON("a.geo"
+ << "2dsphere"
+ << "a.b" << 1 << "a.c" << 1),
+ multikeyPaths);
+ runQuery(fromjson("{'a.geo': {$nearSphere: [0, 0]}, 'a.b': 2, 'a.c': 3}"));
+
+ assertNumSolutions(1U);
+ assertSolutionExists(
+ "{fetch: {node: {geoNear2dsphere: {pattern: {'a.geo': '2dsphere', 'a.b': 1, 'a.c': 1}, "
+ "bounds: {'a.geo': [['MinKey', 'MaxKey', true, true]], "
+ "'a.b': [['MinKey', 'MaxKey', true, true]], "
+ "'a.c': [['MinKey', 'MaxKey', true, true]]}}}}}");
+}
+
+TEST_F(QueryPlannerGeo2dsphereTest, CanCompoundBoundsWhenSharedPrefixIsMultikeyButHasElemMatch) {
+ MultikeyPaths multikeyPaths{{0U}, {0U}, {0U}};
+ addIndex(BSON("a.b" << 1 << "a.c" << 1 << "a.geo"
+ << "2dsphere"),
+ multikeyPaths);
+ runQuery(fromjson("{a: {$elemMatch: {b: 2, c: 3}}, 'a.geo': {$nearSphere: [0, 0]}}"));
+
+ assertNumSolutions(1U);
+ assertSolutionExists(
+ "{fetch: {node: {geoNear2dsphere: {pattern: {'a.b': 1, 'a.c': 1, 'a.geo': '2dsphere'}, "
+ "bounds: {'a.b': [[2, 2, true, true]], 'a.c': [[3, 3, true, true]], "
+ "'a.geo': [['MinKey', 'MaxKey', true, true]]}}}}}");
+}
+
+TEST_F(QueryPlannerGeo2dsphereTest,
+ CannotCompoundBoundsWhenSharedPrefixIsMultikeyButHasElemMatchAndFirstFieldIsGeo) {
+ MultikeyPaths multikeyPaths{{0U}, {0U}, {0U}};
+ addIndex(BSON("a.geo"
+ << "2dsphere"
+ << "a.b" << 1 << "a.c" << 1),
+ multikeyPaths);
+ runQuery(fromjson("{'a.geo': {$nearSphere: [0, 0]}, a: {$elemMatch: {b: 2, c: 3}}}"));
+
+ assertNumSolutions(1U);
+ assertSolutionExists(
+ "{fetch: {node: {geoNear2dsphere: {pattern: {'a.geo': '2dsphere', 'a.b': 1, 'a.c': 1}, "
+ "bounds: {'a.geo': [['MinKey', 'MaxKey', true, true]], "
+ "'a.b': [['MinKey', 'MaxKey', true, true]], "
+ "'a.c': [['MinKey', 'MaxKey', true, true]]}}}}}");
+}
+
+TEST_F(QueryPlannerGeo2dsphereTest, CannotCompoundBoundsWhenSharedPrefixInsideElemMatchIsMultikey) {
+ MultikeyPaths multikeyPaths{{0U, 1U}, {0U, 1U}, {0U, 1U}};
+ addIndex(BSON("a.b.c" << 1 << "a.b.d" << 1 << "a.b.geo"
+ << "2dsphere"),
+ multikeyPaths);
+ runQuery(fromjson("{a: {$elemMatch: {'b.c': 2, 'b.d': 3}}, 'a.b.geo': {$nearSphere: [0, 0]}}"));
+
+ assertNumSolutions(1U);
+ assertSolutionExists(
+ "{fetch: {node: {geoNear2dsphere: {"
+ "pattern: {'a.b.c': 1, 'a.b.d': 1, 'a.b.geo': '2dsphere'}, "
+ "bounds: {'a.b.c': [[2, 2, true, true]], 'a.b.d': [['MinKey', 'MaxKey', true, true]], "
+ "'a.b.geo': [['MinKey', 'MaxKey', true, true]]}}}}}");
+}
+
+TEST_F(QueryPlannerGeo2dsphereTest,
+ CannotCompoundBoundsWhenSharedPrefixInsideElemMatchIsMultikeyAndFirstFieldIsGeo) {
+ MultikeyPaths multikeyPaths{{0U, 1U}, {0U, 1U}, {0U, 1U}};
+ addIndex(BSON("a.b.geo"
+ << "2dsphere"
+ << "a.b.c" << 1 << "a.b.d" << 1),
+ multikeyPaths);
+ runQuery(fromjson("{'a.b.geo': {$nearSphere: [0, 0]}, a: {$elemMatch: {'b.c': 2, 'b.d': 3}}}"));
+
+ assertNumSolutions(1U);
+ assertSolutionExists(
+ "{fetch: {node: {geoNear2dsphere: {"
+ "pattern: {'a.b.geo': '2dsphere', 'a.b.c': 1, 'a.b.d': 1}, "
+ "bounds: {'a.b.geo': [['MinKey', 'MaxKey', true, true]], "
+ "'a.b.c': [['MinKey', 'MaxKey', true, true]], "
+ "'a.b.d': [['MinKey', 'MaxKey', true, true]]}}}}}");
+}
+
+TEST_F(QueryPlannerGeo2dsphereTest, CanIntersectBoundsOn2dsphereFieldWhenItIsNotMultikey) {
+ MultikeyPaths multikeyPaths{std::set<size_t>{}};
+ addIndex(BSON("geo"
+ << "2dsphere"),
+ multikeyPaths);
+ runQuery(fromjson(
+ "{$and: [{geo: {$nearSphere: [0, 0]}}, "
+ "{geo: {$geoIntersects: {$geometry: {type: 'Point', coordinates: [0, 0]}}}}]}"));
+
+ assertNumSolutions(1U);
+ assertSolutionExists(
+ "{fetch: {filter: {geo: {$geoIntersects: "
+ "{$geometry: {type: 'Point', coordinates: [0, 0]}}}}, "
+ "node: {geoNear2dsphere: {pattern: {geo: '2dsphere'}}}}}}");
+}
+
+TEST_F(QueryPlannerGeo2dsphereTest, CannotIntersectBoundsOn2dsphereFieldWhenItIsMultikey) {
+ MultikeyPaths multikeyPaths{{0U}};
+ addIndex(BSON("geo"
+ << "2dsphere"),
+ multikeyPaths);
+ runQuery(fromjson(
+ "{$and: [{geo: {$nearSphere: [0, 0]}}, "
+ "{geo: {$geoIntersects: {$geometry: {type: 'Point', coordinates: [0, 0]}}}}]}"));
+
+ assertNumSolutions(1U);
+ assertSolutionExists(
+ "{fetch: {filter: {geo: {$geoIntersects: "
+ "{$geometry: {type: 'Point', coordinates: [0, 0]}}}}, "
+ "node: {geoNear2dsphere: {pattern: {geo: '2dsphere'}, "
+ "bounds: {geo: [['MinKey', 'MaxKey', true, true]]}}}}}}");
+}
+
+//
// 2dsphere V2 sparse indices, SERVER-9639
//
diff --git a/src/mongo/db/query/query_planner_test_fixture.cpp b/src/mongo/db/query/query_planner_test_fixture.cpp
index d5a593b1bed..e690d5ff48b 100644
--- a/src/mongo/db/query/query_planner_test_fixture.cpp
+++ b/src/mongo/db/query/query_planner_test_fixture.cpp
@@ -105,7 +105,7 @@ void QueryPlannerTest::addIndex(BSONObj keyPattern, MatchExpression* filterExpr)
BSONObj()));
}
-void QueryPlannerTest::addIndex(BSONObj keyPattern, MultikeyPaths multikeyPaths) {
+void QueryPlannerTest::addIndex(BSONObj keyPattern, const MultikeyPaths& multikeyPaths) {
invariant(multikeyPaths.size() == static_cast<size_t>(keyPattern.nFields()));
const bool multikey =
diff --git a/src/mongo/db/query/query_planner_test_fixture.h b/src/mongo/db/query/query_planner_test_fixture.h
index c6e2e824337..5ef2490e171 100644
--- a/src/mongo/db/query/query_planner_test_fixture.h
+++ b/src/mongo/db/query/query_planner_test_fixture.h
@@ -61,7 +61,7 @@ protected:
void addIndex(BSONObj keyPattern, MatchExpression* filterExpr);
- void addIndex(BSONObj keyPattern, MultikeyPaths multikeyPaths);
+ void addIndex(BSONObj keyPattern, const MultikeyPaths& multikeyPaths);
void addIndex(BSONObj keyPattern, CollatorInterface* collator);