summaryrefslogtreecommitdiff
path: root/src/mongo/db/commands/geo_near_cmd.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/db/commands/geo_near_cmd.cpp')
-rw-r--r--src/mongo/db/commands/geo_near_cmd.cpp449
1 files changed, 227 insertions, 222 deletions
diff --git a/src/mongo/db/commands/geo_near_cmd.cpp b/src/mongo/db/commands/geo_near_cmd.cpp
index 6f44749925d..5be50f433f6 100644
--- a/src/mongo/db/commands/geo_near_cmd.cpp
+++ b/src/mongo/db/commands/geo_near_cmd.cpp
@@ -53,271 +53,276 @@
namespace mongo {
- using std::unique_ptr;
- using std::stringstream;
-
- class Geo2dFindNearCmd : public Command {
- public:
- Geo2dFindNearCmd() : Command("geoNear") {}
-
- virtual bool isWriteCommandForConfigServer() const { return false; }
- bool slaveOk() const { return true; }
- bool slaveOverrideOk() const { return true; }
-
- void help(stringstream& h) const {
- h << "http://dochub.mongodb.org/core/geo#GeospatialIndexing-geoNearCommand";
- }
-
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
- ActionSet actions;
- actions.addAction(ActionType::find);
- out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
+using std::unique_ptr;
+using std::stringstream;
+
+class Geo2dFindNearCmd : public Command {
+public:
+ Geo2dFindNearCmd() : Command("geoNear") {}
+
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+ bool slaveOk() const {
+ return true;
+ }
+ bool slaveOverrideOk() const {
+ return true;
+ }
+
+ void help(stringstream& h) const {
+ h << "http://dochub.mongodb.org/core/geo#GeospatialIndexing-geoNearCommand";
+ }
+
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ ActionSet actions;
+ actions.addAction(ActionType::find);
+ out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
+ }
+
+ bool run(OperationContext* txn,
+ const string& dbname,
+ BSONObj& cmdObj,
+ int,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ if (!cmdObj["start"].eoo()) {
+ errmsg = "using deprecated 'start' argument to geoNear";
+ return false;
}
- bool run(OperationContext* txn,
- const string& dbname,
- BSONObj& cmdObj,
- int,
- string& errmsg,
- BSONObjBuilder& result) {
- if (!cmdObj["start"].eoo()) {
- errmsg = "using deprecated 'start' argument to geoNear";
- return false;
- }
-
- const NamespaceString nss(parseNs(dbname, cmdObj));
- AutoGetCollectionForRead ctx(txn, nss);
+ const NamespaceString nss(parseNs(dbname, cmdObj));
+ AutoGetCollectionForRead ctx(txn, nss);
- Collection* collection = ctx.getCollection();
- if ( !collection ) {
- errmsg = "can't find ns";
- return false;
- }
-
- IndexCatalog* indexCatalog = collection->getIndexCatalog();
-
- // cout << "raw cmd " << cmdObj.toString() << endl;
-
- // We seek to populate this.
- string nearFieldName;
- bool using2DIndex = false;
- if (!getFieldName(txn, collection, indexCatalog, &nearFieldName, &errmsg, &using2DIndex)) {
- return false;
- }
-
- PointWithCRS point;
- uassert(17304, "'near' field must be point",
- GeoParser::parseQueryPoint(cmdObj["near"], &point).isOK());
-
- bool isSpherical = cmdObj["spherical"].trueValue();
- if (!using2DIndex) {
- uassert(17301, "2dsphere index must have spherical: true", isSpherical);
- }
-
- // Build the $near expression for the query.
- BSONObjBuilder nearBob;
- if (isSpherical) {
- nearBob.append("$nearSphere", cmdObj["near"].Obj());
- }
- else {
- nearBob.append("$near", cmdObj["near"].Obj());
- }
+ Collection* collection = ctx.getCollection();
+ if (!collection) {
+ errmsg = "can't find ns";
+ return false;
+ }
- if (!cmdObj["maxDistance"].eoo()) {
- uassert(17299, "maxDistance must be a number",cmdObj["maxDistance"].isNumber());
- nearBob.append("$maxDistance", cmdObj["maxDistance"].number());
- }
+ IndexCatalog* indexCatalog = collection->getIndexCatalog();
- if (!cmdObj["minDistance"].eoo()) {
- uassert(17298, "minDistance doesn't work on 2d index", !using2DIndex);
- uassert(17300, "minDistance must be a number",cmdObj["minDistance"].isNumber());
- nearBob.append("$minDistance", cmdObj["minDistance"].number());
- }
+ // cout << "raw cmd " << cmdObj.toString() << endl;
- if (!cmdObj["uniqueDocs"].eoo()) {
- warning() << nss << ": ignoring deprecated uniqueDocs option in geoNear command";
- }
+ // We seek to populate this.
+ string nearFieldName;
+ bool using2DIndex = false;
+ if (!getFieldName(txn, collection, indexCatalog, &nearFieldName, &errmsg, &using2DIndex)) {
+ return false;
+ }
- // And, build the full query expression.
- BSONObjBuilder queryBob;
- queryBob.append(nearFieldName, nearBob.obj());
- if (!cmdObj["query"].eoo() && cmdObj["query"].isABSONObj()) {
- queryBob.appendElements(cmdObj["query"].Obj());
- }
- BSONObj rewritten = queryBob.obj();
+ PointWithCRS point;
+ uassert(17304,
+ "'near' field must be point",
+ GeoParser::parseQueryPoint(cmdObj["near"], &point).isOK());
- // cout << "rewritten query: " << rewritten.toString() << endl;
+ bool isSpherical = cmdObj["spherical"].trueValue();
+ if (!using2DIndex) {
+ uassert(17301, "2dsphere index must have spherical: true", isSpherical);
+ }
- long long numWanted = 100;
- const char* limitName = !cmdObj["num"].eoo() ? "num" : "limit";
- BSONElement eNumWanted = cmdObj[limitName];
- if (!eNumWanted.eoo()) {
- uassert(17303, "limit must be number", eNumWanted.isNumber());
- numWanted = eNumWanted.safeNumberLong();
- uassert(17302, "limit must be >=0", numWanted >= 0);
- }
+ // Build the $near expression for the query.
+ BSONObjBuilder nearBob;
+ if (isSpherical) {
+ nearBob.append("$nearSphere", cmdObj["near"].Obj());
+ } else {
+ nearBob.append("$near", cmdObj["near"].Obj());
+ }
- bool includeLocs = false;
- if (!cmdObj["includeLocs"].eoo()) {
- includeLocs = cmdObj["includeLocs"].trueValue();
- }
+ if (!cmdObj["maxDistance"].eoo()) {
+ uassert(17299, "maxDistance must be a number", cmdObj["maxDistance"].isNumber());
+ nearBob.append("$maxDistance", cmdObj["maxDistance"].number());
+ }
- double distanceMultiplier = 1.0;
- BSONElement eDistanceMultiplier = cmdObj["distanceMultiplier"];
- if (!eDistanceMultiplier.eoo()) {
- uassert(17296, "distanceMultiplier must be a number", eDistanceMultiplier.isNumber());
- distanceMultiplier = eDistanceMultiplier.number();
- uassert(17297, "distanceMultiplier must be non-negative", distanceMultiplier >= 0);
- }
+ if (!cmdObj["minDistance"].eoo()) {
+ uassert(17298, "minDistance doesn't work on 2d index", !using2DIndex);
+ uassert(17300, "minDistance must be a number", cmdObj["minDistance"].isNumber());
+ nearBob.append("$minDistance", cmdObj["minDistance"].number());
+ }
- BSONObj projObj = BSON("$pt" << BSON("$meta" << LiteParsedQuery::metaGeoNearPoint) <<
- "$dis" << BSON("$meta" << LiteParsedQuery::metaGeoNearDistance));
-
- CanonicalQuery* cq;
- const WhereCallbackReal whereCallback(txn, nss.db());
-
- if (!CanonicalQuery::canonicalize(nss,
- rewritten,
- BSONObj(),
- projObj,
- 0,
- numWanted,
- BSONObj(),
- &cq,
- whereCallback).isOK()) {
- errmsg = "Can't parse filter / create query";
- return false;
- }
+ if (!cmdObj["uniqueDocs"].eoo()) {
+ warning() << nss << ": ignoring deprecated uniqueDocs option in geoNear command";
+ }
- // Prevent chunks from being cleaned up during yields - this allows us to only check the
- // version on initial entry into geoNear.
- RangePreserver preserver(collection);
+ // And, build the full query expression.
+ BSONObjBuilder queryBob;
+ queryBob.append(nearFieldName, nearBob.obj());
+ if (!cmdObj["query"].eoo() && cmdObj["query"].isABSONObj()) {
+ queryBob.appendElements(cmdObj["query"].Obj());
+ }
+ BSONObj rewritten = queryBob.obj();
- PlanExecutor* rawExec;
- if (!getExecutor(txn, collection, cq, PlanExecutor::YIELD_AUTO, &rawExec, 0).isOK()) {
- errmsg = "can't get query executor";
- return false;
- }
+ // cout << "rewritten query: " << rewritten.toString() << endl;
- unique_ptr<PlanExecutor> exec(rawExec);
-
- double totalDistance = 0;
- BSONObjBuilder resultBuilder(result.subarrayStart("results"));
- double farthestDist = 0;
-
- BSONObj currObj;
- long long results = 0;
- while ((results < numWanted) && PlanExecutor::ADVANCED == exec->getNext(&currObj, NULL)) {
-
- // Come up with the correct distance.
- double dist = currObj["$dis"].number() * distanceMultiplier;
- totalDistance += dist;
- if (dist > farthestDist) { farthestDist = dist; }
-
- // Strip out '$dis' and '$pt' from the result obj. The rest gets added as 'obj'
- // in the command result.
- BSONObjIterator resIt(currObj);
- BSONObjBuilder resBob;
- while (resIt.more()) {
- BSONElement elt = resIt.next();
- if (!mongoutils::str::equals("$pt", elt.fieldName())
- && !mongoutils::str::equals("$dis", elt.fieldName())) {
- resBob.append(elt);
- }
- }
- BSONObj resObj = resBob.obj();
+ long long numWanted = 100;
+ const char* limitName = !cmdObj["num"].eoo() ? "num" : "limit";
+ BSONElement eNumWanted = cmdObj[limitName];
+ if (!eNumWanted.eoo()) {
+ uassert(17303, "limit must be number", eNumWanted.isNumber());
+ numWanted = eNumWanted.safeNumberLong();
+ uassert(17302, "limit must be >=0", numWanted >= 0);
+ }
- // Don't make a too-big result object.
- if (resultBuilder.len() + resObj.objsize()> BSONObjMaxUserSize) {
- warning() << "Too many geoNear results for query " << rewritten.toString()
- << ", truncating output.";
- break;
- }
+ bool includeLocs = false;
+ if (!cmdObj["includeLocs"].eoo()) {
+ includeLocs = cmdObj["includeLocs"].trueValue();
+ }
- // Add the next result to the result builder.
- BSONObjBuilder oneResultBuilder(
- resultBuilder.subobjStart(BSONObjBuilder::numStr(results)));
- oneResultBuilder.append("dis", dist);
- if (includeLocs) {
- oneResultBuilder.appendAs(currObj["$pt"], "loc");
- }
- oneResultBuilder.append("obj", resObj);
- oneResultBuilder.done();
- ++results;
- }
+ double distanceMultiplier = 1.0;
+ BSONElement eDistanceMultiplier = cmdObj["distanceMultiplier"];
+ if (!eDistanceMultiplier.eoo()) {
+ uassert(17296, "distanceMultiplier must be a number", eDistanceMultiplier.isNumber());
+ distanceMultiplier = eDistanceMultiplier.number();
+ uassert(17297, "distanceMultiplier must be non-negative", distanceMultiplier >= 0);
+ }
- resultBuilder.done();
+ BSONObj projObj = BSON("$pt" << BSON("$meta" << LiteParsedQuery::metaGeoNearPoint) << "$dis"
+ << BSON("$meta" << LiteParsedQuery::metaGeoNearDistance));
- // Fill out the stats subobj.
- BSONObjBuilder stats(result.subobjStart("stats"));
+ CanonicalQuery* cq;
+ const WhereCallbackReal whereCallback(txn, nss.db());
- // Fill in nscanned from the explain.
- PlanSummaryStats summary;
- Explain::getSummaryStats(exec.get(), &summary);
- stats.appendNumber("nscanned", summary.totalKeysExamined);
- stats.appendNumber("objectsLoaded", summary.totalDocsExamined);
+ if (!CanonicalQuery::canonicalize(
+ nss, rewritten, BSONObj(), projObj, 0, numWanted, BSONObj(), &cq, whereCallback)
+ .isOK()) {
+ errmsg = "Can't parse filter / create query";
+ return false;
+ }
- stats.append("avgDistance", totalDistance / results);
- stats.append("maxDistance", farthestDist);
- stats.append("time", CurOp::get(txn)->elapsedMillis());
- stats.done();
+ // Prevent chunks from being cleaned up during yields - this allows us to only check the
+ // version on initial entry into geoNear.
+ RangePreserver preserver(collection);
- return true;
+ PlanExecutor* rawExec;
+ if (!getExecutor(txn, collection, cq, PlanExecutor::YIELD_AUTO, &rawExec, 0).isOK()) {
+ errmsg = "can't get query executor";
+ return false;
}
- private:
- bool getFieldName(OperationContext* txn, Collection* collection, IndexCatalog* indexCatalog,
- string* fieldOut, string* errOut, bool *isFrom2D) {
- vector<IndexDescriptor*> idxs;
+ unique_ptr<PlanExecutor> exec(rawExec);
- // First, try 2d.
- collection->getIndexCatalog()->findIndexByType(txn, IndexNames::GEO_2D, idxs);
- if (idxs.size() > 1) {
- *errOut = "more than one 2d index, not sure which to run geoNear on";
- return false;
+ double totalDistance = 0;
+ BSONObjBuilder resultBuilder(result.subarrayStart("results"));
+ double farthestDist = 0;
+
+ BSONObj currObj;
+ long long results = 0;
+ while ((results < numWanted) && PlanExecutor::ADVANCED == exec->getNext(&currObj, NULL)) {
+ // Come up with the correct distance.
+ double dist = currObj["$dis"].number() * distanceMultiplier;
+ totalDistance += dist;
+ if (dist > farthestDist) {
+ farthestDist = dist;
}
- if (1 == idxs.size()) {
- BSONObj indexKp = idxs[0]->keyPattern();
- BSONObjIterator kpIt(indexKp);
- while (kpIt.more()) {
- BSONElement elt = kpIt.next();
- if (String == elt.type() && IndexNames::GEO_2D == elt.valuestr()) {
- *fieldOut = elt.fieldName();
- *isFrom2D = true;
- return true;
- }
+ // Strip out '$dis' and '$pt' from the result obj. The rest gets added as 'obj'
+ // in the command result.
+ BSONObjIterator resIt(currObj);
+ BSONObjBuilder resBob;
+ while (resIt.more()) {
+ BSONElement elt = resIt.next();
+ if (!mongoutils::str::equals("$pt", elt.fieldName()) &&
+ !mongoutils::str::equals("$dis", elt.fieldName())) {
+ resBob.append(elt);
}
}
+ BSONObj resObj = resBob.obj();
- // Next, 2dsphere.
- idxs.clear();
- collection->getIndexCatalog()->findIndexByType(txn, IndexNames::GEO_2DSPHERE, idxs);
- if (0 == idxs.size()) {
- *errOut = "no geo indices for geoNear";
- return false;
+ // Don't make a too-big result object.
+ if (resultBuilder.len() + resObj.objsize() > BSONObjMaxUserSize) {
+ warning() << "Too many geoNear results for query " << rewritten.toString()
+ << ", truncating output.";
+ break;
}
- if (idxs.size() > 1) {
- *errOut = "more than one 2dsphere index, not sure which to run geoNear on";
- return false;
+ // Add the next result to the result builder.
+ BSONObjBuilder oneResultBuilder(
+ resultBuilder.subobjStart(BSONObjBuilder::numStr(results)));
+ oneResultBuilder.append("dis", dist);
+ if (includeLocs) {
+ oneResultBuilder.appendAs(currObj["$pt"], "loc");
}
+ oneResultBuilder.append("obj", resObj);
+ oneResultBuilder.done();
+ ++results;
+ }
- // 1 == idx.size()
+ resultBuilder.done();
+
+ // Fill out the stats subobj.
+ BSONObjBuilder stats(result.subobjStart("stats"));
+
+ // Fill in nscanned from the explain.
+ PlanSummaryStats summary;
+ Explain::getSummaryStats(exec.get(), &summary);
+ stats.appendNumber("nscanned", summary.totalKeysExamined);
+ stats.appendNumber("objectsLoaded", summary.totalDocsExamined);
+
+ stats.append("avgDistance", totalDistance / results);
+ stats.append("maxDistance", farthestDist);
+ stats.append("time", CurOp::get(txn)->elapsedMillis());
+ stats.done();
+
+ return true;
+ }
+
+private:
+ bool getFieldName(OperationContext* txn,
+ Collection* collection,
+ IndexCatalog* indexCatalog,
+ string* fieldOut,
+ string* errOut,
+ bool* isFrom2D) {
+ vector<IndexDescriptor*> idxs;
+
+ // First, try 2d.
+ collection->getIndexCatalog()->findIndexByType(txn, IndexNames::GEO_2D, idxs);
+ if (idxs.size() > 1) {
+ *errOut = "more than one 2d index, not sure which to run geoNear on";
+ return false;
+ }
+
+ if (1 == idxs.size()) {
BSONObj indexKp = idxs[0]->keyPattern();
BSONObjIterator kpIt(indexKp);
while (kpIt.more()) {
BSONElement elt = kpIt.next();
- if (String == elt.type() && IndexNames::GEO_2DSPHERE == elt.valuestr()) {
+ if (String == elt.type() && IndexNames::GEO_2D == elt.valuestr()) {
*fieldOut = elt.fieldName();
- *isFrom2D = false;
+ *isFrom2D = true;
return true;
}
}
+ }
+ // Next, 2dsphere.
+ idxs.clear();
+ collection->getIndexCatalog()->findIndexByType(txn, IndexNames::GEO_2DSPHERE, idxs);
+ if (0 == idxs.size()) {
+ *errOut = "no geo indices for geoNear";
return false;
}
- } geo2dFindNearCmd;
+
+ if (idxs.size() > 1) {
+ *errOut = "more than one 2dsphere index, not sure which to run geoNear on";
+ return false;
+ }
+
+ // 1 == idx.size()
+ BSONObj indexKp = idxs[0]->keyPattern();
+ BSONObjIterator kpIt(indexKp);
+ while (kpIt.more()) {
+ BSONElement elt = kpIt.next();
+ if (String == elt.type() && IndexNames::GEO_2DSPHERE == elt.valuestr()) {
+ *fieldOut = elt.fieldName();
+ *isFrom2D = false;
+ return true;
+ }
+ }
+
+ return false;
+ }
+} geo2dFindNearCmd;
} // namespace mongo