mirror of
https://github.com/mongodb/mongo.git
synced 2024-12-01 09:32:32 +01:00
SERVER-58359: More flexible query containment for $geoWithin
This commit is contained in:
parent
d34cc3d0cc
commit
f4e6aef833
@ -1,4 +1,4 @@
|
||||
// @tags: [requires_non_retryable_writes, requires_fcv_50, requires_fcv_51]
|
||||
// @tags: [requires_non_retryable_writes, requires_fcv_51]
|
||||
|
||||
load("jstests/libs/analyze_plan.js");
|
||||
(function() {
|
||||
@ -61,10 +61,10 @@ if (isFeatureEnabled) {
|
||||
{a: 1, name: "Dallas", loc: {type: "Point", coordinates: [-96.808891, 32.779]}}));
|
||||
assert.commandWorked(coll.insert(
|
||||
{a: 1, name: "Paris TX", loc: {type: "Point", coordinates: [-95.555513, 33.6609389]}}));
|
||||
assert.commandWorked(coll.insert(
|
||||
{a: 1, name: "San Antonio", loc: {type: "Point", coordinates: [-98.4936282, 29.4241219]}}));
|
||||
assert.commandWorked(coll.insert(
|
||||
{a: 2, name: "Houston", loc: {type: "Point", coordinates: [-95.3632715, 29.7632836]}}));
|
||||
assert.commandWorked(coll.insert(
|
||||
{a: 1, name: "San Antonio", loc: {type: "Point", coordinates: [-98.4936282, 29.4241219]}}));
|
||||
assert.commandWorked(coll.insert(
|
||||
{a: 3, name: "LA", loc: {type: "Point", coordinates: [-118.2436849, 34.0522342]}}));
|
||||
assert.commandWorked(coll.insert(
|
||||
@ -78,8 +78,8 @@ if (isFeatureEnabled) {
|
||||
[-97.516473, 26.02054],
|
||||
[-106.528371, 31.895644],
|
||||
[-103.034724, 31.932947],
|
||||
[-102.979798, 36.456096],
|
||||
[-100.051947, 36.482601],
|
||||
[-103.068314, 36.426696],
|
||||
[-100.080033, 36.497382],
|
||||
[-99.975048, 34.506004],
|
||||
[-94.240190, 33.412542],
|
||||
[-94.075400, 29.725640],
|
||||
@ -113,46 +113,58 @@ if (isFeatureEnabled) {
|
||||
// Search for points only located in a smaller region within our larger index, in this case
|
||||
// Texas.
|
||||
var command = coll.find({
|
||||
$and: [
|
||||
{loc: {$geoWithin: {$geometry: southWestUSPolygon}}},
|
||||
{loc: {$geoWithin: {$geometry: texasPolygon}}},
|
||||
]
|
||||
a: 1,
|
||||
loc: {$geoWithin: {$geometry: texasPolygon}},
|
||||
});
|
||||
explainResults = command.explain("queryPlanner");
|
||||
winningPlan = getWinningPlan(explainResults.queryPlanner);
|
||||
var explainResults = command.explain("queryPlanner");
|
||||
var winningPlan = getWinningPlan(explainResults.queryPlanner);
|
||||
assert(isIxscan(db, winningPlan));
|
||||
var results = command.toArray();
|
||||
assert.eq(results.length, 4);
|
||||
|
||||
command = coll.find({
|
||||
a: 1,
|
||||
$and: [
|
||||
{loc: {$geoWithin: {$geometry: southWestUSPolygon}}},
|
||||
{loc: {$geoWithin: {$geometry: texasPolygon}}},
|
||||
]
|
||||
});
|
||||
explainResults = command.explain("queryPlanner");
|
||||
winningPlan = getWinningPlan(explainResults.queryPlanner);
|
||||
assert(isIxscan(db, winningPlan));
|
||||
results = command.toArray();
|
||||
assert.eq(results.length, 3);
|
||||
|
||||
// Test index maintenace to make sure a doc is removed from index when it is no longer in the
|
||||
// $geoWithin bounds.
|
||||
assert.commandWorked(coll.updateMany(
|
||||
{loc: {type: "Point", coordinates: [-95.555513, 33.6609389]}},
|
||||
{name: "Paris TX"},
|
||||
{$set: {name: "Paris France", loc: {type: "Point", coordinates: [2.360791, 48.885033]}}}));
|
||||
|
||||
command = coll.find({
|
||||
$and: [
|
||||
{loc: {$geoWithin: {$geometry: southWestUSPolygon}}},
|
||||
{loc: {$geoWithin: {$geometry: texasPolygon}}},
|
||||
]
|
||||
loc: {$geoWithin: {$geometry: texasPolygon}},
|
||||
});
|
||||
explainResults = command.explain("queryPlanner");
|
||||
winningPlan = getWinningPlan(explainResults.queryPlanner);
|
||||
assert(isIxscan(db, winningPlan));
|
||||
results = command.toArray();
|
||||
assert.eq(results.length, 3);
|
||||
|
||||
// Test using { $geoWithin: { $geometry : ...}} query on a collection indexed on $centerSphere
|
||||
// shape. This works because both centersphere and a Geo-JSON polygon define regions using
|
||||
// spherical-geometry.
|
||||
coll.drop();
|
||||
coll.createIndex({loc: "2dsphere"}, {
|
||||
partialFilterExpression:
|
||||
{loc: {$geoWithin: {$centerSphere: [[-74.0064, 40.7142], 10 / 3963.2]}}}
|
||||
});
|
||||
// Point corresponding to UWS of Manhattan.
|
||||
coll.insert({loc: [-73.974709, 40.793110]});
|
||||
// Point corresponding to Downtown Brooklyn.
|
||||
coll.insert({loc: [-73.985728, 40.705174]});
|
||||
// Polygon roughly representing the UWS of Manhattan.
|
||||
var uwsPolygon = {
|
||||
type: "Polygon",
|
||||
coordinates: [[
|
||||
[-73.987286, 40.771117],
|
||||
[-73.980511, 40.801531],
|
||||
[-73.958801, 40.800751],
|
||||
[-73.987286, 40.771117]
|
||||
]]
|
||||
};
|
||||
command = coll.find({loc: {$geoWithin: {$geometry: uwsPolygon}}});
|
||||
explainResults = command.explain("queryPlanner");
|
||||
winningPlan = getWinningPlan(explainResults.queryPlanner);
|
||||
assert(isIxscan(db, winningPlan));
|
||||
results = command.toArray();
|
||||
// We expect to only find one matching result because we only have one point in our collection
|
||||
// inside the limits of our polygon (or in other words, inside the UWS of Manhattan ).
|
||||
assert.eq(results.length, 1);
|
||||
}
|
||||
})();
|
@ -34,6 +34,7 @@
|
||||
#include "mongo/db/matcher/expression_algo.h"
|
||||
#include "mongo/db/matcher/expression_array.h"
|
||||
#include "mongo/db/matcher/expression_expr.h"
|
||||
#include "mongo/db/matcher/expression_geo.h"
|
||||
#include "mongo/db/matcher/expression_leaf.h"
|
||||
#include "mongo/db/matcher/expression_tree.h"
|
||||
#include "mongo/db/matcher/schema/expression_internal_schema_xor.h"
|
||||
@ -346,6 +347,7 @@ bool hasExistencePredicateOnPath(const MatchExpression& expr, StringData path) {
|
||||
}
|
||||
|
||||
bool isSubsetOf(const MatchExpression* lhs, const MatchExpression* rhs) {
|
||||
// lhs is the query and rhs is the index.
|
||||
invariant(lhs);
|
||||
invariant(rhs);
|
||||
|
||||
@ -353,6 +355,39 @@ bool isSubsetOf(const MatchExpression* lhs, const MatchExpression* rhs) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (lhs->matchType() == MatchExpression::GEO && rhs->matchType() == MatchExpression::GEO) {
|
||||
// lhs is the query, eg {loc: {$geoWithin: {$geometry: {type: "Polygon", coordinates:
|
||||
// [...]}}}} geoWithinObj is {$geoWithin: {$geometry: {type: "Polygon", coordinates:
|
||||
// [...]}}} geoWithinElement is '$geoWithin: {$geometry: {type: "Polygon", coordinates:
|
||||
// [...]}}' geometryObj is {$geometry: {type: "Polygon", coordinates: [...]}}
|
||||
// geometryElement '$geometry: {type: "Polygon", coordinates: [...]}'
|
||||
|
||||
const GeoMatchExpression* queryMatchExpression =
|
||||
static_cast<const GeoMatchExpression*>(lhs);
|
||||
// We only handle geoWithin queries
|
||||
if (queryMatchExpression->getGeoExpression().getPred() != GeoExpression::WITHIN) {
|
||||
return false;
|
||||
}
|
||||
const GeoMatchExpression* indexMatchExpression =
|
||||
static_cast<const GeoMatchExpression*>(rhs);
|
||||
auto geoWithinObj = queryMatchExpression->getSerializedRightHandSide();
|
||||
auto geoWithinElement = geoWithinObj.firstElement();
|
||||
auto geometryObj = geoWithinElement.Obj();
|
||||
|
||||
// More specifically, we only handle geoWithin queries that use the $geometry operator.
|
||||
if (!geometryObj.hasField("$geometry")) {
|
||||
return false;
|
||||
}
|
||||
auto geometryElement = geometryObj.firstElement();
|
||||
MatchDetails* details = nullptr;
|
||||
|
||||
if (indexMatchExpression->matchesSingleElement(geometryElement, details)) {
|
||||
// The region described by query is within the region captured by the index.
|
||||
// Therefore this index can be used in a potential solution for this query.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (rhs->matchType() == MatchExpression::AND) {
|
||||
// 'lhs' must match a subset of the documents matched by each clause of 'rhs'.
|
||||
for (size_t i = 0; i < rhs->numChildren(); i++) {
|
||||
|
Loading…
Reference in New Issue
Block a user