summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--etc/backports_required_for_multiversion_tests.yml2
-rw-r--r--jstests/core/geo_near_point_query.js51
-rw-r--r--src/mongo/db/exec/geo_near.cpp21
-rw-r--r--src/mongo/db/geo/geometry_container.cpp10
4 files changed, 80 insertions, 4 deletions
diff --git a/etc/backports_required_for_multiversion_tests.yml b/etc/backports_required_for_multiversion_tests.yml
index a22586a0777..47ddd16346d 100644
--- a/etc/backports_required_for_multiversion_tests.yml
+++ b/etc/backports_required_for_multiversion_tests.yml
@@ -100,6 +100,8 @@ all:
test_file: jstests/replsets/rollback_reconstructs_transactions_prepared_before_stable.js
- ticket: SERVER-54366
test_file: jstests/replsets/force_shutdown_primary.js
+ - ticket: SERVER-52953
+ test_file: jstests/core/geo_near_point_query.js
# Tests that should only be excluded from particular suites should be listed under that suite.
suites:
diff --git a/jstests/core/geo_near_point_query.js b/jstests/core/geo_near_point_query.js
new file mode 100644
index 00000000000..d3faba9c29e
--- /dev/null
+++ b/jstests/core/geo_near_point_query.js
@@ -0,0 +1,51 @@
+/**
+ * Verifies that $geoNear correctly matches the point given to the 'near' parameter when the
+ * 'maxDistance' parameter is set to 0.
+ *
+ * @tags: [backport_required_multiversion]
+ */
+(function() {
+"use strict";
+const collName = jsTestName();
+const coll = db[collName];
+coll.drop();
+
+const docs = [
+ {"location": {"type": "Point", "coordinates": [-90, 45.1]}},
+ {"location": {"type": "Point", "coordinates": [-90.0001, 45.1]}},
+ {"location": {"type": "Point", "coordinates": [-90, 45.00001]}},
+ {"location": {"type": "Point", "coordinates": [-90, 45.01]}},
+ {"location": {"type": "Point", "coordinates": [90, -45]}},
+ {"location": {"type": "Point", "coordinates": [90.00007, -45]}},
+ {"location": {"type": "Point", "coordinates": [90, 45]}},
+ {"location": {"type": "Point", "coordinates": [90, 45.1]}},
+ {"location": {"type": "Point", "coordinates": [90, 45.01]}},
+
+];
+assert.commandWorked(coll.insert(docs));
+assert.commandWorked(coll.createIndex({location: "2dsphere"}));
+
+for (const doc of docs) {
+ // We test a distance of 0 to verify that point queries work correctly as well as a small,
+ // non-zero distance of 0.001 to verify that the distance computation used in constructing
+ // an S2Cap doesn't underflow.
+ for (const dist of [0, 0.001]) {
+ const pipeline = [
+ {
+ $geoNear: {
+ near: doc["location"],
+ maxDistance: dist,
+ spherical: true,
+ distanceField: "dist.calculated",
+ includeLocs: "dist.location"
+ }
+ },
+ {$project: {_id: 0, location: 1}}
+ ];
+ const result = coll.aggregate(pipeline).toArray();
+ assert.eq(1, result.length, tojson(doc));
+ const item = result[0];
+ assert.eq(doc, item);
+ }
+}
+})();
diff --git a/src/mongo/db/exec/geo_near.cpp b/src/mongo/db/exec/geo_near.cpp
index 3a41a7a2538..b8b6670d10a 100644
--- a/src/mongo/db/exec/geo_near.cpp
+++ b/src/mongo/db/exec/geo_near.cpp
@@ -813,12 +813,27 @@ S2Region* buildS2Region(const R2Annulus& sphereBounds) {
regions.push_back(new S2Cap(innerCap));
}
+ // 'kEpsilon' is about 9 times the double-precision roundoff relative error.
+ const double kEpsilon = 1e-15;
+
// We only need to max bound if this is not a full search of the Earth
// Using the constant here is important since we use the min of kMaxEarthDistance
// and the actual bounds passed in to set up the search area.
- if (outer < kMaxEarthDistanceInMeters) {
- S2Cap outerCap = S2Cap::FromAxisAngle(latLng.ToPoint(),
- S1Angle::Radians(outer / kRadiusOfEarthInMeters));
+ if ((outer * (1 + kEpsilon)) < kMaxEarthDistanceInMeters) {
+ // SERVER-52953: The cell covering returned by S2 may have a matching point along its
+ // boundary. In certain cases, this boundary point is not contained within the covering,
+ // which means that this search will not match said point. As such, we avoid this issue by
+ // finding a covering for the region expanded over a very small radius because this covering
+ // is guaranteed to contain the boundary point.
+ auto angle = S1Angle::Radians((outer * (1 + kEpsilon)) / kRadiusOfEarthInMeters);
+ S2Cap outerCap = S2Cap::FromAxisAngle(latLng.ToPoint(), angle);
+
+ // If 'outer' is sufficiently small, the computation of the S2Cap's height from 'angle' may
+ // underflow, resulting in a height less than 'kEpsilon' and an empty cap. As such, we
+ // guarantee that 'outerCap' has a height of at least 'kEpsilon'.
+ if (outerCap.height() < kEpsilon) {
+ outerCap = S2Cap::FromAxisHeight(latLng.ToPoint(), kEpsilon);
+ }
regions.push_back(new S2Cap(outerCap));
}
diff --git a/src/mongo/db/geo/geometry_container.cpp b/src/mongo/db/geo/geometry_container.cpp
index 97ae2533fc8..a622323df64 100644
--- a/src/mongo/db/geo/geometry_container.cpp
+++ b/src/mongo/db/geo/geometry_container.cpp
@@ -1288,7 +1288,15 @@ double GeometryContainer::minDistance(const PointWithCRS& otherPoint) const {
double minDistance = -1;
if (NULL != _point) {
- minDistance = S2Distance::distanceRad(otherPoint.point, _point->point);
+ // SERVER-52953: Calculating the distance between identical points can sometimes result
+ // in a small positive value due to a loss of floating point precision on certain
+ // platforms. As such, we perform a simple equality check to guarantee that equivalent
+ // points will always produce a distance of 0.
+ if (_point->point == otherPoint.point) {
+ minDistance = 0;
+ } else {
+ minDistance = S2Distance::distanceRad(otherPoint.point, _point->point);
+ }
} else if (NULL != _line) {
minDistance = S2Distance::minDistanceRad(otherPoint.point, _line->line);
} else if (NULL != _polygon) {