summaryrefslogtreecommitdiff
path: root/src/mongo
diff options
context:
space:
mode:
authorSiyuan Zhou <siyuan.zhou@mongodb.com>2014-08-12 14:29:11 -0400
committerSiyuan Zhou <siyuan.zhou@mongodb.com>2014-08-21 16:21:49 -0400
commit528ddd163be6d93f839d57cd068bd17ab55440cd (patch)
treeb8e1dc6e5a885ccaeae1584c2384a3f118392b1f /src/mongo
parent4cdeada1cc17bdf3fec7b39b2ec66faf8b84054d (diff)
downloadmongo-528ddd163be6d93f839d57cd068bd17ab55440cd.tar.gz
SERVER-14508 Break header dependencies of geo stuffs in query framework.
Diffstat (limited to 'src/mongo')
-rw-r--r--src/mongo/SConscript4
-rw-r--r--src/mongo/db/commands/geo_near_cmd.cpp4
-rw-r--r--src/mongo/db/exec/geo_near.cpp38
-rw-r--r--src/mongo/db/exec/geo_near.h7
-rw-r--r--src/mongo/db/geo/geo_query.cpp295
-rw-r--r--src/mongo/db/geo/geo_query.h118
-rw-r--r--src/mongo/db/geo/geometry_container.cpp2
-rw-r--r--src/mongo/db/geo/r2_region_coverer.cpp1
-rw-r--r--src/mongo/db/geo/r2_region_coverer.h3
-rw-r--r--src/mongo/db/geo/s2common.cpp106
-rw-r--r--src/mongo/db/index/expression_keys_private.cpp2
-rw-r--r--src/mongo/db/index/expression_params.h2
-rw-r--r--src/mongo/db/index/external_key_generator.cpp2
-rw-r--r--src/mongo/db/index/s2_access_method.cpp2
-rw-r--r--src/mongo/db/index/s2_access_method.h2
-rw-r--r--src/mongo/db/index/s2_common.h (renamed from src/mongo/db/geo/s2common.h)12
-rw-r--r--src/mongo/db/matcher/expression_geo.cpp302
-rw-r--r--src/mongo/db/matcher/expression_geo.h94
-rw-r--r--src/mongo/db/matcher/expression_geo_test.cpp6
-rw-r--r--src/mongo/db/matcher/expression_parser_geo.cpp4
-rw-r--r--src/mongo/db/query/canonical_query.cpp14
-rw-r--r--src/mongo/db/query/index_bounds_builder.cpp9
-rw-r--r--src/mongo/db/query/planner_access.cpp4
-rw-r--r--src/mongo/db/query/planner_ixselect.cpp10
-rw-r--r--src/mongo/db/query/query_solution.cpp5
-rw-r--r--src/mongo/db/query/query_solution.h9
26 files changed, 453 insertions, 604 deletions
diff --git a/src/mongo/SConscript b/src/mongo/SConscript
index c470316a679..ca460c6346f 100644
--- a/src/mongo/SConscript
+++ b/src/mongo/SConscript
@@ -179,8 +179,7 @@ env.Library('expressions',
env.Library('expressions_geo',
['db/matcher/expression_geo.cpp',
- 'db/matcher/expression_parser_geo.cpp',
- 'db/geo/geo_query.cpp'],
+ 'db/matcher/expression_parser_geo.cpp'],
LIBDEPS=['expressions','db/geo/geometry','db/geo/geoparser'] )
env.Library('expressions_text',
@@ -635,7 +634,6 @@ serverOnlyFiles = [ "db/curop.cpp",
"db/index_rebuilder.cpp",
"db/commands/geo_near_cmd.cpp",
"db/geo/haystack.cpp",
- "db/geo/s2common.cpp",
"db/ops/delete.cpp",
"db/ops/delete_executor.cpp",
"db/ops/insert.cpp",
diff --git a/src/mongo/db/commands/geo_near_cmd.cpp b/src/mongo/db/commands/geo_near_cmd.cpp
index 7631e2f279b..187fdf1caad 100644
--- a/src/mongo/db/commands/geo_near_cmd.cpp
+++ b/src/mongo/db/commands/geo_near_cmd.cpp
@@ -35,8 +35,8 @@
#include "mongo/db/commands.h"
#include "mongo/db/curop.h"
#include "mongo/db/geo/geoconstants.h"
-#include "mongo/db/geo/geo_query.h"
-#include "mongo/db/geo/s2common.h"
+#include "mongo/db/matcher/expression_geo.h"
+#include "mongo/db/geo/geoparser.h"
#include "mongo/db/index_names.h"
#include "mongo/db/index/index_descriptor.h"
#include "mongo/db/jsobj.h"
diff --git a/src/mongo/db/exec/geo_near.cpp b/src/mongo/db/exec/geo_near.cpp
index 270a18152d2..c9ac11ced74 100644
--- a/src/mongo/db/exec/geo_near.cpp
+++ b/src/mongo/db/exec/geo_near.cpp
@@ -137,12 +137,12 @@ namespace mongo {
// Must have an object in order to get geometry out of it.
invariant(member->hasObj());
- CRS queryCRS = nearParams.nearQuery.centroid.crs;
+ CRS queryCRS = nearParams.nearQuery->centroid->crs;
// Extract all the geometries out of this document for the near query
OwnedPointerVector<StoredGeometry> geometriesOwned;
vector<StoredGeometry*>& geometries = geometriesOwned.mutableVector();
- extractGeometries(member->obj, nearParams.nearQuery.field, &geometries);
+ extractGeometries(member->obj, nearParams.nearQuery->field, &geometries);
// Compute the minimum distance of all the geometries in the document
double minDistance = -1;
@@ -164,7 +164,7 @@ namespace mongo {
continue;
stored.geometry.projectInto(queryCRS);
- double nextDistance = stored.geometry.minDistance(nearParams.nearQuery.centroid);
+ double nextDistance = stored.geometry.minDistance(*nearParams.nearQuery->centroid);
if (minDistance < 0 || nextDistance < minDistance) {
minDistance = nextDistance;
@@ -178,7 +178,7 @@ namespace mongo {
}
if (nearParams.addDistMeta) {
- if (nearParams.nearQuery.unitsAreRadians) {
+ if (nearParams.nearQuery->unitsAreRadians) {
// Hack for nearSphere
// TODO: Remove nearSphere?
invariant(SPHERE == queryCRS);
@@ -197,12 +197,12 @@ namespace mongo {
return StatusWith<double>(minDistance);
}
- static R2Annulus geoNearDistanceBounds(const NearQuery& query) {
+ static R2Annulus geoNearDistanceBounds(const GeoNearExpression& query) {
- const CRS queryCRS = query.centroid.crs;
+ const CRS queryCRS = query.centroid->crs;
if (FLAT == queryCRS) {
- return R2Annulus(query.centroid.oldPoint, query.minDistance, query.maxDistance);
+ return R2Annulus(query.centroid->oldPoint, query.minDistance, query.maxDistance);
}
invariant(SPHERE == queryCRS);
@@ -224,7 +224,7 @@ namespace mongo {
// place.
// TODO: Wrapping behavior should not depend on the index, which would make $near code
// insensitive to which direction we explore the index in.
- return R2Annulus(query.centroid.oldPoint,
+ return R2Annulus(query.centroid->oldPoint,
min(minDistance, kMaxEarthDistanceInMeters),
min(maxDistance, kMaxEarthDistanceInMeters));
}
@@ -236,8 +236,8 @@ namespace mongo {
static R2Annulus twoDDistanceBounds(const GeoNearParams& nearParams,
const IndexDescriptor* twoDIndex) {
- R2Annulus fullBounds = geoNearDistanceBounds(nearParams.nearQuery);
- const CRS queryCRS = nearParams.nearQuery.centroid.crs;
+ R2Annulus fullBounds = geoNearDistanceBounds(*nearParams.nearQuery);
+ const CRS queryCRS = nearParams.nearQuery->centroid->crs;
if (FLAT == queryCRS) {
@@ -259,7 +259,7 @@ namespace mongo {
// Spherical queries have upper bounds set by the earth - no-op
// TODO: Wrapping errors would creep in here if nearSphere wasn't defined to not wrap
invariant(SPHERE == queryCRS);
- invariant(!nearParams.nearQuery.isWrappingQuery);
+ invariant(!nearParams.nearQuery->isWrappingQuery);
}
return fullBounds;
@@ -267,7 +267,7 @@ namespace mongo {
static double twoDBoundsIncrement(const GeoNearParams& nearParams) {
// TODO: Revisit and tune these
- if (FLAT == nearParams.nearQuery.centroid.crs) {
+ if (FLAT == nearParams.nearQuery->centroid->crs) {
return 10;
}
else {
@@ -454,7 +454,7 @@ namespace mongo {
};
}
- static double min2DBoundsIncrement(NearQuery query, IndexDescriptor* twoDIndex) {
+ static double min2DBoundsIncrement(const GeoNearExpression& query, IndexDescriptor* twoDIndex) {
GeoHashConverter::Parameters hashParams;
Status status = GeoHashConverter::parseParameters(twoDIndex->infoObj(), &hashParams);
invariant(status.isOK()); // The index status should always be valid
@@ -465,7 +465,7 @@ namespace mongo {
// max radius. This is slightly conservative for now (box diagonal vs circle radius).
double minBoundsIncrement = hasher.getError() / 2;
- const CRS queryCRS = query.centroid.crs;
+ const CRS queryCRS = query.centroid->crs;
if (FLAT == queryCRS)
return minBoundsIncrement;
@@ -514,7 +514,7 @@ namespace mongo {
}
_boundsIncrement = max(_boundsIncrement,
- min2DBoundsIncrement(_nearParams.nearQuery, _twoDIndex));
+ min2DBoundsIncrement(*_nearParams.nearQuery, _twoDIndex));
R2Annulus nextBounds(_currBounds.center(),
_currBounds.getOuter(),
@@ -528,7 +528,7 @@ namespace mongo {
// Get a covering region for this interval
//
- const CRS queryCRS = _nearParams.nearQuery.centroid.crs;
+ const CRS queryCRS = _nearParams.nearQuery->centroid->crs;
auto_ptr<R2Region> coverRegion;
@@ -602,7 +602,7 @@ namespace mongo {
scanParams.bounds = _nearParams.baseBounds;
// The "2d" field is always the first in the index
- const string twoDFieldName = _nearParams.nearQuery.field;
+ const string twoDFieldName = _nearParams.nearQuery->field;
const int twoDFieldPosition = 0;
OrderedIntervalList coveredIntervals;
@@ -700,7 +700,7 @@ namespace mongo {
STAGE_GEO_NEAR_2DSPHERE)),
_nearParams(nearParams),
_s2Index(s2Index),
- _fullBounds(geoNearDistanceBounds(nearParams.nearQuery)),
+ _fullBounds(geoNearDistanceBounds(*nearParams.nearQuery)),
_currBounds(_fullBounds.center(), -1, _fullBounds.getInner()),
_boundsIncrement(twoDSphereBoundsIncrement(s2Index)) {
@@ -865,7 +865,7 @@ namespace mongo {
scanParams.bounds = _nearParams.baseBounds;
// Because the planner doesn't yet set up 2D index bounds, do it ourselves here
- const string s2Field = _nearParams.nearQuery.field;
+ const string s2Field = _nearParams.nearQuery->field;
const int s2FieldPosition = getFieldPosition(_s2Index, s2Field);
scanParams.bounds.fields[s2FieldPosition].intervals.clear();
OrderedIntervalList* coveredIntervals = &scanParams.bounds.fields[s2FieldPosition];
diff --git a/src/mongo/db/exec/geo_near.h b/src/mongo/db/exec/geo_near.h
index 0eb656ee20e..d7f2bb3e8f6 100644
--- a/src/mongo/db/exec/geo_near.h
+++ b/src/mongo/db/exec/geo_near.h
@@ -32,8 +32,9 @@
#include "mongo/db/exec/working_set.h"
#include "mongo/db/exec/plan_stats.h"
#include "mongo/db/index/index_descriptor.h"
-#include "mongo/db/geo/geo_query.h"
+#include "mongo/db/geo/geometry_container.h"
#include "mongo/db/matcher/expression.h"
+#include "mongo/db/matcher/expression_geo.h"
#include "mongo/db/query/index_bounds.h"
namespace mongo {
@@ -48,11 +49,13 @@ namespace mongo {
}
// MatchExpression to apply to the index keys and fetched documents
+ // Not owned here, owned by solution nodes
MatchExpression* filter;
// Index scan bounds, not including the geo bounds
IndexBounds baseBounds;
- NearQuery nearQuery;
+ // Not owned here
+ const GeoNearExpression* nearQuery;
bool addPointMeta;
bool addDistMeta;
};
diff --git a/src/mongo/db/geo/geo_query.cpp b/src/mongo/db/geo/geo_query.cpp
deleted file mode 100644
index 22270a2732e..00000000000
--- a/src/mongo/db/geo/geo_query.cpp
+++ /dev/null
@@ -1,295 +0,0 @@
-/**
- * Copyright (C) 2013 MongoDB Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- * As a special exception, the copyright holders give permission to link the
- * code of portions of this program with the OpenSSL library under certain
- * conditions as described in each individual source file and distribute
- * linked combinations including the program with the OpenSSL library. You
- * must comply with the GNU Affero General Public License in all respects for
- * all of the code used other than as permitted herein. If you modify file(s)
- * with this exception, you may extend this exception to your version of the
- * file(s), but you are not obligated to do so. If you do not wish to do so,
- * delete this exception statement from your version. If you delete this
- * exception statement from all source files in the program, then also delete
- * it in the license file.
- */
-
-#include "mongo/db/geo/geo_query.h"
-
-#include "mongo/db/geo/geoparser.h"
-#include "mongo/util/log.h"
-#include "mongo/util/mongoutils/str.h"
-
-namespace mongo {
-
- using mongoutils::str::equals;
-
- bool NearQuery::parseLegacyQuery(const BSONObj &obj) {
-
- bool hasGeometry = false;
-
- // First, try legacy near, e.g.:
- // t.find({ loc : { $nearSphere: [0,0], $minDistance: 1, $maxDistance: 3 }})
- // t.find({ loc : { $nearSphere: [0,0] }})
- // t.find({ loc : { $near : [0, 0, 1] } });
- // t.find({ loc : { $near: { someGeoJSONPoint}})
- // t.find({ loc : { $geoNear: { someGeoJSONPoint}})
- BSONObjIterator it(obj);
- while (it.more()) {
- BSONElement e = it.next();
- if (equals(e.fieldName(), "$near") || equals(e.fieldName(), "$geoNear")
- || equals(e.fieldName(), "$nearSphere")) {
- if (!e.isABSONObj()) { return false; }
- BSONObj embeddedObj = e.embeddedObject();
-
- if ((GeoParser::isPoint(embeddedObj) && GeoParser::parsePoint(embeddedObj, &centroid))
- || GeoParser::parsePointWithMaxDistance(embeddedObj, &centroid, &maxDistance)) {
- uassert(18522, "max distance must be non-negative", maxDistance >= 0.0);
- hasGeometry = true;
- isNearSphere = equals(e.fieldName(), "$nearSphere");
- }
- } else if (equals(e.fieldName(), "$minDistance")) {
- uassert(16893, "$minDistance must be a number", e.isNumber());
- minDistance = e.Number();
- uassert(16894, "$minDistance must be non-negative", minDistance >= 0.0);
- } else if (equals(e.fieldName(), "$maxDistance")) {
- uassert(16895, "$maxDistance must be a number", e.isNumber());
- maxDistance = e.Number();
- uassert(16896, "$maxDistance must be non-negative", maxDistance >= 0.0);
- } else if (equals(e.fieldName(), "$uniqueDocs")) {
- warning() << "ignoring deprecated option $uniqueDocs";
- }
- }
-
- return hasGeometry;
- }
-
- Status NearQuery::parseNewQuery(const BSONObj &obj) {
- bool hasGeometry = false;
-
- BSONObjIterator objIt(obj);
- if (!objIt.more()) {
- return Status(ErrorCodes::BadValue, "empty geo near query object");
- }
- BSONElement e = objIt.next();
- // Just one arg. to $geoNear.
- if (objIt.more()) {
- return Status(ErrorCodes::BadValue, mongoutils::str::stream() <<
- "geo near accepts just one argument when querying for a GeoJSON " <<
- "point. Extra field found: " << objIt.next());
- }
-
- // Parse "new" near:
- // t.find({"geo" : {"$near" : {"$geometry": pointA, $minDistance: 1, $maxDistance: 3}}})
- // t.find({"geo" : {"$geoNear" : {"$geometry": pointA, $minDistance: 1, $maxDistance: 3}}})
- if (!e.isABSONObj()) {
- return Status(ErrorCodes::BadValue, "geo near query argument is not an object");
- }
- BSONObj::MatchType matchType = static_cast<BSONObj::MatchType>(e.getGtLtOp());
- if (BSONObj::opNEAR != matchType) {
- return Status(ErrorCodes::BadValue, mongoutils::str::stream() <<
- "invalid geo near query operator: " << e.fieldName());
- }
-
- // Iterate over the argument.
- BSONObjIterator it(e.embeddedObject());
- while (it.more()) {
- BSONElement e = it.next();
- if (equals(e.fieldName(), "$geometry")) {
- if (e.isABSONObj()) {
- BSONObj embeddedObj = e.embeddedObject();
- uassert(16885, "$near requires a point, given " + embeddedObj.toString(),
- GeoParser::isPoint(embeddedObj));
- if (!GeoParser::parsePoint(embeddedObj, &centroid)) {
- return Status(ErrorCodes::BadValue, mongoutils::str::stream() <<
- "invalid point in geo near query $geometry argument: " <<
- embeddedObj);
- }
- uassert(16681, "$near requires geojson point, given " + embeddedObj.toString(),
- (SPHERE == centroid.crs));
- hasGeometry = true;
- }
- } else if (equals(e.fieldName(), "$minDistance")) {
- uassert(16897, "$minDistance must be a number", e.isNumber());
- minDistance = e.Number();
- uassert(16898, "$minDistance must be non-negative", minDistance >= 0.0);
- } else if (equals(e.fieldName(), "$maxDistance")) {
- uassert(16899, "$maxDistance must be a number", e.isNumber());
- maxDistance = e.Number();
- uassert(16900, "$maxDistance must be non-negative", maxDistance >= 0.0);
- }
- }
-
- if (!hasGeometry) {
- return Status(ErrorCodes::BadValue, "$geometry is required for geo near query");
- }
-
- return Status::OK();
- }
-
-
- Status NearQuery::parseFrom(const BSONObj &obj) {
-
- Status status = Status::OK();
-
- if (!parseLegacyQuery(obj)) {
- // Clear out any half-baked data.
- minDistance = 0;
- isNearSphere = false;
- maxDistance = std::numeric_limits<double>::max();
- centroid = PointWithCRS();
- // ...and try parsing new format.
- status = parseNewQuery(obj);
- }
-
- if (!status.isOK())
- return status;
-
- // Fixup the near query for anonoyances caused by $nearSphere
- if (isNearSphere) {
-
- // The user-provided point can be flat for a spherical query - needs to be projectable
- uassert(17444,
- "Legacy point is out of bounds for spherical query",
- ShapeProjection::supportsProject(centroid, SPHERE));
-
- unitsAreRadians = SPHERE != centroid.crs;
- // GeoJSON points imply wrapping queries
- isWrappingQuery = SPHERE == centroid.crs;
-
- // Project the point to a spherical CRS now that we've got the settings we need
- // We need to manually project here since we aren't using GeometryContainer
- ShapeProjection::projectInto(&centroid, SPHERE);
- }
- else {
- unitsAreRadians = false;
- isWrappingQuery = SPHERE == centroid.crs;
- }
-
- return status;
- }
-
- bool GeoQuery::parseLegacyQuery(const BSONObj &obj) {
- // The only legacy syntax is {$within: {.....}}
- BSONObjIterator outerIt(obj);
- if (!outerIt.more()) { return false; }
- BSONElement withinElt = outerIt.next();
- if (outerIt.more()) { return false; }
- if (!withinElt.isABSONObj()) { return false; }
- if (!equals(withinElt.fieldName(), "$within") && !equals(withinElt.fieldName(), "$geoWithin")) {
- return false;
- }
- BSONObj withinObj = withinElt.embeddedObject();
-
- bool hasGeometry = false;
-
- BSONObjIterator withinIt(withinObj);
- while (withinIt.more()) {
- BSONElement elt = withinIt.next();
- if (equals(elt.fieldName(), "$uniqueDocs")) {
- warning() << "deprecated $uniqueDocs option: " << obj.toString() << endl;
- // return false;
- }
- else if (elt.isABSONObj()) {
- hasGeometry = geoContainer.parseFrom(elt.wrap());
- }
- else {
- warning() << "bad geo query: " << obj.toString() << endl;
- return false;
- }
- }
-
- predicate = GeoQuery::WITHIN;
-
- return hasGeometry;
- }
-
- bool GeoQuery::parseNewQuery(const BSONObj &obj) {
- // pointA = { "type" : "Point", "coordinates": [ 40, 5 ] }
- // t.find({ "geo" : { "$intersect" : { "$geometry" : pointA} } })
- // t.find({ "geo" : { "$within" : { "$geometry" : polygon } } })
- // where field.name is "geo"
- BSONElement e = obj.firstElement();
- if (!e.isABSONObj()) { return false; }
-
- BSONObj::MatchType matchType = static_cast<BSONObj::MatchType>(e.getGtLtOp());
- if (BSONObj::opGEO_INTERSECTS == matchType) {
- predicate = GeoQuery::INTERSECT;
- } else if (BSONObj::opWITHIN == matchType) {
- predicate = GeoQuery::WITHIN;
- } else {
- return false;
- }
-
- bool hasGeometry = false;
- BSONObjIterator argIt(e.embeddedObject());
- while (argIt.more()) {
- BSONElement e = argIt.next();
- if (mongoutils::str::equals(e.fieldName(), "$geometry")) {
- if (e.isABSONObj()) {
- BSONObj embeddedObj = e.embeddedObject();
- if (geoContainer.parseFrom(embeddedObj)) {
- hasGeometry = true;
- }
- }
- }
- }
-
- // Don't want to give the error below if we could not pull any geometry out.
- if (!hasGeometry) { return false; }
-
- if (GeoQuery::WITHIN == predicate) {
- // Why do we only deal with $within {polygon}?
- // 1. Finding things within a point is silly and only valid
- // for points and degenerate lines/polys.
- //
- // 2. Finding points within a line is easy but that's called intersect.
- // Finding lines within a line is kind of tricky given what S2 gives us.
- // Doing line-within-line is a valid yet unsupported feature,
- // though I wonder if we want to preserve orientation for lines or
- // allow (a,b),(c,d) to be within (c,d),(a,b). Anyway, punt on
- // this for now.
- uassert(16672, "$within not supported with provided geometry: " + obj.toString(),
- geoContainer.supportsContains());
- }
-
- return hasGeometry;
- }
-
- bool GeoQuery::parseFrom(const BSONObj &obj) {
- if (!(parseLegacyQuery(obj) || parseNewQuery(obj)))
- return false;
-
- // Big polygon with strict winding order is represented as an S2Loop in SPHERE CRS.
- // So converting the query to SPHERE CRS makes things easier than projecting all the data
- // into STRICT_SPHERE CRS.
- if (STRICT_SPHERE == geoContainer.getNativeCRS()) {
- if (!geoContainer.supportsProject(SPHERE))
- return false;
- geoContainer.projectInto(SPHERE);
- }
-
- // $geoIntersect queries are hardcoded to *always* be in SPHERE CRS
- // TODO: This is probably bad semantics, should not do this
- if (GeoQuery::INTERSECT == predicate) {
- if (!geoContainer.supportsProject(SPHERE))
- return false;
- geoContainer.projectInto(SPHERE);
- }
-
- return true;
- }
-
-} // namespace mongo
diff --git a/src/mongo/db/geo/geo_query.h b/src/mongo/db/geo/geo_query.h
deleted file mode 100644
index 5390d7c5a23..00000000000
--- a/src/mongo/db/geo/geo_query.h
+++ /dev/null
@@ -1,118 +0,0 @@
-/**
-* Copyright (C) 2013 10gen Inc.
-*
-* This program is free software: you can redistribute it and/or modify
-* it under the terms of the GNU Affero General Public License, version 3,
-* as published by the Free Software Foundation.
-*
-* This program is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see <http://www.gnu.org/licenses/>.
-*
-* As a special exception, the copyright holders give permission to link the
-* code of portions of this program with the OpenSSL library under certain
-* conditions as described in each individual source file and distribute
-* linked combinations including the program with the OpenSSL library. You
-* must comply with the GNU Affero General Public License in all respects for
-* all of the code used other than as permitted herein. If you modify file(s)
-* with this exception, you may extend this exception to your version of the
-* file(s), but you are not obligated to do so. If you do not wish to do so,
-* delete this exception statement from your version. If you delete this
-* exception statement from all source files in the program, then also delete
-* it in the license file.
-*/
-
-#pragma once
-
-#include <string>
-
-#include "mongo/db/geo/shapes.h"
-#include "mongo/db/geo/geometry_container.h"
-
-namespace mongo {
-
- // TODO: Make a struct, turn parse stuff into something like
- // static Status parseNearQuery(const BSONObj& obj, NearQuery** out);
- class NearQuery {
- public:
- NearQuery()
- : minDistance(0),
- maxDistance(std::numeric_limits<double>::max()),
- isNearSphere(false),
- unitsAreRadians(false),
- isWrappingQuery(false) { }
-
- NearQuery(const std::string& f)
- : field(f),
- minDistance(0),
- maxDistance(std::numeric_limits<double>::max()),
- isNearSphere(false),
- unitsAreRadians(false),
- isWrappingQuery(false) { }
-
- Status parseFrom(const BSONObj &obj);
-
- // The name of the field that contains the geometry.
- std::string field;
-
- // The starting point of the near search.
- PointWithCRS centroid;
-
- // Min and max distance from centroid that we're willing to search.
- // Distance is in units of the geometry's CRS, except SPHERE and isNearSphere => radians
- double minDistance;
- double maxDistance;
-
- // Is this a $nearSphere query
- bool isNearSphere;
- // $nearSphere with a legacy point implies units are radians
- bool unitsAreRadians;
- // $near with a non-legacy point implies a wrapping query, otherwise the query doesn't wrap
- bool isWrappingQuery;
-
- std::string toString() const {
- std::stringstream ss;
- ss << " field=" << field;
- ss << " maxdist=" << maxDistance;
- ss << " isNearSphere=" << isNearSphere;
- return ss.str();
- }
-
- private:
- bool parseLegacyQuery(const BSONObj &obj);
- Status parseNewQuery(const BSONObj &obj);
- };
-
- // This represents either a $within or a $geoIntersects.
- class GeoQuery {
- public:
- GeoQuery() : field(""), predicate(INVALID) {}
- GeoQuery(const std::string& f) : field(f), predicate(INVALID) {}
-
- enum Predicate {
- WITHIN,
- INTERSECT,
- INVALID
- };
-
- bool parseFrom(const BSONObj &obj);
-
- std::string getField() const { return field; }
- Predicate getPred() const { return predicate; }
- const GeometryContainer& getGeometry() const { return geoContainer; }
-
- private:
- // Try to parse the provided object into the right place.
- bool parseLegacyQuery(const BSONObj &obj);
- bool parseNewQuery(const BSONObj &obj);
-
- // Name of the field in the query.
- std::string field;
- GeometryContainer geoContainer;
- Predicate predicate;
- };
-} // namespace mongo
diff --git a/src/mongo/db/geo/geometry_container.cpp b/src/mongo/db/geo/geometry_container.cpp
index c7ad0706ded..e62799a917c 100644
--- a/src/mongo/db/geo/geometry_container.cpp
+++ b/src/mongo/db/geo/geometry_container.cpp
@@ -29,7 +29,7 @@
#include "mongo/db/geo/geometry_container.h"
#include "mongo/db/geo/geoconstants.h"
-#include "mongo/db/geo/s2common.h"
+#include "mongo/db/geo/geoparser.h"
#include "mongo/util/mongoutils/str.h"
namespace mongo {
diff --git a/src/mongo/db/geo/r2_region_coverer.cpp b/src/mongo/db/geo/r2_region_coverer.cpp
index b63bee21331..37dde0ac8e9 100644
--- a/src/mongo/db/geo/r2_region_coverer.cpp
+++ b/src/mongo/db/geo/r2_region_coverer.cpp
@@ -30,6 +30,7 @@
#include "mongo/platform/basic.h"
+#include "mongo/db/geo/shapes.h"
#include "mongo/db/geo/r2_region_coverer.h"
#include "mongo/util/log.h"
diff --git a/src/mongo/db/geo/r2_region_coverer.h b/src/mongo/db/geo/r2_region_coverer.h
index 1bdc8104649..d3c0620535d 100644
--- a/src/mongo/db/geo/r2_region_coverer.h
+++ b/src/mongo/db/geo/r2_region_coverer.h
@@ -31,11 +31,12 @@
#include <queue>
#include "mongo/db/geo/hash.h"
-#include "mongo/db/geo/shapes.h"
namespace mongo {
+ class R2Region;
+
class R2RegionCoverer : boost::noncopyable {
// By default, the covering uses at most 8 cells at any level.
static const int kDefaultMaxCells; // = 8;
diff --git a/src/mongo/db/geo/s2common.cpp b/src/mongo/db/geo/s2common.cpp
deleted file mode 100644
index 9be2465640a..00000000000
--- a/src/mongo/db/geo/s2common.cpp
+++ /dev/null
@@ -1,106 +0,0 @@
-/**
-* Copyright (C) 2012 10gen Inc.
-*
-* This program is free software: you can redistribute it and/or modify
-* it under the terms of the GNU Affero General Public License, version 3,
-* as published by the Free Software Foundation.
-*
-* This program is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see <http://www.gnu.org/licenses/>.
-*
-* As a special exception, the copyright holders give permission to link the
-* code of portions of this program with the OpenSSL library under certain
-* conditions as described in each individual source file and distribute
-* linked combinations including the program with the OpenSSL library. You
-* must comply with the GNU Affero General Public License in all respects for
-* all of the code used other than as permitted herein. If you modify file(s)
-* with this exception, you may extend this exception to your version of the
-* file(s), but you are not obligated to do so. If you do not wish to do so,
-* delete this exception statement from your version. If you delete this
-* exception statement from all source files in the program, then also delete
-* it in the license file.
-*/
-
-#include "mongo/db/geo/s2common.h"
-
-#include "mongo/db/geo/geoconstants.h"
-#include "mongo/db/geo/geoparser.h"
-#include "mongo/db/geo/s2.h"
-#include "third_party/s2/s2cell.h"
-#include "third_party/s2/s2regioncoverer.h"
-
-namespace mongo {
-
- static string myitoa(int d) {
- stringstream ss;
- ss << d;
- return ss.str();
- }
-
- void S2SearchUtil::setCoverLimitsBasedOnArea(double area, S2RegionCoverer *coverer,
- int coarsestIndexedLevel) {
- area = sqrt(area);
- coverer->set_min_level(min(coarsestIndexedLevel, 2 + S2::kAvgEdge.GetClosestLevel(area)));
- coverer->set_max_level(4 + coverer->min_level());
- }
-
- BSONObj S2SearchUtil::coverAsBSON(const vector<S2CellId> &cover, const string& field,
- const int coarsestIndexedLevel) {
- BSONObjBuilder queryBuilder;
- BSONObjBuilder inBuilder(queryBuilder.subobjStart(field));
- // To have an array where elements of that array are regexes, we have to do this.
- BSONObjBuilder inArrayBuilder(inBuilder.subarrayStart("$in"));
- // Sadly we must keep track of this ourselves. Oh, BSONObjBuilder, you rascal!
- int arrayPos = 0;
-
- bool considerCoarser = false;
-
- // Look at the cells we cover and all cells that are within our covering and
- // finer. Anything with our cover as a strict prefix is contained within the cover and
- // should be intersection tested.
- for (size_t i = 0; i < cover.size(); ++i) {
- // First argument is position in the array as a string.
- // Third argument is options to regex.
- inArrayBuilder.appendRegex(myitoa(arrayPos++), "^" + cover[i].toString(), "");
- // If any of our covers could be covered by something in the index, we have
- // to look at things coarser.
- considerCoarser = considerCoarser || (cover[i].level() > coarsestIndexedLevel);
- }
-
- if (considerCoarser) {
- // Look at the cells that cover us. We want to look at every cell that
- // contains the covering we would index on if we were to insert the
- // query geometry. We generate the would-index-with-this-covering and
- // find all the cells strictly containing the cells in that set, until we hit the
- // coarsest indexed cell. We use $in, not a prefix match. Why not prefix? Because
- // we've already looked at everything finer or as fine as our initial covering.
- //
- // Say we have a fine point with cell id 212121, we go up one, get 21212, we don't
- // want to look at cells 21212[not-1] because we know they're not going to intersect
- // with 212121, but entries inserted with cell value 21212 (no trailing digits) may.
- // And we've already looked at points with the cell id 211111 from the regex search
- // created above, so we only want things where the value of the last digit is not
- // stored (and therefore could be 1).
- unordered_set<S2CellId> parents;
- for (size_t i = 0; i < cover.size(); ++i) {
- for (S2CellId id = cover[i].parent(); id.level() >= coarsestIndexedLevel;
- id = id.parent()) {
- parents.insert(id);
- }
- }
-
- for (unordered_set<S2CellId>::const_iterator it = parents.begin(); it != parents.end(); ++it) {
- inArrayBuilder.append(myitoa(arrayPos++), it->toString());
- }
- }
-
- inArrayBuilder.done();
- inBuilder.done();
- return queryBuilder.obj();
- }
-} // namespace mongo
diff --git a/src/mongo/db/index/expression_keys_private.cpp b/src/mongo/db/index/expression_keys_private.cpp
index 0cbdcf1f6e0..b5e14d9ce30 100644
--- a/src/mongo/db/index/expression_keys_private.cpp
+++ b/src/mongo/db/index/expression_keys_private.cpp
@@ -34,7 +34,7 @@
#include "mongo/db/geo/geoconstants.h"
#include "mongo/db/geo/geometry_container.h"
#include "mongo/db/geo/geoparser.h"
-#include "mongo/db/geo/s2common.h"
+#include "mongo/db/index/s2_common.h"
#include "mongo/db/geo/s2.h"
#include "mongo/db/index_names.h"
#include "mongo/db/index/2d_common.h"
diff --git a/src/mongo/db/index/expression_params.h b/src/mongo/db/index/expression_params.h
index f8fdf818755..e7b190f5eb9 100644
--- a/src/mongo/db/index/expression_params.h
+++ b/src/mongo/db/index/expression_params.h
@@ -26,7 +26,7 @@
* it in the license file.
*/
-#include "mongo/db/geo/s2common.h"
+#include "mongo/db/index/s2_common.h"
#include "mongo/db/index_names.h"
#include "mongo/db/index/2d_common.h"
#include "mongo/db/jsobj.h"
diff --git a/src/mongo/db/index/external_key_generator.cpp b/src/mongo/db/index/external_key_generator.cpp
index 1a4a17eba77..ffa6675f20f 100644
--- a/src/mongo/db/index/external_key_generator.cpp
+++ b/src/mongo/db/index/external_key_generator.cpp
@@ -29,7 +29,7 @@
#include "mongo/db/index/external_key_generator.h"
#include "mongo/db/fts/fts_index_format.h"
-#include "mongo/db/geo/s2common.h"
+#include "mongo/db/index/s2_common.h"
#include "mongo/db/index_names.h"
#include "mongo/db/index/2d_common.h"
#include "mongo/db/index/btree_key_generator.h"
diff --git a/src/mongo/db/index/s2_access_method.cpp b/src/mongo/db/index/s2_access_method.cpp
index fe8d41d5f0f..008dcbc332b 100644
--- a/src/mongo/db/index/s2_access_method.cpp
+++ b/src/mongo/db/index/s2_access_method.cpp
@@ -33,7 +33,7 @@
#include "mongo/base/status.h"
#include "mongo/db/geo/geoparser.h"
#include "mongo/db/geo/geoconstants.h"
-#include "mongo/db/geo/s2common.h"
+#include "mongo/db/index/s2_common.h"
#include "mongo/db/index_names.h"
#include "mongo/db/index/expression_keys_private.h"
#include "mongo/db/index/expression_params.h"
diff --git a/src/mongo/db/index/s2_access_method.h b/src/mongo/db/index/s2_access_method.h
index 773c4e04d05..47b74823342 100644
--- a/src/mongo/db/index/s2_access_method.h
+++ b/src/mongo/db/index/s2_access_method.h
@@ -29,7 +29,7 @@
#pragma once
#include "mongo/base/status.h"
-#include "mongo/db/geo/s2common.h"
+#include "mongo/db/index/s2_common.h"
#include "mongo/db/index/btree_based_access_method.h"
#include "mongo/db/index/index_descriptor.h"
#include "mongo/db/jsobj.h"
diff --git a/src/mongo/db/geo/s2common.h b/src/mongo/db/index/s2_common.h
index 6f8da991764..3df3c833694 100644
--- a/src/mongo/db/geo/s2common.h
+++ b/src/mongo/db/index/s2_common.h
@@ -26,10 +26,9 @@
* it in the license file.
*/
-#include "mongo/db/geo/geoparser.h"
+#include "mongo/db/jsobj.h"
#include "mongo/db/geo/geoconstants.h"
#include "mongo/db/geo/s2.h"
-#include "third_party/s2/s2regioncoverer.h"
#include "third_party/s2/s2cell.h"
#include "third_party/s2/s2polyline.h"
#include "third_party/s2/s2polygon.h"
@@ -87,13 +86,4 @@ namespace mongo {
}
};
- class S2SearchUtil {
- public:
- // Given a coverer, region, and field name, generate a BSONObj that we can pass to a
- // FieldRangeSet so that we only examine the keys that the provided region may intersect.
- static BSONObj coverAsBSON(const std::vector<S2CellId> &cover, const std::string& field,
- const int coarsestIndexedLevel);
- static void setCoverLimitsBasedOnArea(double area, S2RegionCoverer *coverer, int coarsestIndexedLevel);
- };
-
} // namespace mongo
diff --git a/src/mongo/db/matcher/expression_geo.cpp b/src/mongo/db/matcher/expression_geo.cpp
index ee3142d1120..1fbc834bdb6 100644
--- a/src/mongo/db/matcher/expression_geo.cpp
+++ b/src/mongo/db/matcher/expression_geo.cpp
@@ -30,14 +30,308 @@
#include "mongo/pch.h"
#include "mongo/db/matcher/expression_geo.h"
+#include "mongo/db/geo/geoparser.h"
+#include "mongo/util/mongoutils/str.h"
+#include "mongo/util/log.h"
namespace mongo {
+
+ using mongoutils::str::equals;
+
+ //
+ // GeoExpression
+ //
+
+ // Put simple constructors here for scoped_ptr.
+ GeoExpression::GeoExpression() : field(""), predicate(INVALID) {}
+ GeoExpression::GeoExpression(const std::string& f) : field(f), predicate(INVALID) {}
+
+ bool GeoExpression::parseLegacyQuery(const BSONObj &obj) {
+ // The only legacy syntax is {$within: {.....}}
+ BSONObjIterator outerIt(obj);
+ if (!outerIt.more()) { return false; }
+ BSONElement withinElt = outerIt.next();
+ if (outerIt.more()) { return false; }
+ if (!withinElt.isABSONObj()) { return false; }
+ if (!equals(withinElt.fieldName(), "$within") && !equals(withinElt.fieldName(), "$geoWithin")) {
+ return false;
+ }
+ BSONObj withinObj = withinElt.embeddedObject();
+
+ bool hasGeometry = false;
+
+ BSONObjIterator withinIt(withinObj);
+ while (withinIt.more()) {
+ BSONElement elt = withinIt.next();
+ if (equals(elt.fieldName(), "$uniqueDocs")) {
+ warning() << "deprecated $uniqueDocs option: " << obj.toString() << endl;
+ // return false;
+ }
+ else if (elt.isABSONObj()) {
+ hasGeometry = geoContainer->parseFrom(elt.wrap());
+ }
+ else {
+ warning() << "bad geo query: " << obj.toString() << endl;
+ return false;
+ }
+ }
+
+ predicate = GeoExpression::WITHIN;
+
+ return hasGeometry;
+ }
+
+ bool GeoExpression::parseNewQuery(const BSONObj &obj) {
+ // pointA = { "type" : "Point", "coordinates": [ 40, 5 ] }
+ // t.find({ "geo" : { "$intersect" : { "$geometry" : pointA} } })
+ // t.find({ "geo" : { "$within" : { "$geometry" : polygon } } })
+ // where field.name is "geo"
+ BSONElement e = obj.firstElement();
+ if (!e.isABSONObj()) { return false; }
+
+ BSONObj::MatchType matchType = static_cast<BSONObj::MatchType>(e.getGtLtOp());
+ if (BSONObj::opGEO_INTERSECTS == matchType) {
+ predicate = GeoExpression::INTERSECT;
+ } else if (BSONObj::opWITHIN == matchType) {
+ predicate = GeoExpression::WITHIN;
+ } else {
+ return false;
+ }
+
+ bool hasGeometry = false;
+ BSONObjIterator argIt(e.embeddedObject());
+ while (argIt.more()) {
+ BSONElement e = argIt.next();
+ if (mongoutils::str::equals(e.fieldName(), "$geometry")) {
+ if (e.isABSONObj()) {
+ BSONObj embeddedObj = e.embeddedObject();
+ if (geoContainer->parseFrom(embeddedObj)) {
+ hasGeometry = true;
+ }
+ }
+ }
+ }
+
+ // Don't want to give the error below if we could not pull any geometry out.
+ if (!hasGeometry) { return false; }
+
+ if (GeoExpression::WITHIN == predicate) {
+ // Why do we only deal with $within {polygon}?
+ // 1. Finding things within a point is silly and only valid
+ // for points and degenerate lines/polys.
+ //
+ // 2. Finding points within a line is easy but that's called intersect.
+ // Finding lines within a line is kind of tricky given what S2 gives us.
+ // Doing line-within-line is a valid yet unsupported feature,
+ // though I wonder if we want to preserve orientation for lines or
+ // allow (a,b),(c,d) to be within (c,d),(a,b). Anyway, punt on
+ // this for now.
+ uassert(16672, "$within not supported with provided geometry: " + obj.toString(),
+ geoContainer->supportsContains());
+ }
+
+ return hasGeometry;
+ }
+
+ bool GeoExpression::parseFrom(const BSONObj &obj) {
+ geoContainer.reset(new GeometryContainer());
+ if (!(parseLegacyQuery(obj) || parseNewQuery(obj)))
+ return false;
+
+ // Big polygon with strict winding order is represented as an S2Loop in SPHERE CRS.
+ // So converting the query to SPHERE CRS makes things easier than projecting all the data
+ // into STRICT_SPHERE CRS.
+ if (STRICT_SPHERE == geoContainer->getNativeCRS()) {
+ if (!geoContainer->supportsProject(SPHERE))
+ return false;
+ geoContainer->projectInto(SPHERE);
+ }
+
+ // $geoIntersect queries are hardcoded to *always* be in SPHERE CRS
+ // TODO: This is probably bad semantics, should not do this
+ if (GeoExpression::INTERSECT == predicate) {
+ if (!geoContainer->supportsProject(SPHERE))
+ return false;
+ geoContainer->projectInto(SPHERE);
+ }
+
+ return true;
+ }
+
+ //
+ // GeoNearExpression
+ //
+
+ GeoNearExpression::GeoNearExpression()
+ : minDistance(0),
+ maxDistance(std::numeric_limits<double>::max()),
+ isNearSphere(false),
+ unitsAreRadians(false),
+ isWrappingQuery(false) { }
+
+ GeoNearExpression::GeoNearExpression(const std::string& f)
+ : field(f),
+ minDistance(0),
+ maxDistance(std::numeric_limits<double>::max()),
+ isNearSphere(false),
+ unitsAreRadians(false),
+ isWrappingQuery(false) { }
+
+ bool GeoNearExpression::parseLegacyQuery(const BSONObj &obj) {
+
+ bool hasGeometry = false;
+
+ // First, try legacy near, e.g.:
+ // t.find({ loc : { $nearSphere: [0,0], $minDistance: 1, $maxDistance: 3 }})
+ // t.find({ loc : { $nearSphere: [0,0] }})
+ // t.find({ loc : { $near : [0, 0, 1] } });
+ // t.find({ loc : { $near: { someGeoJSONPoint}})
+ // t.find({ loc : { $geoNear: { someGeoJSONPoint}})
+ BSONObjIterator it(obj);
+ while (it.more()) {
+ BSONElement e = it.next();
+ if (equals(e.fieldName(), "$near") || equals(e.fieldName(), "$geoNear")
+ || equals(e.fieldName(), "$nearSphere")) {
+ if (!e.isABSONObj()) { return false; }
+ BSONObj embeddedObj = e.embeddedObject();
+
+ if ((GeoParser::isPoint(embeddedObj) && GeoParser::parsePoint(embeddedObj, centroid.get()))
+ || GeoParser::parsePointWithMaxDistance(embeddedObj, centroid.get(), &maxDistance)) {
+ uassert(18522, "max distance must be non-negative", maxDistance >= 0.0);
+ hasGeometry = true;
+ isNearSphere = equals(e.fieldName(), "$nearSphere");
+ }
+ } else if (equals(e.fieldName(), "$minDistance")) {
+ uassert(16893, "$minDistance must be a number", e.isNumber());
+ minDistance = e.Number();
+ uassert(16894, "$minDistance must be non-negative", minDistance >= 0.0);
+ } else if (equals(e.fieldName(), "$maxDistance")) {
+ uassert(16895, "$maxDistance must be a number", e.isNumber());
+ maxDistance = e.Number();
+ uassert(16896, "$maxDistance must be non-negative", maxDistance >= 0.0);
+ } else if (equals(e.fieldName(), "$uniqueDocs")) {
+ warning() << "ignoring deprecated option $uniqueDocs";
+ }
+ }
+
+ return hasGeometry;
+ }
+
+ Status GeoNearExpression::parseNewQuery(const BSONObj &obj) {
+ bool hasGeometry = false;
+
+ BSONObjIterator objIt(obj);
+ if (!objIt.more()) {
+ return Status(ErrorCodes::BadValue, "empty geo near query object");
+ }
+ BSONElement e = objIt.next();
+ // Just one arg. to $geoNear.
+ if (objIt.more()) {
+ return Status(ErrorCodes::BadValue, mongoutils::str::stream() <<
+ "geo near accepts just one argument when querying for a GeoJSON " <<
+ "point. Extra field found: " << objIt.next());
+ }
+
+ // Parse "new" near:
+ // t.find({"geo" : {"$near" : {"$geometry": pointA, $minDistance: 1, $maxDistance: 3}}})
+ // t.find({"geo" : {"$geoNear" : {"$geometry": pointA, $minDistance: 1, $maxDistance: 3}}})
+ if (!e.isABSONObj()) {
+ return Status(ErrorCodes::BadValue, "geo near query argument is not an object");
+ }
+ BSONObj::MatchType matchType = static_cast<BSONObj::MatchType>(e.getGtLtOp());
+ if (BSONObj::opNEAR != matchType) {
+ return Status(ErrorCodes::BadValue, mongoutils::str::stream() <<
+ "invalid geo near query operator: " << e.fieldName());
+ }
+
+ // Iterate over the argument.
+ BSONObjIterator it(e.embeddedObject());
+ while (it.more()) {
+ BSONElement e = it.next();
+ if (equals(e.fieldName(), "$geometry")) {
+ if (e.isABSONObj()) {
+ BSONObj embeddedObj = e.embeddedObject();
+ uassert(16885, "$near requires a point, given " + embeddedObj.toString(),
+ GeoParser::isPoint(embeddedObj));
+ if (!GeoParser::parsePoint(embeddedObj, centroid.get())) {
+ return Status(ErrorCodes::BadValue, mongoutils::str::stream() <<
+ "invalid point in geo near query $geometry argument: " <<
+ embeddedObj);
+ }
+ uassert(16681, "$near requires geojson point, given " + embeddedObj.toString(),
+ (SPHERE == centroid->crs));
+ hasGeometry = true;
+ }
+ } else if (equals(e.fieldName(), "$minDistance")) {
+ uassert(16897, "$minDistance must be a number", e.isNumber());
+ minDistance = e.Number();
+ uassert(16898, "$minDistance must be non-negative", minDistance >= 0.0);
+ } else if (equals(e.fieldName(), "$maxDistance")) {
+ uassert(16899, "$maxDistance must be a number", e.isNumber());
+ maxDistance = e.Number();
+ uassert(16900, "$maxDistance must be non-negative", maxDistance >= 0.0);
+ }
+ }
+
+ if (!hasGeometry) {
+ return Status(ErrorCodes::BadValue, "$geometry is required for geo near query");
+ }
+
+ return Status::OK();
+ }
+
+
+ Status GeoNearExpression::parseFrom(const BSONObj &obj) {
+
+ Status status = Status::OK();
+ centroid.reset(new PointWithCRS());
+
+ if (!parseLegacyQuery(obj)) {
+ // Clear out any half-baked data.
+ minDistance = 0;
+ isNearSphere = false;
+ maxDistance = std::numeric_limits<double>::max();
+ // ...and try parsing new format.
+ status = parseNewQuery(obj);
+ }
+
+ if (!status.isOK())
+ return status;
+
+ // Fixup the near query for anonoyances caused by $nearSphere
+ if (isNearSphere) {
+
+ // The user-provided point can be flat for a spherical query - needs to be projectable
+ uassert(17444,
+ "Legacy point is out of bounds for spherical query",
+ ShapeProjection::supportsProject(*centroid, SPHERE));
+
+ unitsAreRadians = SPHERE != centroid->crs;
+ // GeoJSON points imply wrapping queries
+ isWrappingQuery = SPHERE == centroid->crs;
+
+ // Project the point to a spherical CRS now that we've got the settings we need
+ // We need to manually project here since we aren't using GeometryContainer
+ ShapeProjection::projectInto(centroid.get(), SPHERE);
+ }
+ else {
+ unitsAreRadians = false;
+ isWrappingQuery = SPHERE == centroid->crs;
+ }
+
+ return status;
+ }
+
+ //
+ // GeoMatchExpression and GeoNearMatchExpression
+ //
+
//
// Geo queries we don't need an index to answer: geoWithin and geoIntersects
//
- Status GeoMatchExpression::init( const StringData& path, const GeoQuery* query,
+ Status GeoMatchExpression::init( const StringData& path, const GeoExpression* query,
const BSONObj& rawObj ) {
_query.reset(query);
_rawObj = rawObj;
@@ -62,11 +356,11 @@ namespace mongo {
geometry.projectInto(_query->getGeometry().getNativeCRS());
- if (GeoQuery::WITHIN == _query->getPred()) {
+ if (GeoExpression::WITHIN == _query->getPred()) {
return _query->getGeometry().contains(geometry);
}
else {
- verify(GeoQuery::INTERSECT == _query->getPred());
+ verify(GeoExpression::INTERSECT == _query->getPred());
return _query->getGeometry().intersects(geometry);
}
}
@@ -114,7 +408,7 @@ namespace mongo {
// Parse-only geo expressions: geoNear (formerly known as near).
//
- Status GeoNearMatchExpression::init( const StringData& path, const NearQuery* query,
+ Status GeoNearMatchExpression::init( const StringData& path, const GeoNearExpression* query,
const BSONObj& rawObj ) {
_query.reset(query);
_rawObj = rawObj;
diff --git a/src/mongo/db/matcher/expression_geo.h b/src/mongo/db/matcher/expression_geo.h
index 0be28de408d..ec8711c2119 100644
--- a/src/mongo/db/matcher/expression_geo.h
+++ b/src/mongo/db/matcher/expression_geo.h
@@ -31,21 +31,56 @@
#pragma once
-#include "mongo/db/geo/geo_query.h"
+#include "mongo/db/geo/geometry_container.h"
#include "mongo/db/matcher/expression.h"
#include "mongo/db/matcher/expression_leaf.h"
namespace mongo {
+ struct PointWithCRS;
+ class GeometryContainer;
+
+ // This represents either a $within or a $geoIntersects.
+ class GeoExpression {
+ MONGO_DISALLOW_COPYING(GeoExpression);
+
+ public:
+ GeoExpression();
+ GeoExpression(const std::string& f);
+
+ enum Predicate {
+ WITHIN,
+ INTERSECT,
+ INVALID
+ };
+
+ // parseFrom() must be called before getGeometry() to ensure initialization of geoContainer
+ bool parseFrom(const BSONObj &obj);
+
+ std::string getField() const { return field; }
+ Predicate getPred() const { return predicate; }
+ const GeometryContainer& getGeometry() const { return *geoContainer; }
+
+ private:
+ // Try to parse the provided object into the right place.
+ bool parseLegacyQuery(const BSONObj &obj);
+ bool parseNewQuery(const BSONObj &obj);
+
+ // Name of the field in the query.
+ std::string field;
+ boost::scoped_ptr<GeometryContainer> geoContainer;
+ Predicate predicate;
+ };
+
class GeoMatchExpression : public LeafMatchExpression {
public:
GeoMatchExpression() : LeafMatchExpression( GEO ){}
virtual ~GeoMatchExpression(){}
/**
- * Takes ownership of the passed-in GeoQuery.
+ * Takes ownership of the passed-in GeoExpression.
*/
- Status init( const StringData& path, const GeoQuery* query, const BSONObj& rawObj );
+ Status init( const StringData& path, const GeoExpression* query, const BSONObj& rawObj );
virtual bool matchesSingleElement( const BSONElement& e ) const;
@@ -57,13 +92,56 @@ namespace mongo {
virtual LeafMatchExpression* shallowClone() const;
- const GeoQuery& getGeoQuery() const { return *_query; }
+ const GeoExpression& getGeoExpression() const { return *_query; }
const BSONObj getRawObj() const { return _rawObj; }
private:
BSONObj _rawObj;
// Share ownership of our query with all of our clones
- shared_ptr<const GeoQuery> _query;
+ shared_ptr<const GeoExpression> _query;
+ };
+
+
+ // TODO: Make a struct, turn parse stuff into something like
+ // static Status parseNearQuery(const BSONObj& obj, NearQuery** out);
+ class GeoNearExpression {
+ MONGO_DISALLOW_COPYING(GeoNearExpression);
+
+ public:
+ GeoNearExpression();
+ GeoNearExpression(const std::string& f);
+
+ Status parseFrom(const BSONObj &obj);
+
+ // The name of the field that contains the geometry.
+ std::string field;
+
+ // The starting point of the near search. Use forward declaration of geometries.
+ boost::scoped_ptr<PointWithCRS> centroid;
+
+ // Min and max distance from centroid that we're willing to search.
+ // Distance is in units of the geometry's CRS, except SPHERE and isNearSphere => radians
+ double minDistance;
+ double maxDistance;
+
+ // Is this a $nearSphere query
+ bool isNearSphere;
+ // $nearSphere with a legacy point implies units are radians
+ bool unitsAreRadians;
+ // $near with a non-legacy point implies a wrapping query, otherwise the query doesn't wrap
+ bool isWrappingQuery;
+
+ std::string toString() const {
+ std::stringstream ss;
+ ss << " field=" << field;
+ ss << " maxdist=" << maxDistance;
+ ss << " isNearSphere=" << isNearSphere;
+ return ss.str();
+ }
+
+ private:
+ bool parseLegacyQuery(const BSONObj &obj);
+ Status parseNewQuery(const BSONObj &obj);
};
class GeoNearMatchExpression : public LeafMatchExpression {
@@ -71,7 +149,7 @@ namespace mongo {
GeoNearMatchExpression() : LeafMatchExpression( GEO_NEAR ){}
virtual ~GeoNearMatchExpression(){}
- Status init( const StringData& path, const NearQuery* query, const BSONObj& rawObj );
+ Status init( const StringData& path, const GeoNearExpression* query, const BSONObj& rawObj );
// This shouldn't be called and as such will crash. GeoNear always requires an index.
virtual bool matchesSingleElement( const BSONElement& e ) const;
@@ -84,12 +162,12 @@ namespace mongo {
virtual LeafMatchExpression* shallowClone() const;
- const NearQuery& getData() const { return *_query; }
+ const GeoNearExpression& getData() const { return *_query; }
const BSONObj getRawObj() const { return _rawObj; }
private:
BSONObj _rawObj;
// Share ownership of our query with all of our clones
- shared_ptr<const NearQuery> _query;
+ shared_ptr<const GeoNearExpression> _query;
};
} // namespace mongo
diff --git a/src/mongo/db/matcher/expression_geo_test.cpp b/src/mongo/db/matcher/expression_geo_test.cpp
index 7f2b5fb3742..29c6be49a63 100644
--- a/src/mongo/db/matcher/expression_geo_test.cpp
+++ b/src/mongo/db/matcher/expression_geo_test.cpp
@@ -43,7 +43,7 @@ namespace mongo {
TEST( ExpressionGeoTest, Geo1 ) {
BSONObj query = fromjson("{loc:{$within:{$box:[{x: 4, y:4},[6,6]]}}}");
- auto_ptr<GeoQuery> gq(new GeoQuery);
+ auto_ptr<GeoExpression> gq(new GeoExpression);
ASSERT( gq->parseFrom( query["loc"].Obj() ) );
GeoMatchExpression ge;
@@ -60,14 +60,14 @@ namespace mongo {
TEST(ExpressionGeoTest, GeoNear1) {
BSONObj query = fromjson("{loc:{$near:{$maxDistance:100, "
"$geometry:{type:\"Point\", coordinates:[0,0]}}}}");
- auto_ptr<NearQuery> nq(new NearQuery);
+ auto_ptr<GeoNearExpression> nq(new GeoNearExpression);
ASSERT_OK(nq->parseFrom(query["loc"].Obj()));
GeoNearMatchExpression gne;
ASSERT(gne.init("a", nq.release(), query).isOK());
// We can't match the data but we can make sure it was parsed OK.
- ASSERT_EQUALS(gne.getData().centroid.crs, SPHERE);
+ ASSERT_EQUALS(gne.getData().centroid->crs, SPHERE);
ASSERT_EQUALS(gne.getData().minDistance, 0);
ASSERT_EQUALS(gne.getData().maxDistance, 100);
}
diff --git a/src/mongo/db/matcher/expression_parser_geo.cpp b/src/mongo/db/matcher/expression_parser_geo.cpp
index 146f104ce9e..d5f82d4891b 100644
--- a/src/mongo/db/matcher/expression_parser_geo.cpp
+++ b/src/mongo/db/matcher/expression_parser_geo.cpp
@@ -42,7 +42,7 @@ namespace mongo {
int type,
const BSONObj& section ) {
if (BSONObj::opWITHIN == type || BSONObj::opGEO_INTERSECTS == type) {
- auto_ptr<GeoQuery> gq(new GeoQuery(name));
+ auto_ptr<GeoExpression> gq(new GeoExpression(name));
if ( !gq->parseFrom( section ) )
return StatusWithMatchExpression( ErrorCodes::BadValue,
string("bad geo query: ") + section.toString() );
@@ -61,7 +61,7 @@ namespace mongo {
}
else {
verify(BSONObj::opNEAR == type);
- auto_ptr<NearQuery> nq(new NearQuery(name));
+ auto_ptr<GeoNearExpression> nq(new GeoNearExpression(name));
Status s = nq->parseFrom( section );
if ( !s.isOK() ) {
return StatusWithMatchExpression( s );
diff --git a/src/mongo/db/query/canonical_query.cpp b/src/mongo/db/query/canonical_query.cpp
index a8b501821ba..e9ddee5ae53 100644
--- a/src/mongo/db/query/canonical_query.cpp
+++ b/src/mongo/db/query/canonical_query.cpp
@@ -153,13 +153,13 @@ namespace {
* - CRS (flat or spherical)
*/
void encodeGeoMatchExpression(const GeoMatchExpression* tree, mongoutils::str::stream* os) {
- const GeoQuery& geoQuery = tree->getGeoQuery();
+ const GeoExpression& geoQuery = tree->getGeoExpression();
// Type of geo query.
switch (geoQuery.getPred()) {
- case GeoQuery::WITHIN: *os << "wi"; break;
- case GeoQuery::INTERSECT: *os << "in"; break;
- case GeoQuery::INVALID: *os << "id"; break;
+ case GeoExpression::WITHIN: *os << "wi"; break;
+ case GeoExpression::INTERSECT: *os << "in"; break;
+ case GeoExpression::INVALID: *os << "id"; break;
}
// Geometry type.
@@ -191,18 +191,18 @@ namespace {
*/
void encodeGeoNearMatchExpression(const GeoNearMatchExpression* tree,
mongoutils::str::stream* os) {
- const NearQuery& nearQuery = tree->getData();
+ const GeoNearExpression& nearQuery = tree->getData();
// isNearSphere
*os << (nearQuery.isNearSphere ? "ns" : "nr");
// CRS (flat or spherical or strict-winding spherical)
- switch (nearQuery.centroid.crs) {
+ switch (nearQuery.centroid->crs) {
case FLAT: *os << "fl"; break;
case SPHERE: *os << "sp"; break;
case STRICT_SPHERE: *os << "ss"; break;
case UNSET:
- error() << "unknown CRS type " << (int)nearQuery.centroid.crs
+ error() << "unknown CRS type " << (int)nearQuery.centroid->crs
<< " in point geometry for near query";
invariant(false);
break;
diff --git a/src/mongo/db/query/index_bounds_builder.cpp b/src/mongo/db/query/index_bounds_builder.cpp
index 87a2a8ea135..57007d52ee6 100644
--- a/src/mongo/db/query/index_bounds_builder.cpp
+++ b/src/mongo/db/query/index_bounds_builder.cpp
@@ -30,7 +30,6 @@
#include <limits>
#include "mongo/db/geo/geoconstants.h"
-#include "mongo/db/geo/s2common.h"
#include "mongo/db/matcher/expression_geo.h"
#include "mongo/db/query/expression_index.h"
#include "mongo/db/query/expression_index_knobs.h"
@@ -570,14 +569,14 @@ namespace mongo {
const GeoMatchExpression* gme = static_cast<const GeoMatchExpression*>(expr);
if (mongoutils::str::equals("2dsphere", elt.valuestrsafe())) {
- verify(gme->getGeoQuery().getGeometry().hasS2Region());
- const S2Region& region = gme->getGeoQuery().getGeometry().getS2Region();
+ verify(gme->getGeoExpression().getGeometry().hasS2Region());
+ const S2Region& region = gme->getGeoExpression().getGeometry().getS2Region();
ExpressionMapping::cover2dsphere(region, index.infoObj, oilOut);
*tightnessOut = IndexBoundsBuilder::INEXACT_FETCH;
}
else if (mongoutils::str::equals("2d", elt.valuestrsafe())) {
- verify(gme->getGeoQuery().getGeometry().hasR2Region());
- const R2Region& region = gme->getGeoQuery().getGeometry().getR2Region();
+ verify(gme->getGeoExpression().getGeometry().hasR2Region());
+ const R2Region& region = gme->getGeoExpression().getGeometry().getR2Region();
ExpressionMapping::cover2d(region,
index.infoObj,
diff --git a/src/mongo/db/query/planner_access.cpp b/src/mongo/db/query/planner_access.cpp
index cb43096e9ae..b5f38a61626 100644
--- a/src/mongo/db/query/planner_access.cpp
+++ b/src/mongo/db/query/planner_access.cpp
@@ -118,7 +118,7 @@ namespace mongo {
if (indexIs2D) {
GeoNear2DNode* ret = new GeoNear2DNode();
ret->indexKeyPattern = index.keyPattern;
- ret->nq = nearExpr->getData();
+ ret->nq = &nearExpr->getData();
ret->baseBounds.fields.resize(index.keyPattern.nFields());
if (NULL != query.getProj()) {
ret->addPointMeta = query.getProj()->wantGeoNearPoint();
@@ -130,7 +130,7 @@ namespace mongo {
else {
GeoNear2DSphereNode* ret = new GeoNear2DSphereNode();
ret->indexKeyPattern = index.keyPattern;
- ret->nq = nearExpr->getData();
+ ret->nq = &nearExpr->getData();
ret->baseBounds.fields.resize(index.keyPattern.nFields());
if (NULL != query.getProj()) {
ret->addPointMeta = query.getProj()->wantGeoNearPoint();
diff --git a/src/mongo/db/query/planner_ixselect.cpp b/src/mongo/db/query/planner_ixselect.cpp
index f2067f651dc..69bc2189468 100644
--- a/src/mongo/db/query/planner_ixselect.cpp
+++ b/src/mongo/db/query/planner_ixselect.cpp
@@ -239,14 +239,14 @@ namespace mongo {
if (exprtype == MatchExpression::GEO) {
// within or intersect.
GeoMatchExpression* gme = static_cast<GeoMatchExpression*>(node);
- const GeoQuery& gq = gme->getGeoQuery();
+ const GeoExpression& gq = gme->getGeoExpression();
const GeometryContainer& gc = gq.getGeometry();
return gc.hasS2Region();
}
else if (exprtype == MatchExpression::GEO_NEAR) {
GeoNearMatchExpression* gnme = static_cast<GeoNearMatchExpression*>(node);
// Make sure the near query is compatible with 2dsphere.
- return gnme->getData().centroid.crs == SPHERE;
+ return gnme->getData().centroid->crs == SPHERE;
}
return false;
}
@@ -254,13 +254,13 @@ namespace mongo {
if (exprtype == MatchExpression::GEO_NEAR) {
GeoNearMatchExpression* gnme = static_cast<GeoNearMatchExpression*>(node);
// Make sure the near query is compatible with 2d index
- return gnme->getData().centroid.crs == FLAT || !gnme->getData().isWrappingQuery;
+ return gnme->getData().centroid->crs == FLAT || !gnme->getData().isWrappingQuery;
}
else if (exprtype == MatchExpression::GEO) {
// 2d only supports within.
GeoMatchExpression* gme = static_cast<GeoMatchExpression*>(node);
- const GeoQuery& gq = gme->getGeoQuery();
- if (GeoQuery::WITHIN != gq.getPred()) {
+ const GeoExpression& gq = gme->getGeoExpression();
+ if (GeoExpression::WITHIN != gq.getPred()) {
return false;
}
diff --git a/src/mongo/db/query/query_solution.cpp b/src/mongo/db/query/query_solution.cpp
index f4166b23b4e..deee6cca95a 100644
--- a/src/mongo/db/query/query_solution.cpp
+++ b/src/mongo/db/query/query_solution.cpp
@@ -28,6 +28,7 @@
#include "mongo/db/query/query_solution.h"
#include "mongo/db/query/lite_parsed_query.h"
+#include "mongo/db/matcher/expression_geo.h"
namespace mongo {
@@ -695,7 +696,7 @@ namespace mongo {
addIndent(ss, indent + 1);
*ss << "keyPattern = " << indexKeyPattern.toString() << '\n';
addCommon(ss, indent);
- *ss << "nearQuery = " << nq.toString() << '\n';
+ *ss << "nearQuery = " << nq->toString() << '\n';
if (NULL != filter) {
addIndent(ss, indent + 1);
*ss << " filter = " << filter->toString();
@@ -728,7 +729,7 @@ namespace mongo {
addCommon(ss, indent);
*ss << "baseBounds = " << baseBounds.toString() << '\n';
addIndent(ss, indent + 1);
- *ss << "nearQuery = " << nq.toString() << '\n';
+ *ss << "nearQuery = " << nq->toString() << '\n';
if (NULL != filter) {
addIndent(ss, indent + 1);
*ss << " filter = " << filter->toString();
diff --git a/src/mongo/db/query/query_solution.h b/src/mongo/db/query/query_solution.h
index bb197374ea7..0c5899ce403 100644
--- a/src/mongo/db/query/query_solution.h
+++ b/src/mongo/db/query/query_solution.h
@@ -30,7 +30,6 @@
#include "mongo/db/jsobj.h"
#include "mongo/db/matcher/expression.h"
-#include "mongo/db/geo/geo_query.h"
#include "mongo/db/fts/fts_query.h"
#include "mongo/db/query/index_bounds.h"
#include "mongo/db/query/plan_cache.h"
@@ -40,6 +39,8 @@ namespace mongo {
using mongo::fts::FTSQuery;
+ class GeoNearExpression;
+
/**
* This is an abstract representation of a query plan. It can be transcribed into a tree of
* PlanStages, which can then be handed to a PlanRunner for execution.
@@ -579,7 +580,8 @@ namespace mongo {
BSONObjSet _sorts;
- NearQuery nq;
+ // Not owned here
+ const GeoNearExpression* nq;
IndexBounds baseBounds;
BSONObj indexKeyPattern;
@@ -604,7 +606,8 @@ namespace mongo {
BSONObjSet _sorts;
- NearQuery nq;
+ // Not owned here
+ const GeoNearExpression* nq;
IndexBounds baseBounds;
BSONObj indexKeyPattern;