diff options
-rw-r--r-- | jstests/core/geo_s2intersection.js | 23 | ||||
-rw-r--r-- | src/mongo/db/query/planner_access.cpp | 38 | ||||
-rw-r--r-- | src/mongo/db/query/query_planner_test.cpp | 98 | ||||
-rw-r--r-- | src/mongo/db/query/query_planner_text_test.cpp | 10 |
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 |