diff options
Diffstat (limited to 'src/mongo/db/commands/geo_near_cmd.cpp')
-rw-r--r-- | src/mongo/db/commands/geo_near_cmd.cpp | 449 |
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 |