void getKeys( const BSONObj &obj, BSONObjSet &keys ) const { BSONElement loc = obj.getFieldDotted( _geo ); if ( loc.eoo() ) return; uassert( 13323 , "latlng not an array" , loc.isABSONObj() ); string root; { BSONObjIterator i( loc.Obj() ); BSONElement x = i.next(); BSONElement y = i.next(); root = makeString( hash(x) , hash(y) ); } verify( _other.size() == 1 ); BSONElementSet all; obj.getFieldsDotted( _other[0] , all ); if ( all.size() == 0 ) { _add( obj , root , BSONElement() , keys ); } else { for ( BSONElementSet::iterator i=all.begin(); i!=all.end(); ++i ) { _add( obj , root , *i , keys ); } } }
void getKeys(const BSONObj& obj, BSONObjSet& keys) const { verify(_fields.size() >= 1); BSONObjSet keysToAdd; // We output keys in the same order as the fields we index. for (size_t i = 0; i < _fields.size(); ++i) { const IndexedField &field = _fields[i]; // First, we get the keys that this field adds. Either they're added literally from // the value of the field, or they're transformed if the field is geo. BSONElementSet fieldElements; // false means Don't expand the last array, duh. obj.getFieldsDotted(field.name, fieldElements, false); BSONObjSet keysForThisField; if (IndexedField::GEO == field.type) { getGeoKeys(fieldElements, &keysForThisField); } else if (IndexedField::LITERAL == field.type) { getLiteralKeys(fieldElements, &keysForThisField); } else { verify(0); } // We expect there to be _spec->_missingField() present in the keys if data is // missing. So, this should be non-empty. verify(!keysForThisField.empty()); // We take the Cartesian product of all of the keys. This requires that we have // some keys to take the Cartesian product with. If keysToAdd.empty(), we // initialize it. if (keysToAdd.empty()) { keysToAdd = keysForThisField; continue; } BSONObjSet updatedKeysToAdd; for (BSONObjSet::const_iterator it = keysToAdd.begin(); it != keysToAdd.end(); ++it) { for (BSONObjSet::const_iterator newIt = keysForThisField.begin(); newIt!= keysForThisField.end(); ++newIt) { BSONObjBuilder b; b.appendElements(*it); b.append(newIt->firstElement()); updatedKeysToAdd.insert(b.obj()); } } keysToAdd = updatedKeysToAdd; } if (keysToAdd.size() > _params.maxKeysPerInsert) { warning() << "insert of geo object generated lots of keys (" << keysToAdd.size() << ") consider creating larger buckets. obj=" << obj; } for (BSONObjSet::const_iterator it = keysToAdd.begin(); it != keysToAdd.end(); ++it) { keys.insert(*it); } }
void S2AccessMethod::getKeys(const BSONObj& obj, BSONObjSet* keys) { BSONObjSet keysToAdd; // We output keys in the same order as the fields we index. BSONObjIterator i(_descriptor->keyPattern()); while (i.more()) { BSONElement e = i.next(); // First, we get the keys that this field adds. Either they're added literally from // the value of the field, or they're transformed if the field is geo. BSONElementSet fieldElements; // false means Don't expand the last array, duh. obj.getFieldsDotted(e.fieldName(), fieldElements, false); BSONObjSet keysForThisField; if (IndexNames::GEO_2DSPHERE == e.valuestr()) { // We can't ever return documents that don't have geometry so don't bother indexing // them. if (fieldElements.empty()) { return; } getGeoKeys(obj, fieldElements, &keysForThisField); } else { getLiteralKeys(fieldElements, &keysForThisField); } // We expect there to be the missing field element present in the keys if data is // missing. So, this should be non-empty. verify(!keysForThisField.empty()); // We take the Cartesian product of all of the keys. This requires that we have // some keys to take the Cartesian product with. If keysToAdd.empty(), we // initialize it. if (keysToAdd.empty()) { keysToAdd = keysForThisField; continue; } BSONObjSet updatedKeysToAdd; for (BSONObjSet::const_iterator it = keysToAdd.begin(); it != keysToAdd.end(); ++it) { for (BSONObjSet::const_iterator newIt = keysForThisField.begin(); newIt!= keysForThisField.end(); ++newIt) { BSONObjBuilder b; b.appendElements(*it); b.append(newIt->firstElement()); updatedKeysToAdd.insert(b.obj()); } } keysToAdd = updatedKeysToAdd; } if (keysToAdd.size() > _params.maxKeysPerInsert) { warning() << "insert of geo object generated lots of keys (" << keysToAdd.size() << ") consider creating larger buckets. obj=" << obj; } *keys = keysToAdd; }
bool AllElemMatchOp::matches( const BSONObj& doc, MatchDetails* details ) const { BSONElementSet all; doc.getFieldsDotted( _path, all, false ); for ( BSONElementSet::const_iterator i = all.begin(); i != all.end(); ++i ) { BSONElement sub = *i; if ( sub.type() != Array ) continue; if ( _allMatch( sub.Obj() ) ) { return true; } } return false; }
// static void ExpressionKeysPrivate::getHaystackKeys(const BSONObj& obj, const std::string& geoField, const std::vector<std::string>& otherFields, double bucketSize, BSONObjSet* keys) { BSONElement loc = obj.getFieldDotted(geoField); if (loc.eoo()) { return; } // NOTE: We explicitly test nFields >= 2 to support legacy users who may have indexed // (intentionally or unintentionally) objects/arrays with more than two fields. uassert(16775, str::stream() << "cannot extract [lng, lat] array or object from " << obj, loc.isABSONObj() && loc.Obj().nFields() >= 2); string root; { BSONObjIterator i(loc.Obj()); BSONElement x = i.next(); BSONElement y = i.next(); root = makeHaystackString(hashHaystackElement(x, bucketSize), hashHaystackElement(y, bucketSize)); } verify(otherFields.size() == 1); BSONElementSet all; // This is getFieldsDotted (plural not singular) since the object we're indexing // may be an array. obj.getFieldsDotted(otherFields[0], all); if (all.size() == 0) { // We're indexing a document that doesn't have the secondary non-geo field present. // XXX: do we want to add this even if all.size() > 0? result:empty search terms // match everything instead of only things w/empty search terms) addKey(root, BSONElement(), keys); } else { // Ex:If our secondary field is type: "foo" or type: {a:"foo", b:"bar"}, // all.size()==1. We can query on the complete field. // Ex: If our secondary field is type: ["A", "B"] all.size()==2 and all has values // "A" and "B". The query looks for any of the fields in the array. for (BSONElementSet::iterator i = all.begin(); i != all.end(); ++i) { addKey(root, *i, keys); } } }
/** * Find and parse all geometry elements on the appropriate field path from the document. */ static void extractGeometries(const BSONObj& doc, const string& path, vector<StoredGeometry*>* geometries) { BSONElementSet geomElements; // NOTE: Annoyingly, we cannot just expand arrays b/c single 2d points are arrays, we need // to manually expand all results to check if they are geometries doc.getFieldsDotted(path, geomElements, false /* expand arrays */); for (BSONElementSet::iterator it = geomElements.begin(); it != geomElements.end(); ++it) { const BSONElement& el = *it; auto_ptr<StoredGeometry> stored(StoredGeometry::parseFrom(el)); if (stored.get()) { // Valid geometry element geometries->push_back(stored.release()); } else if (el.type() == Array) { // Many geometries may be in an array BSONObjIterator arrIt(el.Obj()); while (arrIt.more()) { const BSONElement nextEl = arrIt.next(); stored.reset(StoredGeometry::parseFrom(nextEl)); if (stored.get()) { // Valid geometry element geometries->push_back(stored.release()); } else { warning() << "geoNear stage read non-geometry element " << nextEl.toString() << " in array " << el.toString(); } } } else { warning() << "geoNear stage read non-geometry element " << el.toString(); } } }
void getKeys(const BSONObj &obj, BSONObjSet &keys) const { BSONElement loc = obj.getFieldDotted(_geoField); if (loc.eoo()) return; uassert(13323, "latlng not an array", loc.isABSONObj()); string root; { BSONObjIterator i(loc.Obj()); BSONElement x = i.next(); BSONElement y = i.next(); root = makeString(hash(x), hash(y)); } verify(_otherFields.size() == 1); BSONElementSet all; // This is getFieldsDotted (plural not singular) since the object we're indexing // may be an array. obj.getFieldsDotted(_otherFields[0], all); if (all.size() == 0) { // We're indexing a document that doesn't have the secondary non-geo field present. // XXX: do we want to add this even if all.size() > 0? result:empty search terms // match everything instead of only things w/empty search terms) addKey(root, BSONElement(), keys); } else { // Ex:If our secondary field is type: "foo" or type: {a:"foo", b:"bar"}, // all.size()==1. We can query on the complete field. // Ex: If our secondary field is type: ["A", "B"] all.size()==2 and all has values // "A" and "B". The query looks for any of the fields in the array. for (BSONElementSet::iterator i = all.begin(); i != all.end(); ++i) { addKey(root, *i, keys); } } }
bool run(const string& dbname, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool fromRepl ){ string ns = dbname + '.' + cmdObj.firstElement().valuestr(); string key = cmdObj["key"].valuestrsafe(); BSONObj keyPattern = BSON( key << 1 ); BSONObj query = getQuery( cmdObj ); BSONElementSet values; shared_ptr<Cursor> cursor = bestGuessCursor(ns.c_str() , query , BSONObj() ); scoped_ptr<ClientCursor> cc (new ClientCursor(QueryOption_NoCursorTimeout, cursor, ns)); while ( cursor->ok() ){ if ( !cursor->matcher() || cursor->matcher()->matchesCurrent( cursor.get() ) ){ BSONObj o = cursor->current(); o.getFieldsDotted( key, values ); } cursor->advance(); if (!cc->yieldSometimes()) break; RARELY killCurrentOp.checkForInterrupt(); } BSONArrayBuilder b( result.subarrayStart( "values" ) ); for ( BSONElementSet::iterator i = values.begin() ; i != values.end(); i++ ){ b.append( *i ); } BSONObj arr = b.done(); uassert(10044, "distinct too big, 4mb cap", arr.objsize() < BSONObjMaxUserSize ); return true; }
void ExpressionKeysPrivate::getS2Keys(const BSONObj& obj, const BSONObj& keyPattern, const S2IndexingParams& params, BSONObjSet* keys) { BSONObjSet keysToAdd; // Does one of our documents have a geo field? bool haveGeoField = false; // We output keys in the same order as the fields we index. BSONObjIterator i(keyPattern); while (i.more()) { BSONElement e = i.next(); // First, we get the keys that this field adds. Either they're added literally from // the value of the field, or they're transformed if the field is geo. BSONElementSet fieldElements; // false means Don't expand the last array, duh. obj.getFieldsDotted(e.fieldName(), fieldElements, false); BSONObjSet keysForThisField; if (IndexNames::GEO_2DSPHERE == e.valuestr()) { if (params.indexVersion >= S2_INDEX_VERSION_2) { // For >= V2, // geo: null, // geo: undefined // geo: [] // should all behave like there is no geo field. So we look for these cases and // throw out the field elements if we find them. if (1 == fieldElements.size()) { BSONElement elt = *fieldElements.begin(); // Get the :null and :undefined cases. if (elt.isNull() || Undefined == elt.type()) { fieldElements.clear(); } else if (elt.isABSONObj()) { // And this is the :[] case. BSONObj obj = elt.Obj(); if (0 == obj.nFields()) { fieldElements.clear(); } } } // >= V2 2dsphere indices require that at least one geo field to be present in a // document in order to index it. if (fieldElements.size() > 0) { haveGeoField = true; } } getS2GeoKeys(obj, fieldElements, params, &keysForThisField); } else { getS2LiteralKeys(fieldElements, params.collator, &keysForThisField); } // We expect there to be the missing field element present in the keys if data is // missing. So, this should be non-empty. verify(!keysForThisField.empty()); // We take the Cartesian product of all of the keys. This requires that we have // some keys to take the Cartesian product with. If keysToAdd.empty(), we // initialize it. if (keysToAdd.empty()) { keysToAdd = keysForThisField; continue; } BSONObjSet updatedKeysToAdd; for (BSONObjSet::const_iterator it = keysToAdd.begin(); it != keysToAdd.end(); ++it) { for (BSONObjSet::const_iterator newIt = keysForThisField.begin(); newIt != keysForThisField.end(); ++newIt) { BSONObjBuilder b; b.appendElements(*it); b.append(newIt->firstElement()); updatedKeysToAdd.insert(b.obj()); } } keysToAdd = updatedKeysToAdd; } // Make sure that if we're >= V2 there's at least one geo field present in the doc. if (params.indexVersion >= S2_INDEX_VERSION_2) { if (!haveGeoField) { return; } } if (keysToAdd.size() > params.maxKeysPerInsert) { warning() << "Insert of geo object generated a high number of keys." << " num keys: " << keysToAdd.size() << " obj inserted: " << obj; } *keys = keysToAdd; }
// static void ExpressionKeysPrivate::get2DKeys(const BSONObj& obj, const TwoDIndexingParams& params, BSONObjSet* keys, std::vector<BSONObj>* locs) { BSONElementMSet bSet; // Get all the nested location fields, but don't return individual elements from // the last array, if it exists. obj.getFieldsDotted(params.geo.c_str(), bSet, false); if (bSet.empty()) return; for (BSONElementMSet::iterator setI = bSet.begin(); setI != bSet.end(); ++setI) { BSONElement geo = *setI; if (geo.eoo() || !geo.isABSONObj()) continue; // // Grammar for location lookup: // locs ::= [loc,loc,...,loc]|{<k>:loc,<k>:loc,...,<k>:loc}|loc // loc ::= { <k1> : #, <k2> : # }|[#, #]|{} // // Empty locations are ignored, preserving single-location semantics // BSONObj embed = geo.embeddedObject(); if (embed.isEmpty()) continue; // Differentiate between location arrays and locations // by seeing if the first element value is a number bool singleElement = embed.firstElement().isNumber(); BSONObjIterator oi(embed); while (oi.more()) { BSONObj locObj; if (singleElement) { locObj = embed; } else { BSONElement locElement = oi.next(); uassert(16804, mongoutils::str::stream() << "location object expected, location array not in correct format", locElement.isABSONObj()); locObj = locElement.embeddedObject(); if (locObj.isEmpty()) continue; } BSONObjBuilder b(64); // Remember the actual location object if needed if (locs) locs->push_back(locObj); // Stop if we don't need to get anything but location objects if (!keys) { if (singleElement) break; else continue; } params.geoHashConverter->hash(locObj, &obj).appendHashMin(&b, ""); // Go through all the other index keys for (vector<pair<string, int>>::const_iterator i = params.other.begin(); i != params.other.end(); ++i) { // Get *all* fields for the index key BSONElementSet eSet; obj.getFieldsDotted(i->first, eSet); if (eSet.size() == 0) b.appendNull(""); else if (eSet.size() == 1) b.appendAs(*(eSet.begin()), ""); else { // If we have more than one key, store as an array of the objects BSONArrayBuilder aBuilder; for (BSONElementSet::iterator ei = eSet.begin(); ei != eSet.end(); ++ei) { aBuilder.append(*ei); } b.append("", aBuilder.arr()); } } keys->insert(b.obj()); if (singleElement) break; } } }
static bool run2DSphereGeoNear(NamespaceDetails* nsDetails, int idxNo, BSONObj& cmdObj, const GeoNearArguments &parsedArgs, string& errmsg, BSONObjBuilder& result) { auto_ptr<IndexDescriptor> descriptor(CatalogHack::getDescriptor(nsDetails, idxNo)); auto_ptr<S2AccessMethod> sam(new S2AccessMethod(descriptor.get())); const S2IndexingParams& params = sam->getParams(); auto_ptr<S2NearIndexCursor> nic(new S2NearIndexCursor(descriptor.get(), params)); vector<string> geoFieldNames; BSONObjIterator i(descriptor->keyPattern()); while (i.more()) { BSONElement e = i.next(); if (e.type() == String && IndexNames::GEO_2DSPHERE == e.valuestr()) { geoFieldNames.push_back(e.fieldName()); } } // NOTE(hk): If we add a new argument to geoNear, we could have a // 2dsphere index with multiple indexed geo fields, and the geoNear // could pick the one to run over. Right now, we just require one. uassert(16552, "geoNear requires exactly one indexed geo field", 1 == geoFieldNames.size()); NearQuery nearQuery(geoFieldNames[0]); uassert(16679, "Invalid geometry given as arguments to geoNear: " + cmdObj.toString(), nearQuery.parseFromGeoNear(cmdObj, params.radius)); uassert(16683, "geoNear on 2dsphere index requires spherical", parsedArgs.isSpherical); // NOTE(hk): For a speedup, we could look through the query to see if // we've geo-indexed any of the fields in it. vector<GeoQuery> regions; nic->seek(parsedArgs.query, nearQuery, regions); // We do pass in the query above, but it's just so we can possibly use it in our index // scan. We have to do our own matching. auto_ptr<Matcher> matcher(new Matcher(parsedArgs.query)); double totalDistance = 0; BSONObjBuilder resultBuilder(result.subarrayStart("results")); double farthestDist = 0; int results; for (results = 0; results < parsedArgs.numWanted && !nic->isEOF(); ++results) { BSONObj currObj = nic->getValue().obj(); if (!matcher->matches(currObj)) { --results; nic->next(); continue; } double dist = nic->currentDistance(); // If we got the distance in radians, output it in radians too. if (nearQuery.fromRadians) { dist /= params.radius; } dist *= parsedArgs.distanceMultiplier; totalDistance += dist; if (dist > farthestDist) { farthestDist = dist; } BSONObjBuilder oneResultBuilder( resultBuilder.subobjStart(BSONObjBuilder::numStr(results))); oneResultBuilder.append("dis", dist); if (parsedArgs.includeLocs) { BSONElementSet geoFieldElements; currObj.getFieldsDotted(geoFieldNames[0], geoFieldElements, false); for (BSONElementSet::iterator oi = geoFieldElements.begin(); oi != geoFieldElements.end(); ++oi) { if (oi->isABSONObj()) { oneResultBuilder.appendAs(*oi, "loc"); } } } oneResultBuilder.append("obj", currObj); oneResultBuilder.done(); nic->next(); } resultBuilder.done(); BSONObjBuilder stats(result.subobjStart("stats")); stats.appendNumber("nscanned", nic->nscanned()); stats.append("avgDistance", totalDistance / results); stats.append("maxDistance", farthestDist); stats.append("time", cc().curop()->elapsedMillis()); stats.done(); return true; }
bool run(const string& dbname, BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result, bool fromRepl ) { Timer t; string ns = dbname + '.' + cmdObj.firstElement().valuestr(); string key = cmdObj["key"].valuestrsafe(); BSONObj keyPattern = BSON( key << 1 ); BSONObj query = getQuery( cmdObj ); int bufSize = BSONObjMaxUserSize - 4096; BufBuilder bb( bufSize ); char * start = bb.buf(); BSONArrayBuilder arr( bb ); BSONElementSet values; long long nscanned = 0; // locations looked at long long nscannedObjects = 0; // full objects looked at long long n = 0; // matches NamespaceDetails * d = nsdetails( ns ); string cursorName; if (!d) { result.appendArray( "values" , BSONObj() ); result.append("stats", BSON("n" << 0 << "nscanned" << 0 << "nscannedObjects" << 0)); return true; } CanonicalQuery* cq; // XXX: project out just the field we're distinct-ing. May be covered... if (!CanonicalQuery::canonicalize(ns, query, &cq).isOK()) { uasserted(17215, "Can't canonicalize query " + query.toString()); return 0; } Runner* rawRunner; if (!getRunner(cq, &rawRunner).isOK()) { uasserted(17216, "Can't get runner for query " + query.toString()); return 0; } auto_ptr<Runner> runner(rawRunner); auto_ptr<DeregisterEvenIfUnderlyingCodeThrows> safety; ClientCursor::registerRunner(runner.get()); runner->setYieldPolicy(Runner::YIELD_AUTO); safety.reset(new DeregisterEvenIfUnderlyingCodeThrows(runner.get())); BSONObj obj; Runner::RunnerState state; while (Runner::RUNNER_ADVANCED == (state = runner->getNext(&obj, NULL))) { BSONElementSet elts; obj.getFieldsDotted(key, elts); for (BSONElementSet::iterator it = elts.begin(); it != elts.end(); ++it) { BSONElement elt = *it; if (values.count(elt)) { continue; } int currentBufPos = bb.len(); uassert(17217, "distinct too big, 16mb cap", (currentBufPos + elt.size() + 1024) < bufSize); arr.append(elt); BSONElement x(start + currentBufPos); values.insert(x); } } TypeExplain* bareExplain; Status res = runner->getExplainPlan(&bareExplain); if (res.isOK()) { auto_ptr<TypeExplain> explain(bareExplain); if (explain->isCursorSet()) { cursorName = explain->getCursor(); } n = explain->getN(); nscanned = explain->getNScanned(); nscannedObjects = explain->getNScannedObjects(); } verify( start == bb.buf() ); result.appendArray( "values" , arr.done() ); { BSONObjBuilder b; b.appendNumber( "n" , n ); b.appendNumber( "nscanned" , nscanned ); b.appendNumber( "nscannedObjects" , nscannedObjects ); b.appendNumber( "timems" , t.millis() ); b.append( "cursor" , cursorName ); result.append( "stats" , b.obj() ); } return true; }
bool run(OperationContext* txn, const string& dbname, BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result) { Timer t; // ensure that the key is a string uassert(18510, mongoutils::str::stream() << "The first argument to the distinct command " << "must be a string but was a " << typeName(cmdObj["key"].type()), cmdObj["key"].type() == mongo::String); // ensure that the where clause is a document if (cmdObj["query"].isNull() == false && cmdObj["query"].eoo() == false) { uassert(18511, mongoutils::str::stream() << "The query for the distinct command must be a " << "document but was a " << typeName(cmdObj["query"].type()), cmdObj["query"].type() == mongo::Object); } string key = cmdObj["key"].valuestrsafe(); BSONObj keyPattern = BSON(key << 1); BSONObj query = getQuery(cmdObj); int bufSize = BSONObjMaxUserSize - 4096; BufBuilder bb(bufSize); char* start = bb.buf(); BSONArrayBuilder arr(bb); BSONElementSet values; const string ns = parseNs(dbname, cmdObj); AutoGetCollectionForRead ctx(txn, ns); Collection* collection = ctx.getCollection(); if (!collection) { result.appendArray("values", BSONObj()); result.append("stats", BSON("n" << 0 << "nscanned" << 0 << "nscannedObjects" << 0)); return true; } auto statusWithPlanExecutor = getExecutorDistinct(txn, collection, query, key, PlanExecutor::YIELD_AUTO); if (!statusWithPlanExecutor.isOK()) { uasserted(17216, mongoutils::str::stream() << "Can't get executor for query " << query << ": " << statusWithPlanExecutor.getStatus().toString()); return 0; } unique_ptr<PlanExecutor> exec = std::move(statusWithPlanExecutor.getValue()); BSONObj obj; PlanExecutor::ExecState state; while (PlanExecutor::ADVANCED == (state = exec->getNext(&obj, NULL))) { // Distinct expands arrays. // // If our query is covered, each value of the key should be in the index key and // available to us without this. If a collection scan is providing the data, we may // have to expand an array. BSONElementSet elts; obj.getFieldsDotted(key, elts); for (BSONElementSet::iterator it = elts.begin(); it != elts.end(); ++it) { BSONElement elt = *it; if (values.count(elt)) { continue; } int currentBufPos = bb.len(); uassert(17217, "distinct too big, 16mb cap", (currentBufPos + elt.size() + 1024) < bufSize); arr.append(elt); BSONElement x(start + currentBufPos); values.insert(x); } } // Get summary information about the plan. PlanSummaryStats stats; Explain::getSummaryStats(*exec, &stats); verify(start == bb.buf()); result.appendArray("values", arr.done()); { BSONObjBuilder b; b.appendNumber("n", stats.nReturned); b.appendNumber("nscanned", stats.totalKeysExamined); b.appendNumber("nscannedObjects", stats.totalDocsExamined); b.appendNumber("timems", t.millis()); b.append("planSummary", Explain::getPlanSummary(exec.get())); result.append("stats", b.obj()); } return true; }
bool run(const string& dbname, BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result, bool fromRepl ) { Timer t; string ns = dbname + '.' + cmdObj.firstElement().valuestr(); string key = cmdObj["key"].valuestrsafe(); BSONObj keyPattern = BSON( key << 1 ); BSONObj query = getQuery( cmdObj ); int bufSize = BSONObjMaxUserSize - 4096; BufBuilder bb( bufSize ); char * start = bb.buf(); BSONArrayBuilder arr( bb ); BSONElementSet values; long long nscanned = 0; // locations looked at long long nscannedObjects = 0; // full objects looked at long long n = 0; // matches Collection* collection = cc().database()->getCollection( ns ); if (!collection) { result.appendArray( "values" , BSONObj() ); result.append("stats", BSON("n" << 0 << "nscanned" << 0 << "nscannedObjects" << 0)); return true; } Runner* rawRunner; Status status = getRunnerDistinct(collection, query, key, &rawRunner); if (!status.isOK()) { uasserted(17216, mongoutils::str::stream() << "Can't get runner for query " << query << ": " << status.toString()); return 0; } auto_ptr<Runner> runner(rawRunner); const ScopedRunnerRegistration safety(runner.get()); runner->setYieldPolicy(Runner::YIELD_AUTO); string cursorName; BSONObj obj; Runner::RunnerState state; while (Runner::RUNNER_ADVANCED == (state = runner->getNext(&obj, NULL))) { // Distinct expands arrays. // // If our query is covered, each value of the key should be in the index key and // available to us without this. If a collection scan is providing the data, we may // have to expand an array. BSONElementSet elts; obj.getFieldsDotted(key, elts); for (BSONElementSet::iterator it = elts.begin(); it != elts.end(); ++it) { BSONElement elt = *it; if (values.count(elt)) { continue; } int currentBufPos = bb.len(); uassert(17217, "distinct too big, 16mb cap", (currentBufPos + elt.size() + 1024) < bufSize); arr.append(elt); BSONElement x(start + currentBufPos); values.insert(x); } } TypeExplain* bareExplain; Status res = runner->getInfo(&bareExplain, NULL); if (res.isOK()) { auto_ptr<TypeExplain> explain(bareExplain); if (explain->isCursorSet()) { cursorName = explain->getCursor(); } n = explain->getN(); nscanned = explain->getNScanned(); nscannedObjects = explain->getNScannedObjects(); } verify( start == bb.buf() ); result.appendArray( "values" , arr.done() ); { BSONObjBuilder b; b.appendNumber( "n" , n ); b.appendNumber( "nscanned" , nscanned ); b.appendNumber( "nscannedObjects" , nscannedObjects ); b.appendNumber( "timems" , t.millis() ); b.append( "cursor" , cursorName ); result.append( "stats" , b.obj() ); } return true; }
bool run(OperationContext* txn, const string& dbname, BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result) { Timer t; const string ns = parseNs(dbname, cmdObj); AutoGetCollectionForRead ctx(txn, ns); Collection* collection = ctx.getCollection(); auto executor = getPlanExecutor(txn, collection, ns, cmdObj, false); if (!executor.isOK()) { return appendCommandStatus(result, executor.getStatus()); } string key = cmdObj[kKeyField].valuestrsafe(); int bufSize = BSONObjMaxUserSize - 4096; BufBuilder bb(bufSize); char* start = bb.buf(); BSONArrayBuilder arr(bb); BSONElementSet values; BSONObj obj; PlanExecutor::ExecState state; while (PlanExecutor::ADVANCED == (state = executor.getValue()->getNext(&obj, NULL))) { // Distinct expands arrays. // // If our query is covered, each value of the key should be in the index key and // available to us without this. If a collection scan is providing the data, we may // have to expand an array. BSONElementSet elts; obj.getFieldsDotted(key, elts); for (BSONElementSet::iterator it = elts.begin(); it != elts.end(); ++it) { BSONElement elt = *it; if (values.count(elt)) { continue; } int currentBufPos = bb.len(); uassert(17217, "distinct too big, 16mb cap", (currentBufPos + elt.size() + 1024) < bufSize); arr.append(elt); BSONElement x(start + currentBufPos); values.insert(x); } } // Return an error if execution fails for any reason. if (PlanExecutor::FAILURE == state || PlanExecutor::DEAD == state) { const std::unique_ptr<PlanStageStats> stats(executor.getValue()->getStats()); log() << "Plan executor error during distinct command: " << PlanExecutor::statestr(state) << ", stats: " << Explain::statsToBSON(*stats); return appendCommandStatus(result, Status(ErrorCodes::OperationFailed, str::stream() << "Executor error during distinct command: " << WorkingSetCommon::toStatusString(obj))); } // Get summary information about the plan. PlanSummaryStats stats; Explain::getSummaryStats(*executor.getValue(), &stats); collection->infoCache()->notifyOfQuery(txn, stats.indexesUsed); CurOp::get(txn)->debug().fromMultiPlanner = stats.fromMultiPlanner; CurOp::get(txn)->debug().replanned = stats.replanned; verify(start == bb.buf()); result.appendArray("values", arr.done()); { BSONObjBuilder b; b.appendNumber("n", stats.nReturned); b.appendNumber("nscanned", stats.totalKeysExamined); b.appendNumber("nscannedObjects", stats.totalDocsExamined); b.appendNumber("timems", t.millis()); b.append("planSummary", Explain::getPlanSummary(executor.getValue().get())); result.append("stats", b.obj()); } return true; }