0
0
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:
Maddie Zechar 2021-07-28 19:53:03 +00:00 committed by Evergreen Agent
parent d34cc3d0cc
commit f4e6aef833
2 changed files with 77 additions and 30 deletions

View File

@ -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);
}
})();

View File

@ -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++) {