int64_t WiredTigerUtil::getIdentSize(WT_SESSION* s, const std::string& uri ) { BSONObjBuilder b; Status status = WiredTigerUtil::exportTableToBSON(s, "statistics:" + uri, "statistics=(fast)", &b); if ( !status.isOK() ) { if ( status.code() == ErrorCodes::CursorNotFound ) { // ident gone, so its 0 return 0; } uassertStatusOK( status ); } BSONObj obj = b.obj(); BSONObj sub = obj["block-manager"].Obj(); BSONElement e = sub["file size in bytes"]; invariant( e.type() ); if ( e.isNumber() ) return e.safeNumberLong(); return strtoull( e.valuestrsafe(), NULL, 10 ); }
void BackgroundSync::loadLastAppliedHash(OperationContext* txn) { BSONObj oplogEntry; try { if (!Helpers::getLast(txn, rsoplog, oplogEntry)) { // This can happen when we are to do an initial sync. lastHash will be set // after the initial sync is complete. _lastAppliedHash = 0; return; } } catch (const DBException& ex) { severe() << "Problem reading " << rsoplog << ": " << ex.toStatus(); fassertFailed(18904); } BSONElement hashElement = oplogEntry[hashFieldName]; if (hashElement.eoo()) { severe() << "Most recent entry in " << rsoplog << " missing \"" << hashFieldName << "\" field"; fassertFailed(18902); } if (hashElement.type() != NumberLong) { severe() << "Expected type of \"" << hashFieldName << "\" in most recent " << rsoplog << " entry to have type NumberLong, but found " << typeName(hashElement.type()); fassertFailed(18903); } _lastAppliedHash = hashElement.safeNumberLong(); }
long long BackgroundSync::_readLastAppliedHash(OperationContext* txn) { BSONObj oplogEntry; try { MONGO_WRITE_CONFLICT_RETRY_LOOP_BEGIN { ScopedTransaction transaction(txn, MODE_IX); Lock::DBLock lk(txn->lockState(), "local", MODE_X); bool success = Helpers::getLast(txn, rsOplogName.c_str(), oplogEntry); if (!success) { // This can happen when we are to do an initial sync. lastHash will be set // after the initial sync is complete. return 0; } } MONGO_WRITE_CONFLICT_RETRY_LOOP_END(txn, "readLastAppliedHash", rsOplogName); } catch (const DBException& ex) { severe() << "Problem reading " << rsOplogName << ": " << ex.toStatus(); fassertFailed(18904); } BSONElement hashElement = oplogEntry[hashFieldName]; if (hashElement.eoo()) { severe() << "Most recent entry in " << rsOplogName << " missing \"" << hashFieldName << "\" field"; fassertFailed(18902); } if (hashElement.type() != NumberLong) { severe() << "Expected type of \"" << hashFieldName << "\" in most recent " << rsOplogName << " entry to have type NumberLong, but found " << typeName(hashElement.type()); fassertFailed(18903); } return hashElement.safeNumberLong(); }
long long BackgroundSync::_readLastAppliedHash(OperationContext* txn) { BSONObj oplogEntry; try { // Uses WuoW because there is no way to demarcate a read transaction boundary. Lock::DBLock lk(txn->lockState(), "local", MODE_X); WriteUnitOfWork uow(txn); bool success = Helpers::getLast(txn, rsoplog, oplogEntry); uow.commit(); if (!success) { // This can happen when we are to do an initial sync. lastHash will be set // after the initial sync is complete. return 0; } } catch (const DBException& ex) { severe() << "Problem reading " << rsoplog << ": " << ex.toStatus(); fassertFailed(18904); } BSONElement hashElement = oplogEntry[hashFieldName]; if (hashElement.eoo()) { severe() << "Most recent entry in " << rsoplog << " missing \"" << hashFieldName << "\" field"; fassertFailed(18902); } if (hashElement.type() != NumberLong) { severe() << "Expected type of \"" << hashFieldName << "\" in most recent " << rsoplog << " entry to have type NumberLong, but found " << typeName(hashElement.type()); fassertFailed(18903); } return hashElement.safeNumberLong(); }
void ParsedQuery::init( const BSONObj& q ) { _reset(); uassert( 10105 , "bad skip value in query", _ntoskip >= 0); if ( _ntoreturn < 0 ) { /* _ntoreturn greater than zero is simply a hint on how many objects to send back per "cursor batch". A negative number indicates a hard limit. */ _wantMore = false; _ntoreturn = -_ntoreturn; } BSONElement e = q["query"]; if ( ! e.isABSONObj() ) e = q["$query"]; if ( e.isABSONObj() ) { _filter = e.embeddedObject(); _initTop( q ); } else { _filter = q; } _filter = _filter.getOwned(); // // Parse options that are valid for both queries and commands // // $readPreference _hasReadPref = q.hasField(Query::ReadPrefField.name()); // $maxTimeMS BSONElement maxTimeMSElt = q.getField("$maxTimeMS"); if (!maxTimeMSElt.eoo()) { uassert(16987, mongoutils::str::stream() << "$maxTimeMS must be a number type, instead found type: " << maxTimeMSElt.type(), maxTimeMSElt.isNumber()); } // If $maxTimeMS was not specified, _maxTimeMS is set to 0 (special value for "allow to // run indefinitely"). long long maxTimeMSLongLong = maxTimeMSElt.safeNumberLong(); uassert(16988, "$maxTimeMS out of range [0,2147483647]", maxTimeMSLongLong >= 0 && maxTimeMSLongLong <= INT_MAX); _maxTimeMS = static_cast<int>(maxTimeMSLongLong); }
// static bool LiteParsedQuery::isValidSortOrder(const BSONObj& sortObj) { BSONObjIterator i(sortObj); while (i.more()) { BSONElement e = i.next(); if (isTextMeta(e)) { continue; } long long n = e.safeNumberLong(); if (!(e.isNumber() && (n == -1LL || n == 1LL))) { return false; } } return true; }
// static BSONObj QueryPlannerAnalysis::getSortPattern(const BSONObj& indexKeyPattern) { BSONObjBuilder sortBob; BSONObjIterator kpIt(indexKeyPattern); while (kpIt.more()) { BSONElement elt = kpIt.next(); if (elt.type() == mongo::String) { break; } long long val = elt.safeNumberLong(); int sortOrder = val >= 0 ? 1 : -1; sortBob.append(elt.fieldName(), sortOrder); } return sortBob.obj(); }
// static BSONObj LiteParsedQuery::normalizeSortOrder(const BSONObj& sortObj) { BSONObjBuilder b; BSONObjIterator i(sortObj); while (i.more()) { BSONElement e = i.next(); if (isTextScoreMeta(e)) { b.append(e); continue; } long long n = e.safeNumberLong(); int sortOrder = n >= 0 ? 1 : -1; b.append(e.fieldName(), sortOrder); } return b.obj(); }
// static bool LiteParsedQuery::isValidSortOrder(const BSONObj& sortObj) { BSONObjIterator i(sortObj); while (i.more()) { BSONElement e = i.next(); // fieldNameSize() includes NULL terminator. For empty field name, // we should be checking for 1 instead of 0. if (1 == e.fieldNameSize()) { return false; } if (isTextScoreMeta(e)) { continue; } long long n = e.safeNumberLong(); if (!(e.isNumber() && (n == -1LL || n == 1LL))) { return false; } } return true; }
void BSONElementHasher::recursiveHash( Hasher* h , const BSONElement& e , bool includeFieldName ) { int canonicalType = e.canonicalType(); h->addData( &canonicalType , sizeof( canonicalType ) ); if ( includeFieldName ){ h->addData( e.fieldName() , e.fieldNameSize() ); } if ( !e.mayEncapsulate() ){ //if there are no embedded objects (subobjects or arrays), //compute the hash, squashing numeric types to 64-bit ints if ( e.isNumber() ){ long long int i = e.safeNumberLong(); //well-defined for troublesome doubles h->addData( &i , sizeof( i ) ); } else { h->addData( e.value() , e.valuesize() ); } } else { //else identify the subobject. //hash any preceding stuff (in the case of codeWscope) //then each sub-element //then finish with the EOO element. BSONObj b; if ( e.type() == CodeWScope ) { h->addData( e.codeWScopeCode() , e.codeWScopeCodeLen() ); b = e.codeWScopeObject(); } else { b = e.embeddedObject(); } BSONObjIterator i(b); while( i.moreWithEOO() ) { BSONElement el = i.next(); recursiveHash( h , el , true ); } } }
virtual bool run(const string &db, BSONObj &cmdObj, int options, string &errmsg, BSONObjBuilder &result, bool fromRepl) { BSONElement e = cmdObj.firstElement(); long long bps; if (e.type() == String) { Status status = BytesQuantity<long long>::fromString(e.Stringdata(), bps); if (!status.isOK()) { stringstream ss; ss << "error parsing number " << e.Stringdata() << ": " << status.codeString() << " " << status.reason(); errmsg = ss.str(); return false; } } else { if (!e.isNumber()) { errmsg = "backupThrottle argument must be a number"; return false; } bps = e.safeNumberLong(); } return Manager::throttle(bps, errmsg, result); }
Status bsonExtractIntegerField(const BSONObj& object, const StringData& fieldName, long long* out) { BSONElement value; Status status = bsonExtractField(object, fieldName, &value); if (!status.isOK()) return status; if (!value.isNumber()) { return Status(ErrorCodes::TypeMismatch, mongoutils::str::stream() << "Expected field \"" << fieldName << "\" to have numeric type, but found " << typeName(value.type())); } long long result = value.safeNumberLong(); if (result != value.numberDouble()) { return Status(ErrorCodes::BadValue, mongoutils::str::stream() << "Expected field \"" << fieldName << "\" to have a value " "exactly representable as a 64-bit integer, but found " << value); } *out = result; return Status::OK(); }
// static StatusWith<int> LiteParsedQuery::parseMaxTimeMS(const BSONElement& maxTimeMSElt) { if (!maxTimeMSElt.eoo() && !maxTimeMSElt.isNumber()) { return StatusWith<int>(ErrorCodes::BadValue, (StringBuilder() << maxTimeMSElt.fieldNameStringData() << " must be a number").str()); } long long maxTimeMSLongLong = maxTimeMSElt.safeNumberLong(); // returns 0 on EOO if (maxTimeMSLongLong < 0 || maxTimeMSLongLong > INT_MAX) { return StatusWith<int>(ErrorCodes::BadValue, (StringBuilder() << maxTimeMSElt.fieldNameStringData() << " is out of range").str()); } double maxTimeMSDouble = maxTimeMSElt.numberDouble(); if (maxTimeMSElt.type() == mongo::NumberDouble && floor(maxTimeMSDouble) != maxTimeMSDouble) { return StatusWith<int>(ErrorCodes::BadValue, (StringBuilder() << maxTimeMSElt.fieldNameStringData() << " has non-integral value").str()); } return StatusWith<int>(static_cast<int>(maxTimeMSLongLong)); }
Status LiteParsedQuery::init(const string& ns, int ntoskip, int ntoreturn, int queryOptions, const BSONObj& queryObj, bool fromQueryMessage) { _ns = ns; _ntoskip = ntoskip; _ntoreturn = ntoreturn; _options = queryOptions; // TODO: If pq.hasOption(QueryOption_CursorTailable) make sure it's a capped collection and // make sure the order(??) is $natural: 1. if (_ntoskip < 0) { return Status(ErrorCodes::BadValue, "bad skip value in query"); } if (_ntoreturn < 0) { // _ntoreturn greater than zero is simply a hint on how many objects to send back per // "cursor batch". A negative number indicates a hard limit. _wantMore = false; _ntoreturn = -_ntoreturn; } if (fromQueryMessage) { BSONElement queryField = queryObj["query"]; if (!queryField.isABSONObj()) { queryField = queryObj["$query"]; } if (queryField.isABSONObj()) { _filter = queryField.embeddedObject().getOwned(); Status status = initFullQuery(queryObj); if (!status.isOK()) { return status; } } else { // TODO: Does this ever happen? _filter = queryObj.getOwned(); } } else { // This is the debugging code path. _filter = queryObj.getOwned(); } // // Parse options that are valid for both queries and commands // // $readPreference _hasReadPref = queryObj.hasField("$readPreference"); // $maxTimeMS BSONElement maxTimeMSElt = queryObj.getField("$maxTimeMS"); if (!maxTimeMSElt.eoo() && !maxTimeMSElt.isNumber()) { return Status(ErrorCodes::BadValue, "$maxTimeMS must be a number"); } // If $maxTimeMS was not specified, _maxTimeMS is set to 0 (special value for "allow to // run indefinitely"). long long maxTimeMSLongLong = maxTimeMSElt.safeNumberLong(); if (maxTimeMSLongLong < 0 || maxTimeMSLongLong > INT_MAX) { return Status(ErrorCodes::BadValue, "$maxTimeMS is out of range"); } _maxTimeMS = static_cast<int>(maxTimeMSLongLong); return Status::OK(); }
/** * If uuid is specified, add it to the collection specified by nss. This will error if the * collection already has a UUID. */ Status _collModInternal(OperationContext* opCtx, const NamespaceString& nss, const BSONObj& cmdObj, BSONObjBuilder* result, bool upgradeUniqueIndexes) { StringData dbName = nss.db(); AutoGetDb autoDb(opCtx, dbName, MODE_X); Database* const db = autoDb.getDb(); Collection* coll = db ? db->getCollection(opCtx, nss) : nullptr; // May also modify a view instead of a collection. boost::optional<ViewDefinition> view; if (db && !coll) { const auto sharedView = db->getViewCatalog()->lookup(opCtx, nss.ns()); if (sharedView) { // We copy the ViewDefinition as it is modified below to represent the requested state. view = {*sharedView}; } } // This can kill all cursors so don't allow running it while a background operation is in // progress. BackgroundOperation::assertNoBgOpInProgForNs(nss); // If db/collection/view does not exist, short circuit and return. if (!db || (!coll && !view)) { return Status(ErrorCodes::NamespaceNotFound, "ns does not exist"); } // This is necessary to set up CurOp and update the Top stats. OldClientContext ctx(opCtx, nss.ns()); bool userInitiatedWritesAndNotPrimary = opCtx->writesAreReplicated() && !repl::ReplicationCoordinator::get(opCtx)->canAcceptWritesFor(opCtx, nss); if (userInitiatedWritesAndNotPrimary) { return Status(ErrorCodes::NotMaster, str::stream() << "Not primary while setting collection options on " << nss.ns()); } BSONObjBuilder oplogEntryBuilder; auto statusW = parseCollModRequest(opCtx, nss, coll, cmdObj, &oplogEntryBuilder); if (!statusW.isOK()) { return statusW.getStatus(); } CollModRequest cmr = statusW.getValue(); WriteUnitOfWork wunit(opCtx); // Handle collMod on a view and return early. The View Catalog handles the creation of oplog // entries for modifications on a view. if (view) { if (!cmr.viewPipeLine.eoo()) view->setPipeline(cmr.viewPipeLine); if (!cmr.viewOn.empty()) view->setViewOn(NamespaceString(dbName, cmr.viewOn)); ViewCatalog* catalog = db->getViewCatalog(); BSONArrayBuilder pipeline; for (auto& item : view->pipeline()) { pipeline.append(item); } auto errorStatus = catalog->modifyView(opCtx, nss, view->viewOn(), BSONArray(pipeline.obj())); if (!errorStatus.isOK()) { return errorStatus; } wunit.commit(); return Status::OK(); } // In order to facilitate the replication rollback process, which makes a best effort attempt to // "undo" a set of oplog operations, we store a snapshot of the old collection options to // provide to the OpObserver. TTL index updates aren't a part of collection options so we // save the relevant TTL index data in a separate object. CollectionOptions oldCollOptions = coll->getCatalogEntry()->getCollectionOptions(opCtx); boost::optional<TTLCollModInfo> ttlInfo; // Handle collMod operation type appropriately. // TTLIndex if (!cmr.indexExpireAfterSeconds.eoo()) { BSONElement& newExpireSecs = cmr.indexExpireAfterSeconds; BSONElement oldExpireSecs = cmr.idx->infoObj().getField("expireAfterSeconds"); if (SimpleBSONElementComparator::kInstance.evaluate(oldExpireSecs != newExpireSecs)) { result->appendAs(oldExpireSecs, "expireAfterSeconds_old"); // Change the value of "expireAfterSeconds" on disk. coll->getCatalogEntry()->updateTTLSetting( opCtx, cmr.idx->indexName(), newExpireSecs.safeNumberLong()); // Notify the index catalog that the definition of this index changed. cmr.idx = coll->getIndexCatalog()->refreshEntry(opCtx, cmr.idx); result->appendAs(newExpireSecs, "expireAfterSeconds_new"); opCtx->recoveryUnit()->onRollback([ opCtx, idx = cmr.idx, coll ]() { coll->getIndexCatalog()->refreshEntry(opCtx, idx); }); } // Save previous TTL index expiration. ttlInfo = TTLCollModInfo{Seconds(newExpireSecs.safeNumberLong()), Seconds(oldExpireSecs.safeNumberLong()), cmr.idx->indexName()}; } // The Validator, ValidationAction and ValidationLevel are already parsed and must be OK. if (!cmr.collValidator.eoo()) invariant(coll->setValidator(opCtx, cmr.collValidator.Obj())); if (!cmr.collValidationAction.empty()) invariant(coll->setValidationAction(opCtx, cmr.collValidationAction)); if (!cmr.collValidationLevel.empty()) invariant(coll->setValidationLevel(opCtx, cmr.collValidationLevel)); // UsePowerof2Sizes if (!cmr.usePowerOf2Sizes.eoo()) setCollectionOptionFlag(opCtx, coll, cmr.usePowerOf2Sizes, result); // NoPadding if (!cmr.noPadding.eoo()) setCollectionOptionFlag(opCtx, coll, cmr.noPadding, result); // Upgrade unique indexes if (upgradeUniqueIndexes) { // A cmdObj with an empty collMod, i.e. nFields = 1, implies that it is a Unique Index // upgrade collMod. invariant(cmdObj.nFields() == 1); std::vector<std::string> indexNames; coll->getCatalogEntry()->getAllUniqueIndexes(opCtx, &indexNames); for (size_t i = 0; i < indexNames.size(); i++) { const IndexDescriptor* desc = coll->getIndexCatalog()->findIndexByName(opCtx, indexNames[i]); invariant(desc); // Update index metadata in storage engine. coll->getCatalogEntry()->updateIndexMetadata(opCtx, desc); // Refresh the in-memory instance of the index. desc = coll->getIndexCatalog()->refreshEntry(opCtx, desc); opCtx->recoveryUnit()->onRollback( [opCtx, desc, coll]() { coll->getIndexCatalog()->refreshEntry(opCtx, desc); }); } } // Only observe non-view collMods, as view operations are observed as operations on the // system.views collection. getGlobalServiceContext()->getOpObserver()->onCollMod( opCtx, nss, coll->uuid(), oplogEntryBuilder.obj(), oldCollOptions, ttlInfo); wunit.commit(); return Status::OK(); }
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); 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()); } if (!cmdObj["maxDistance"].eoo()) { uassert(17299, "maxDistance must be a number", cmdObj["maxDistance"].isNumber()); nearBob.append("$maxDistance", cmdObj["maxDistance"].number()); } 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()); } if (!cmdObj["uniqueDocs"].eoo()) { warning() << nss << ": ignoring deprecated uniqueDocs option in geoNear command"; } // 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(); // cout << "rewritten query: " << rewritten.toString() << endl; 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); } bool includeLocs = false; if (!cmdObj["includeLocs"].eoo()) { includeLocs = cmdObj["includeLocs"].trueValue(); } 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); } BSONObj projObj = BSON("$pt" << BSON("$meta" << LiteParsedQuery::metaGeoNearPoint) << "$dis" << BSON("$meta" << LiteParsedQuery::metaGeoNearDistance)); const WhereCallbackReal whereCallback(txn, nss.db()); auto statusWithCQ = CanonicalQuery::canonicalize( nss, rewritten, BSONObj(), projObj, 0, numWanted, BSONObj(), whereCallback); if (!statusWithCQ.isOK()) { errmsg = "Can't parse filter / create query"; return false; } unique_ptr<CanonicalQuery> cq = std::move(statusWithCQ.getValue()); // Prevent chunks from being cleaned up during yields - this allows us to only check the // version on initial entry into geoNear. RangePreserver preserver(collection); PlanExecutor* rawExec; if (!getExecutor(txn, collection, cq.release(), PlanExecutor::YIELD_AUTO, &rawExec, 0) .isOK()) { errmsg = "can't get query executor"; return false; } 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(); // 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; } // 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; } 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; }
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); 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()); } if (!cmdObj["maxDistance"].eoo()) { uassert(17299, "maxDistance must be a number", cmdObj["maxDistance"].isNumber()); nearBob.append("$maxDistance", cmdObj["maxDistance"].number()); } 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()); } if (!cmdObj["uniqueDocs"].eoo()) { warning() << nss << ": ignoring deprecated uniqueDocs option in geoNear command"; } // 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(); // Extract the collation, if it exists. // TODO SERVER-23473: Pass this collation spec object down so that it can be converted into // a CollatorInterface. BSONObj collation; { BSONElement collationElt; Status collationEltStatus = bsonExtractTypedField(cmdObj, "collation", BSONType::Object, &collationElt); if (!collationEltStatus.isOK() && (collationEltStatus != ErrorCodes::NoSuchKey)) { return appendCommandStatus(result, collationEltStatus); } if (collationEltStatus.isOK()) { collation = collationElt.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); } bool includeLocs = false; if (!cmdObj["includeLocs"].eoo()) { includeLocs = cmdObj["includeLocs"].trueValue(); } 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); } BSONObj projObj = BSON("$pt" << BSON("$meta" << LiteParsedQuery::metaGeoNearPoint) << "$dis" << BSON("$meta" << LiteParsedQuery::metaGeoNearDistance)); const ExtensionsCallbackReal extensionsCallback(txn, &nss); auto statusWithCQ = CanonicalQuery::canonicalize( nss, rewritten, BSONObj(), projObj, 0, numWanted, BSONObj(), extensionsCallback); if (!statusWithCQ.isOK()) { errmsg = "Can't parse filter / create query"; return false; } unique_ptr<CanonicalQuery> cq = std::move(statusWithCQ.getValue()); // Prevent chunks from being cleaned up during yields - this allows us to only check the // version on initial entry into geoNear. RangePreserver preserver(collection); auto statusWithPlanExecutor = getExecutor(txn, collection, std::move(cq), PlanExecutor::YIELD_AUTO, 0); if (!statusWithPlanExecutor.isOK()) { errmsg = "can't get query executor"; return false; } unique_ptr<PlanExecutor> exec = std::move(statusWithPlanExecutor.getValue()); double totalDistance = 0; BSONObjBuilder resultBuilder(result.subarrayStart("results")); double farthestDist = 0; BSONObj currObj; long long results = 0; PlanExecutor::ExecState state; while (PlanExecutor::ADVANCED == (state = 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(); // 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; } // 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; // Break if we have the number of requested result documents. if (results >= numWanted) { break; } } resultBuilder.done(); // Return an error if execution fails for any reason. if (PlanExecutor::FAILURE == state || PlanExecutor::DEAD == state) { log() << "Plan executor error during geoNear command: " << PlanExecutor::statestr(state) << ", stats: " << Explain::getWinningPlanStats(exec.get()); return appendCommandStatus(result, Status(ErrorCodes::OperationFailed, str::stream() << "Executor error during geoNear command: " << WorkingSetCommon::toStatusString(currObj))); } PlanSummaryStats summary; Explain::getSummaryStats(*exec, &summary); // Fill out the stats subobj. BSONObjBuilder stats(result.subobjStart("stats")); stats.appendNumber("nscanned", summary.totalKeysExamined); stats.appendNumber("objectsLoaded", summary.totalDocsExamined); if (results > 0) { stats.append("avgDistance", totalDistance / results); } stats.append("maxDistance", farthestDist); stats.append("time", CurOp::get(txn)->elapsedMillis()); stats.done(); collection->infoCache()->notifyOfQuery(txn, summary.indexesUsed); CurOp::get(txn)->debug().setPlanSummaryMetrics(summary); return true; }