diff options
author | Siyuan Zhou <siyuan.zhou@mongodb.com> | 2014-08-12 14:29:11 -0400 |
---|---|---|
committer | Siyuan Zhou <siyuan.zhou@mongodb.com> | 2014-08-21 16:21:49 -0400 |
commit | 528ddd163be6d93f839d57cd068bd17ab55440cd (patch) | |
tree | b8e1dc6e5a885ccaeae1584c2384a3f118392b1f /src/mongo | |
parent | 4cdeada1cc17bdf3fec7b39b2ec66faf8b84054d (diff) | |
download | mongo-528ddd163be6d93f839d57cd068bd17ab55440cd.tar.gz |
SERVER-14508 Break header dependencies of geo stuffs in query framework.
Diffstat (limited to 'src/mongo')
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, ¢roid)) - || GeoParser::parsePointWithMaxDistance(embeddedObj, ¢roid, &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, ¢roid)) { - 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(¢roid, 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; |