summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--jstests/core/geo_s2intersection.js23
-rw-r--r--src/mongo/db/query/planner_access.cpp38
-rw-r--r--src/mongo/db/query/query_planner_test.cpp98
-rw-r--r--src/mongo/db/query/query_planner_text_test.cpp10
4 files changed, 153 insertions, 16 deletions
diff --git a/jstests/core/geo_s2intersection.js b/jstests/core/geo_s2intersection.js
index 42abacca98d..35cc531c7eb 100644
--- a/jstests/core/geo_s2intersection.js
+++ b/jstests/core/geo_s2intersection.js
@@ -139,3 +139,26 @@ testPoint = {type: "Point",
result = t.find({geo: {$geoIntersects: {$geometry: testPoint}}});
assert.eq(result.count(), 1);
assert.eq(result[0]['name'], 'canonPoly');
+
+// Case 12: Make sure that we properly handle and $and of two
+// $geoIntersects predicates.
+t.drop();
+t.ensureIndex({a: "2dsphere"});
+t.insert({a: {type: "Polygon", coordinates: [[[0,0], [3,6], [6,0], [0,0]]]}});
+
+var firstPoint = {$geometry: {type: "Point", coordinates: [3.0, 1.0]}};
+var secondPoint = {$geometry: {type: "Point", coordinates: [4.0, 1.0]}};
+
+// First point should intersect with the polygon.
+result = t.find({a: {$geoIntersects: firstPoint}});
+assert.eq(result.count(), 1);
+
+// Second point also intersects with the polygon.
+result = t.find({a: {$geoIntersects: secondPoint}});
+assert.eq(result.count(), 1);
+
+// Both points intersect with the polygon, so the $and of
+// two $geoIntersects should as well.
+result = t.find({$and: [{a: {$geoIntersects: firstPoint}},
+ {a: {$geoIntersects: secondPoint}}]});
+assert.eq(result.count(), 1);
diff --git a/src/mongo/db/query/planner_access.cpp b/src/mongo/db/query/planner_access.cpp
index 92387fd26af..84082d0fcb8 100644
--- a/src/mongo/db/query/planner_access.cpp
+++ b/src/mongo/db/query/planner_access.cpp
@@ -184,11 +184,43 @@ namespace mongo {
const StageType type = node->getType();
verify(STAGE_GEO_NEAR_2D != type);
- if (STAGE_GEO_2D == type || STAGE_TEXT == type ||
- STAGE_GEO_NEAR_2DSPHERE == type) {
- return true;
+ const MatchExpression::MatchType exprType = expr->matchType();
+
+ //
+ // First handle special solution tree leaf types. In general, normal index bounds
+ // building is not used for special leaf types, and hence we cannot merge leaves.
+ //
+ // This rule is always true for OR, but there are exceptions for AND.
+ // Specifically, we can often merge a predicate with a special leaf type
+ // by adding a filter to the special leaf type.
+ //
+
+ if (STAGE_GEO_2D == type) {
+ // Don't merge GEO with a geo leaf. Instead, we will generate an AND_HASH solution
+ // with two separate leaves.
+ return MatchExpression::AND == mergeType
+ && MatchExpression::GEO != exprType;
+ }
+
+ if (STAGE_TEXT == type) {
+ // Currently only one text predicate is allowed, but to be safe, make sure that we
+ // do not try to merge two text predicates.
+ return MatchExpression::AND == mergeType
+ && MatchExpression::TEXT != exprType;
+ }
+
+ if (STAGE_GEO_NEAR_2DSPHERE == type) {
+ // Currently only one GEO_NEAR is allowed, but to be safe, make sure that we
+ // do not try to merge two GEO_NEAR predicates.
+ return MatchExpression::AND == mergeType
+ && MatchExpression::GEO_NEAR != exprType;
}
+ //
+ // If we're here, then we're done checking for special leaf nodes, and the leaf
+ // must be a regular index scan.
+ //
+
invariant(type == STAGE_IXSCAN);
IndexScanNode* scan = static_cast<IndexScanNode*>(node);
IndexBounds* boundsToFillOut = &scan->bounds;
diff --git a/src/mongo/db/query/query_planner_test.cpp b/src/mongo/db/query/query_planner_test.cpp
index 8b592cf25f7..1132e2bb533 100644
--- a/src/mongo/db/query/query_planner_test.cpp
+++ b/src/mongo/db/query/query_planner_test.cpp
@@ -1297,25 +1297,25 @@ namespace {
// Polygon
runQuery(fromjson("{a : { $within: { $polygon : [[0,0], [2,0], [4,0]] } }}"));
- ASSERT_EQUALS(getNumSolutions(), 2U);
+ assertNumSolutions(2U);
assertSolutionExists("{cscan: {dir: 1}}");
assertSolutionExists("{fetch: {node: {geo2d: {a: '2d'}}}}");
// Center
runQuery(fromjson("{a : { $within : { $center : [[ 5, 5 ], 7 ] } }}"));
- ASSERT_EQUALS(getNumSolutions(), 2U);
+ assertNumSolutions(2U);
assertSolutionExists("{cscan: {dir: 1}}");
assertSolutionExists("{fetch: {node: {geo2d: {a: '2d'}}}}");
// Centersphere
runQuery(fromjson("{a : { $within : { $centerSphere : [[ 10, 20 ], 0.01 ] } }}"));
- ASSERT_EQUALS(getNumSolutions(), 2U);
+ assertNumSolutions(2U);
assertSolutionExists("{cscan: {dir: 1}}");
assertSolutionExists("{fetch: {node: {geo2d: {a: '2d'}}}}");
// Within box.
runQuery(fromjson("{a : {$within: {$box : [[0,0],[9,9]]}}}"));
- ASSERT_EQUALS(getNumSolutions(), 2U);
+ assertNumSolutions(2U);
assertSolutionExists("{cscan: {dir: 1}}");
assertSolutionExists("{fetch: {node: {geo2d: {a: '2d'}}}}");
@@ -1329,7 +1329,7 @@ namespace {
runQuery(fromjson("{loc:{$near:{$geometry:{type:'Point',"
"coordinates : [-81.513743,28.369947] },"
" $maxDistance :100}},a: 'mouse'}"));
- ASSERT_EQUALS(getNumSolutions(), 1U);
+ assertNumSolutions(1U);
assertSolutionExists("{fetch: {node: {geoNear2dsphere: {loc: '2dsphere'}}}}");
}
@@ -1339,12 +1339,12 @@ namespace {
runQuery(fromjson("{a: {$geoIntersects: {$geometry: {type: 'Point',"
"coordinates: [10.0, 10.0]}}}}"));
- ASSERT_EQUALS(getNumSolutions(), 2U);
+ assertNumSolutions(2U);
assertSolutionExists("{cscan: {dir: 1}}");
assertSolutionExists("{fetch: {node: {ixscan: {pattern: {a: '2dsphere'}}}}}");
runQuery(fromjson("{a : { $geoWithin : { $centerSphere : [[ 10, 20 ], 0.01 ] } }}"));
- ASSERT_EQUALS(getNumSolutions(), 2U);
+ assertNumSolutions(2U);
assertSolutionExists("{cscan: {dir: 1}}");
assertSolutionExists("{fetch: {node: {ixscan: {pattern: {a: '2dsphere'}}}}}");
@@ -1355,7 +1355,7 @@ namespace {
// Can only do near + old point.
addIndex(BSON("a" << "2d"));
runQuery(fromjson("{a: {$near: [0,0], $maxDistance:0.3 }}"));
- ASSERT_EQUALS(getNumSolutions(), 1U);
+ assertNumSolutions(1U);
assertSolutionExists("{geoNear2d: {a: '2d'}}");
}
@@ -1369,7 +1369,7 @@ namespace {
runQuery(fromjson("{a: {$geoNear: {$geometry: {type: 'Point', coordinates: [0,0]},"
"$maxDistance:100}}}"));
- ASSERT_EQUALS(getNumSolutions(), 1U);
+ assertNumSolutions(1U);
assertSolutionExists("{geoNear2dsphere: {a: '2dsphere'}}");
}
@@ -1378,7 +1378,7 @@ namespace {
addIndex(BSON("x" << 1 << "a" << "2dsphere"));
runQuery(fromjson("{x:1, a: {$nearSphere: [0,0], $maxDistance: 0.31 }}"));
- ASSERT_EQUALS(getNumSolutions(), 1U);
+ assertNumSolutions(1U);
assertSolutionExists("{geoNear2dsphere: {x: 1, a: '2dsphere'}}");
}
@@ -1391,7 +1391,7 @@ namespace {
addIndex(BSON("x" << 1 << "a" << "2dsphere"));
runQuery(fromjson("{x:1}"));
- ASSERT_EQUALS(getNumSolutions(), 2U);
+ assertNumSolutions(2U);
assertSolutionExists("{cscan: {dir: 1}}");
assertSolutionExists("{fetch: {node: {ixscan: {pattern: {x: 1, a: '2dsphere'}}}}}");
}
@@ -1403,11 +1403,22 @@ namespace {
runQuery(fromjson("{$or: [ {a : { $within : { $polygon : [[0,0], [2,0], [4,0]] } }},"
" {b : { $within : { $center : [[ 5, 5 ], 7 ] } }} ]}"));
- ASSERT_EQUALS(getNumSolutions(), 2U);
+ assertNumSolutions(2U);
assertSolutionExists("{cscan: {dir: 1}}");
assertSolutionExists("{fetch: {node: {or: {nodes: [{geo2d: {a: '2d'}}, {geo2d: {b: '2d'}}]}}}}");
}
+ // SERVER-3984, $or 2d index
+ TEST_F(QueryPlannerTest, Or2DSameFieldNonNear) {
+ addIndex(BSON("a" << "2d"));
+ runQuery(fromjson("{$or: [ {a : { $within : { $polygon : [[0,0], [2,0], [4,0]] } }},"
+ " {a : { $within : { $center : [[ 5, 5 ], 7 ] } }} ]}"));
+
+ assertNumSolutions(2U);
+ assertSolutionExists("{cscan: {dir: 1}}");
+ assertSolutionExists("{fetch: {node: {or: {nodes: [{geo2d: {a: '2d'}}, {geo2d: {a: '2d'}}]}}}}");
+ }
+
// SERVER-3984, $or 2dsphere index
TEST_F(QueryPlannerTest, Or2DSphereNonNear) {
addIndex(BSON("a" << "2dsphere"));
@@ -1415,12 +1426,73 @@ namespace {
runQuery(fromjson("{$or: [ {a: {$geoIntersects: {$geometry: {type: 'Point', coordinates: [10.0, 10.0]}}}},"
" {b: {$geoWithin: { $centerSphere: [[ 10, 20 ], 0.01 ] } }} ]}"));
- ASSERT_EQUALS(getNumSolutions(), 2U);
+ assertNumSolutions(2U);
assertSolutionExists("{cscan: {dir: 1}}");
assertSolutionExists("{or: {nodes: [{fetch: {node: {ixscan: {pattern: {a: '2dsphere'}}}}},"
"{fetch: {node: {ixscan: {pattern: {b: '2dsphere'}}}}}]}}");
}
+ TEST_F(QueryPlannerTest, And2DSameFieldNonNear) {
+ addIndex(BSON("a" << "2d"));
+ runQuery(fromjson("{$and: [ {a : { $within : { $polygon : [[0,0], [2,0], [4,0]] } }},"
+ " {a : { $within : { $center : [[ 5, 5 ], 7 ] } }} ]}"));
+
+ assertNumSolutions(2U);
+ assertSolutionExists("{cscan: {dir: 1}}");
+ assertSolutionExists("{fetch: {node: {andHash: {nodes: ["
+ "{geo2d: {a: '2d'}}, {geo2d: {a: '2d'}}]}}}}");
+ }
+
+ TEST_F(QueryPlannerTest, And2DWith2DNearSameField) {
+ addIndex(BSON("a" << "2d"));
+ runQuery(fromjson("{$and: [ {a : { $within : { $polygon : [[0,0], [2,0], [4,0]] } }},"
+ " {a : { $near : [ 5, 5 ] } } ]}"));
+
+ // GEO_NEAR must use the index, and GEO predicate becomes a filter.
+ assertNumSolutions(1U);
+ assertSolutionExists("{geoNear2d: {a: '2d'}}");
+ }
+
+ TEST_F(QueryPlannerTest, And2DSphereSameFieldNonNear) {
+ addIndex(BSON("a" << "2dsphere"));
+ runQuery(fromjson("{$and: [ {a: {$geoIntersects: {$geometry: "
+ "{type: 'Point', coordinates: [3.0, 1.0]}}}},"
+ " {a: {$geoIntersects: {$geometry: "
+ "{type: 'Point', coordinates: [4.0, 1.0]}}}}]}"));
+
+ assertNumSolutions(2U);
+ assertSolutionExists("{cscan: {dir: 1}}");
+ // Bounds of the two 2dsphere geo predicates are combined into
+ // a single index scan.
+ assertSolutionExists("{fetch: {node: {ixscan: {pattern: {a: '2dsphere'}}}}}");
+ }
+
+ TEST_F(QueryPlannerTest, And2DSphereWithNearSameField) {
+ addIndex(BSON("a" << "2dsphere"));
+ runQuery(fromjson("{$and: [{a: {$geoIntersects: {$geometry: "
+ "{type: 'Point', coordinates: [3.0, 1.0]}}}},"
+ "{a: {$near: {$geometry: "
+ "{type: 'Point', coordinates: [10.0, 10.0]}}}}]}"));
+
+ // GEO_NEAR must use the index, and GEO predicate becomes a filter.
+ assertNumSolutions(1U);
+ assertSolutionExists("{fetch: {node: {geoNear2dsphere: {a: '2dsphere'}}}}");
+ }
+
+ TEST_F(QueryPlannerTest, Or2DSphereSameFieldNonNear) {
+ addIndex(BSON("a" << "2dsphere"));
+ runQuery(fromjson("{$or: [ {a: {$geoIntersects: {$geometry: "
+ "{type: 'Point', coordinates: [3.0, 1.0]}}}},"
+ " {a: {$geoIntersects: {$geometry: "
+ "{type: 'Point', coordinates: [4.0, 1.0]}}}}]}"));
+
+ assertNumSolutions(2U);
+ assertSolutionExists("{cscan: {dir: 1}}");
+ assertSolutionExists("{or: {nodes: ["
+ "{fetch: {node: {ixscan: {pattern: {a: '2dsphere'}}}}},"
+ "{fetch: {node: {ixscan: {pattern: {a: '2dsphere'}}}}}]}}");
+ }
+
//
// $in
//
diff --git a/src/mongo/db/query/query_planner_text_test.cpp b/src/mongo/db/query/query_planner_text_test.cpp
index e6d0a153543..f5877a9739c 100644
--- a/src/mongo/db/query/query_planner_text_test.cpp
+++ b/src/mongo/db/query/query_planner_text_test.cpp
@@ -553,4 +553,14 @@ namespace {
"{ixscan: {filter: null, pattern: {a:1}}}]}}}}");
}
+ TEST_F(QueryPlannerTest, AndTextWithGeoNonNear) {
+ addIndex(BSON("_fts" << "text" << "_ftsx" << 1));
+ runQuery(fromjson("{$text: {$search: 'foo'}, a: {$geoIntersects: {$geometry: "
+ "{type: 'Point', coordinates: [3.0, 1.0]}}}}"));
+
+ // Mandatory text index is used, and geo predicate becomes a filter.
+ assertNumSolutions(1U);
+ assertSolutionExists("{fetch: {node: {text: {search: 'foo'}}}}");
+ }
+
} // namespace