// query.cpp /** * Copyright (C) 2008 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 . */ #include "stdafx.h" #include "query.h" #include "pdfile.h" #include "jsobjmanipulator.h" #include "../util/builder.h" #include #include "introspect.h" #include "btree.h" #include "../util/lruishmap.h" #include "json.h" #include "repl.h" #include "replset.h" #include "scanandorder.h" #include "security.h" #include "curop.h" #include "commands.h" #include "queryoptimizer.h" #include "lasterror.h" namespace mongo { /* We cut off further objects once we cross this threshold; thus, you might get a little bit more than this, it is a threshold rather than a limit. */ const int MaxBytesToReturnToClientAtOnce = 4 * 1024 * 1024; //ns->query->DiskLoc // LRUishMap lrutest(123); extern bool useCursors; extern bool useHints; // Just try to identify best plan. class DeleteOp : public QueryOp { public: DeleteOp( bool justOne, int& bestCount ) : justOne_( justOne ), count_(), bestCount_( bestCount ), _nscanned() { } virtual void init() { c_ = qp().newCursor(); _matcher.reset( new CoveredIndexMatcher( qp().query(), qp().indexKey() ) ); } virtual void next() { if ( !c_->ok() ) { setComplete(); return; } DiskLoc rloc = c_->currLoc(); if ( _matcher->matches(c_->currKey(), rloc ) ) { if ( !c_->getsetdup(rloc) ) ++count_; } c_->advance(); ++_nscanned; if ( count_ > bestCount_ ) bestCount_ = count_; if ( count_ > 0 ) { if ( justOne_ ) setComplete(); else if ( _nscanned >= 100 && count_ == bestCount_ ) setComplete(); } } virtual bool mayRecordPlan() const { return !justOne_; } virtual QueryOp *clone() const { return new DeleteOp( justOne_, bestCount_ ); } auto_ptr< Cursor > newCursor() const { return qp().newCursor(); } private: bool justOne_; int count_; int &bestCount_; long long _nscanned; auto_ptr< Cursor > c_; auto_ptr< CoveredIndexMatcher > _matcher; }; /* ns: namespace, e.g. . pattern: the "where" clause / criteria justOne: stop after 1 match */ long long deleteObjects(const char *ns, BSONObj pattern, bool justOne, bool logop, bool god) { if( !god ) { if ( strstr(ns, ".system.") ) { /* note a delete from system.indexes would corrupt the db if done here, as there are pointers into those objects in NamespaceDetails. */ uassert(12050, "cannot delete from system namespace", legalClientSystemNS( ns , true ) ); } if ( strchr( ns , '$' ) ){ log() << "cannot delete from collection with reserved $ in name: " << ns << endl; uassert( 10100 , "cannot delete from collection with reserved $ in name", strchr(ns, '$') == 0 ); } } NamespaceDetails *d = nsdetails( ns ); if ( ! d ) return 0; uassert( 10101 , "can't remove from a capped collection" , ! d->capped ); long long nDeleted = 0; QueryPlanSet s( ns, pattern, BSONObj() ); int best = 0; DeleteOp original( justOne, best ); shared_ptr< DeleteOp > bestOp = s.runOp( original ); auto_ptr< Cursor > creal = bestOp->newCursor(); if( !creal->ok() ) return nDeleted; CoveredIndexMatcher matcher(pattern, creal->indexKeyPattern()); auto_ptr cc( new ClientCursor(creal, ns, false) ); cc->setDoingDeletes( true ); CursorId id = cc->cursorid; unsigned long long nScanned = 0; do { if ( ++nScanned % 128 == 0 && !matcher.docMatcher().atomic() ) { if ( ! cc->yield() ){ cc.release(); // has already been deleted elsewhere break; } } // this way we can avoid calling updateLocation() every time (expensive) // as well as some other nuances handled cc->setDoingDeletes( true ); DiskLoc rloc = cc->c->currLoc(); BSONObj key = cc->c->currKey(); cc->c->advance(); if ( ! matcher.matches( key , rloc ) ) continue; assert( !cc->c->getsetdup(rloc) ); // can't be a dup, we deleted it! if ( !justOne ) { /* NOTE: this is SLOW. this is not good, noteLocation() was designed to be called across getMore blocks. here we might call millions of times which would be bad. */ cc->c->noteLocation(); } if ( logop ) { BSONElement e; if( BSONObj( rloc.rec() ).getObjectID( e ) ) { BSONObjBuilder b; b.append( e ); bool replJustOne = true; logOp( "d", ns, b.done(), 0, &replJustOne ); } else { problem() << "deleted object without id, not logging" << endl; } } theDataFileMgr.deleteRecord(ns, rloc.rec(), rloc); nDeleted++; if ( justOne ) break; cc->c->checkLocation(); } while ( cc->c->ok() ); if ( cc.get() && ClientCursor::find( id , false ) == 0 ){ cc.release(); } return nDeleted; } int otherTraceLevel = 0; int initialExtentSize(int len); bool runCommands(const char *ns, BSONObj& jsobj, CurOp& curop, BufBuilder &b, BSONObjBuilder& anObjBuilder, bool fromRepl, int queryOptions) { try { return _runCommands(ns, jsobj, b, anObjBuilder, fromRepl, queryOptions); } catch ( AssertionException& e ) { if ( !e.msg.empty() ) anObjBuilder.append("assertion", e.msg); } curop.debug().str << " assertion "; anObjBuilder.append("errmsg", "db assertion failure"); anObjBuilder.append("ok", 0.0); BSONObj x = anObjBuilder.done(); b.append((void*) x.objdata(), x.objsize()); return true; } int nCaught = 0; void killCursors(int n, long long *ids) { int k = 0; for ( int i = 0; i < n; i++ ) { if ( ClientCursor::erase(ids[i]) ) k++; } log( k == n ) << "killcursors: found " << k << " of " << n << '\n'; } BSONObj id_obj = fromjson("{\"_id\":1}"); BSONObj empty_obj = fromjson("{}"); //int dump = 0; /* empty result for error conditions */ QueryResult* emptyMoreResult(long long cursorid) { BufBuilder b(32768); b.skip(sizeof(QueryResult)); QueryResult *qr = (QueryResult *) b.buf(); qr->cursorId = 0; // 0 indicates no more data to retrieve. qr->startingFrom = 0; qr->len = b.len(); qr->setOperation(opReply); qr->nReturned = 0; b.decouple(); return qr; } QueryResult* getMore(const char *ns, int ntoreturn, long long cursorid , CurOp& curop ) { StringBuilder& ss = curop.debug().str; ClientCursor::Pointer p(cursorid); ClientCursor *cc = p._c; int bufSize = 512; if ( cc ){ bufSize += sizeof( QueryResult ); bufSize += ( ntoreturn ? 4 : 1 ) * 1024 * 1024; } BufBuilder b( bufSize ); b.skip(sizeof(QueryResult)); int resultFlags = 0; //QueryResult::ResultFlag_AwaitCapable; int start = 0; int n = 0; if ( !cc ) { log() << "getMore: cursorid not found " << ns << " " << cursorid << endl; cursorid = 0; resultFlags = QueryResult::ResultFlag_CursorNotFound; } else { ss << " query: " << cc->query << " "; start = cc->pos; Cursor *c = cc->c.get(); c->checkLocation(); while ( 1 ) { if ( !c->ok() ) { if ( c->tailable() ) { if ( c->advance() ) { continue; } break; } p.release(); bool ok = ClientCursor::erase(cursorid); assert(ok); cursorid = 0; cc = 0; break; } if ( !cc->matcher->matches(c->currKey(), c->currLoc() ) ) { } else { //out() << "matches " << c->currLoc().toString() << '\n'; if( c->getsetdup(c->currLoc()) ) { //out() << " but it's a dup \n"; } else { BSONObj js = c->current(); fillQueryResultFromObj(b, cc->fields.get(), js); n++; if ( (ntoreturn>0 && (n >= ntoreturn || b.len() > MaxBytesToReturnToClientAtOnce)) || (ntoreturn==0 && b.len()>1*1024*1024) ) { c->advance(); cc->pos += n; //cc->updateLocation(); break; } } } c->advance(); } if ( cc ) { cc->updateLocation(); cc->mayUpgradeStorage(); } } QueryResult *qr = (QueryResult *) b.buf(); qr->len = b.len(); qr->setOperation(opReply); qr->_resultFlags() = resultFlags; qr->cursorId = cursorid; qr->startingFrom = start; qr->nReturned = n; b.decouple(); return qr; } class CountOp : public QueryOp { public: CountOp( const BSONObj &spec ) : spec_( spec ), count_(), bc_() {} virtual void init() { query_ = spec_.getObjectField( "query" ); c_ = qp().newCursor(); _matcher.reset( new CoveredIndexMatcher( query_, c_->indexKeyPattern() ) ); if ( qp().exactKeyMatch() && ! _matcher->needRecord() ) { query_ = qp().simplifiedQuery( qp().indexKey() ); bc_ = dynamic_cast< BtreeCursor* >( c_.get() ); bc_->forgetEndKey(); } skip_ = spec_["skip"].numberLong(); limit_ = spec_["limit"].numberLong(); } virtual void next() { if ( !c_->ok() ) { setComplete(); return; } if ( bc_ ) { if ( firstMatch_.isEmpty() ) { firstMatch_ = bc_->currKeyNode().key; // if not match if ( query_.woCompare( firstMatch_, BSONObj(), false ) ) { setComplete(); return; } _gotOne(); } else { if ( !firstMatch_.woEqual( bc_->currKeyNode().key ) ) { setComplete(); return; } _gotOne(); } } else { if ( !_matcher->matches(c_->currKey(), c_->currLoc() ) ) { } else if( !c_->getsetdup(c_->currLoc()) ) { _gotOne(); } } c_->advance(); } virtual QueryOp *clone() const { return new CountOp( spec_ ); } long long count() const { return count_; } virtual bool mayRecordPlan() const { return true; } private: void _gotOne(){ if ( skip_ ){ skip_--; return; } if ( limit_ > 0 && count_ >= limit_ ){ setComplete(); return; } count_++; } BSONObj spec_; long long count_; long long skip_; long long limit_; auto_ptr< Cursor > c_; BSONObj query_; BtreeCursor *bc_; auto_ptr< CoveredIndexMatcher > _matcher; BSONObj firstMatch_; }; /* { count: "collectionname"[, query: ] } returns -1 on ns does not exist error. */ long long runCount( const char *ns, const BSONObj &cmd, string &err ) { NamespaceDetails *d = nsdetails( ns ); if ( !d ) { err = "ns missing"; return -1; } BSONObj query = cmd.getObjectField("query"); // count of all objects if ( query.isEmpty() ){ long long num = d->nrecords; num = num - cmd["skip"].numberLong(); if ( num < 0 ) { num = 0; } if ( cmd["limit"].isNumber() ){ long long limit = cmd["limit"].numberLong(); if ( limit < num ){ num = limit; } } return num; } QueryPlanSet qps( ns, query, BSONObj() ); CountOp original( cmd ); shared_ptr< CountOp > res = qps.runOp( original ); if ( !res->complete() ) { log() << "Count with ns: " << ns << " and query: " << query << " failed with exception: " << res->exceptionMessage() << endl; return 0; } return res->count(); } int _findingStartInitialTimeout = 5; // configurable for testing // Implements database 'query' requests using the query optimizer's QueryOp interface class UserQueryOp : public QueryOp { public: enum FindingStartMode { Initial, FindExtent, InExtent }; UserQueryOp( const ParsedQuery& pq ) : //int ntoskip, int ntoreturn, const BSONObj &order, bool wantMore, // bool explain, FieldMatcher *filter, int queryOptions ) : _buf( 32768 ) , // TODO be smarter here _pq( pq ) , _ntoskip( pq.getSkip() ) , _nscanned(0), _n(0), _inMemSort(false), _saveClientCursor(false), _findingStart( pq.hasOption( QueryOption_OplogReplay) ) , _findingStartCursor(0), _findingStartTimer(0), _findingStartMode() {} void setupMatcher() { if ( ! _c.get() || _c->useMatcher() ) _matcher.reset(new CoveredIndexMatcher( qp().query() , qp().indexKey())); else _matcher.reset(new CoveredIndexMatcher( BSONObj() , qp().indexKey())); } virtual void init() { _buf.skip( sizeof( QueryResult ) ); if ( _findingStart ) { // Use a ClientCursor here so we can release db mutex while scanning // oplog (can take quite a while with large oplogs). auto_ptr c = qp().newReverseCursor(); _findingStartCursor = new ClientCursor(c, qp().ns(), false); _findingStartTimer.reset(); _findingStartMode = Initial; BSONElement tsElt = qp().query()[ "ts" ]; massert( 13044, "no ts field in query", !tsElt.eoo() ); BSONObjBuilder b; b.append( tsElt ); BSONObj tsQuery = b.obj(); _matcher.reset(new CoveredIndexMatcher(tsQuery, qp().indexKey())); } else { _c = qp().newCursor( DiskLoc() , _pq.getNumToReturn() + _pq.getSkip() ); setupMatcher(); } if ( qp().scanAndOrderRequired() ) { _inMemSort = true; _so.reset( new ScanAndOrder( _pq.getSkip() , _pq.getNumToReturn() , _pq.getOrder() ) ); } } DiskLoc startLoc( const DiskLoc &rec ) { Extent *e = rec.rec()->myExtent( rec ); if ( e->myLoc != qp().nsd()->capExtent ) return e->firstRecord; // Likely we are on the fresh side of capExtent, so return first fresh record. // If we are on the stale side of capExtent, then the collection is small and it // doesn't matter if we start the extent scan with capFirstNewRecord. return qp().nsd()->capFirstNewRecord; } DiskLoc prevLoc( const DiskLoc &rec ) { Extent *e = rec.rec()->myExtent( rec ); if ( e->xprev.isNull() ) e = qp().nsd()->lastExtent.ext(); else e = e->xprev.ext(); if ( e->myLoc != qp().nsd()->capExtent ) return e->firstRecord; return DiskLoc(); // reached beginning of collection } void createClientCursor( const DiskLoc &startLoc = DiskLoc() ) { auto_ptr c = qp().newCursor( startLoc ); _findingStartCursor = new ClientCursor(c, qp().ns(), false); } void maybeRelease() { RARELY { CursorId id = _findingStartCursor->cursorid; _findingStartCursor->updateLocation(); { dbtemprelease t; } _findingStartCursor = ClientCursor::find( id, false ); } } virtual void next() { if ( _findingStart ) { if ( !_findingStartCursor || !_findingStartCursor->c->ok() ) { _findingStart = false; _c = qp().newCursor(); // on error, start from beginning setupMatcher(); return; } switch( _findingStartMode ) { case Initial: { if ( !_matcher->matches( _findingStartCursor->c->currKey(), _findingStartCursor->c->currLoc() ) ) { _findingStart = false; // found first record out of query range, so scan normally _c = qp().newCursor( _findingStartCursor->c->currLoc() ); setupMatcher(); return; } _findingStartCursor->c->advance(); RARELY { if ( _findingStartTimer.seconds() >= _findingStartInitialTimeout ) { createClientCursor( startLoc( _findingStartCursor->c->currLoc() ) ); _findingStartMode = FindExtent; return; } } maybeRelease(); return; } case FindExtent: { if ( !_matcher->matches( _findingStartCursor->c->currKey(), _findingStartCursor->c->currLoc() ) ) { _findingStartMode = InExtent; return; } DiskLoc prev = prevLoc( _findingStartCursor->c->currLoc() ); if ( prev.isNull() ) { // hit beginning, so start scanning from here createClientCursor(); _findingStartMode = InExtent; return; } // There might be a more efficient implementation than creating new cursor & client cursor each time, // not worrying about that for now createClientCursor( prev ); maybeRelease(); return; } case InExtent: { if ( _matcher->matches( _findingStartCursor->c->currKey(), _findingStartCursor->c->currLoc() ) ) { _findingStart = false; // found first record in query range, so scan normally _c = qp().newCursor( _findingStartCursor->c->currLoc() ); setupMatcher(); return; } _findingStartCursor->c->advance(); maybeRelease(); return; } default: { massert( 12600, "invalid _findingStartMode", false ); } } } if ( _findingStartCursor ) { ClientCursor::erase( _findingStartCursor->cursorid ); _findingStartCursor = 0; } if ( !_c->ok() ) { finish(); return; } bool mayCreateCursor1 = _pq.wantMore() && ! _inMemSort && _pq.getNumToReturn() != 1 && useCursors; if( 0 ) { cout << "SCANNING this: " << this << " key: " << _c->currKey() << " obj: " << _c->current() << endl; } _nscanned++; if ( !_matcher->matches(_c->currKey(), _c->currLoc() ) ) { // not a match, continue onward } else { DiskLoc cl = _c->currLoc(); if( !_c->getsetdup(cl) ) { // got a match. BSONObj js = _pq.returnKey() ? _c->currKey() : _c->current(); assert( js.objsize() >= 0 ); //defensive for segfaults if ( _inMemSort ) { // note: no cursors for non-indexed, ordered results. results must be fairly small. _so->add(js); } else if ( _ntoskip > 0 ) { _ntoskip--; } else { if ( _pq.isExplain() ) { _n++; if ( _n >= _pq.getNumToReturn() && !_pq.wantMore() ) { // .limit() was used, show just that much. finish(); return; } } else { if ( _pq.returnKey() ){ BSONObjBuilder bb( _buf ); bb.appendKeys( _c->indexKeyPattern() , js ); bb.done(); } else { fillQueryResultFromObj( _buf , _pq.getFields() , js ); } _n++; if ( _pq.enoughForFirstBatch( _n , _buf.len() ) ){ /* if only 1 requested, no cursor saved for efficiency...we assume it is findOne() */ if ( mayCreateCursor1 ) { _c->advance(); if ( _c->ok() ) { // more...so save a cursor _saveClientCursor = true; } } finish(); return; } } } } } _c->advance(); } void finish() { if ( _pq.isExplain() ) { _n = _inMemSort ? _so->size() : _n; } else if ( _inMemSort ) { _so->fill( _buf, _pq.getFields() , _n ); } if ( _pq.hasOption( QueryOption_CursorTailable ) && _pq.getNumToReturn() != 1 ) _c->setTailable(); // If the tailing request succeeded. if ( _c->tailable() ) _saveClientCursor = true; setComplete(); } virtual bool mayRecordPlan() const { return _pq.getNumToReturn() != 1; } virtual QueryOp *clone() const { return new UserQueryOp( _pq ); } BufBuilder &builder() { return _buf; } bool scanAndOrderRequired() const { return _inMemSort; } auto_ptr< Cursor > cursor() { return _c; } auto_ptr< CoveredIndexMatcher > matcher() { return _matcher; } int n() const { return _n; } long long nscanned() const { return _nscanned; } bool saveClientCursor() const { return _saveClientCursor; } private: BufBuilder _buf; const ParsedQuery& _pq; long long _ntoskip; long long _nscanned; int _n; // found so far bool _inMemSort; auto_ptr< ScanAndOrder > _so; auto_ptr< Cursor > _c; auto_ptr< CoveredIndexMatcher > _matcher; bool _saveClientCursor; bool _findingStart; ClientCursor * _findingStartCursor; Timer _findingStartTimer; FindingStartMode _findingStartMode; }; /* run a query -- includes checking for and running a Command */ auto_ptr< QueryResult > runQuery(Message& m, QueryMessage& q, CurOp& curop ) { StringBuilder& ss = curop.debug().str; ParsedQuery pq( q ); const char *ns = q.ns; int ntoskip = q.ntoskip; BSONObj jsobj = q.query; int queryOptions = q.queryOptions; BSONObj snapshotHint; if( logLevel >= 2 ) log() << "runQuery: " << ns << jsobj << endl; long long nscanned = 0; ss << ns << " ntoreturn:" << pq.getNumToReturn(); curop.setQuery(jsobj); BSONObjBuilder cmdResBuf; long long cursorid = 0; auto_ptr< QueryResult > qr; int n = 0; Client& c = cc(); if ( pq.couldBeCommand() ){ BufBuilder bb; bb.skip(sizeof(QueryResult)); if ( runCommands(ns, jsobj, curop, bb, cmdResBuf, false, queryOptions) ) { ss << " command "; curop.markCommand(); n = 1; qr.reset( (QueryResult *) bb.buf() ); bb.decouple(); qr->setResultFlagsToOk(); qr->len = bb.len(); ss << " reslen:" << bb.len(); // qr->channel = 0; qr->setOperation(opReply); qr->cursorId = cursorid; qr->startingFrom = 0; qr->nReturned = n; } return qr; } // regular query mongolock lk(false); // read lock Client::Context ctx( ns , dbpath , &lk ); /* we allow queries to SimpleSlave's -- but not to the slave (nonmaster) member of a replica pair so that queries to a pair are realtime consistent as much as possible. use setSlaveOk() to query the nonmaster member of a replica pair. */ uassert( 10107 , "not master" , isMaster() || pq.hasOption( QueryOption_SlaveOk ) || replSettings.slave == SimpleSlave ); BSONElement hint = useHints ? pq.getHint() : BSONElement(); bool explain = pq.isExplain(); bool snapshot = pq.isSnapshot(); BSONObj query = pq.getFilter(); BSONObj order = pq.getOrder(); if( snapshot ) { NamespaceDetails *d = nsdetails(ns); if ( d ){ int i = d->findIdIndex(); if( i < 0 ) { if ( strstr( ns , ".system." ) == 0 ) log() << "warning: no _id index on $snapshot query, ns:" << ns << endl; } else { /* [dm] the name of an _id index tends to vary, so we build the hint the hard way here. probably need a better way to specify "use the _id index" as a hint. if someone is in the query optimizer please fix this then! */ BSONObjBuilder b; b.append("$hint", d->idx(i).indexName()); snapshotHint = b.obj(); hint = snapshotHint.firstElement(); } } } /* The ElemIter will not be happy if this isn't really an object. So throw exception here when that is true. (Which may indicate bad data from client.) */ if ( query.objsize() == 0 ) { out() << "Bad query object?\n jsobj:"; out() << jsobj.toString() << "\n query:"; out() << query.toString() << endl; uassert( 10110 , "bad query object", false); } if ( isSimpleIdQuery( query ) ){ nscanned = 1; bool nsFound = false; bool indexFound = false; BSONObj resObject; bool found = Helpers::findById( c, ns , query , resObject , &nsFound , &indexFound ); if ( nsFound == false || indexFound == true ){ BufBuilder bb(sizeof(QueryResult)+resObject.objsize()+32); bb.skip(sizeof(QueryResult)); ss << " idhack "; if ( found ){ n = 1; fillQueryResultFromObj( bb , pq.getFields() , resObject ); } qr.reset( (QueryResult *) bb.buf() ); bb.decouple(); qr->setResultFlagsToOk(); qr->len = bb.len(); ss << " reslen:" << bb.len(); qr->setOperation(opReply); qr->cursorId = cursorid; qr->startingFrom = 0; qr->nReturned = n; return qr; } } // regular, not QO bypass query BSONObj oldPlan; if ( explain && ! pq.hasIndexSpecifier() ){ QueryPlanSet qps( ns, query, order ); if ( qps.usingPrerecordedPlan() ) oldPlan = qps.explain(); } QueryPlanSet qps( ns, query, order, &hint, !explain, pq.getMin(), pq.getMax() ); UserQueryOp original( pq ); shared_ptr< UserQueryOp > o = qps.runOp( original ); UserQueryOp &dqo = *o; massert( 10362 , dqo.exceptionMessage(), dqo.complete() ); n = dqo.n(); nscanned = dqo.nscanned(); if ( dqo.scanAndOrderRequired() ) ss << " scanAndOrder "; auto_ptr cursor = dqo.cursor(); log( 5 ) << " used cursor: " << cursor.get() << endl; if ( dqo.saveClientCursor() ) { // the clientcursor now owns the Cursor* and 'c' is released: ClientCursor *cc = new ClientCursor(cursor, ns, !(queryOptions & QueryOption_NoCursorTimeout)); cursorid = cc->cursorid; cc->query = jsobj.getOwned(); DEV out() << " query has more, cursorid: " << cursorid << endl; cc->matcher = dqo.matcher(); cc->pos = n; cc->fields = pq.getFieldPtr(); cc->originalMessage = m; cc->updateLocation(); if ( !cc->c->ok() && cc->c->tailable() ) { DEV out() << " query has no more but tailable, cursorid: " << cursorid << endl; } else { DEV out() << " query has more, cursorid: " << cursorid << endl; } } if ( explain ) { BSONObjBuilder builder; builder.append("cursor", cursor->toString()); builder.append("startKey", cursor->prettyStartKey()); builder.append("endKey", cursor->prettyEndKey()); builder.append("nscanned", double( dqo.nscanned() ) ); builder.append("n", n); if ( dqo.scanAndOrderRequired() ) builder.append("scanAndOrder", true); builder.append("millis", curop.elapsedMillis()); if ( !oldPlan.isEmpty() ) builder.append( "oldPlan", oldPlan.firstElement().embeddedObject().firstElement().embeddedObject() ); if ( hint.eoo() ) builder.appendElements(qps.explain()); BSONObj obj = builder.done(); fillQueryResultFromObj(dqo.builder(), 0, obj); n = 1; } qr.reset( (QueryResult *) dqo.builder().buf() ); dqo.builder().decouple(); qr->cursorId = cursorid; qr->setResultFlagsToOk(); qr->len = dqo.builder().len(); ss << " reslen:" << qr->len; qr->setOperation(opReply); qr->startingFrom = 0; qr->nReturned = n; int duration = curop.elapsedMillis(); bool dbprofile = curop.shouldDBProfile( duration ); if ( dbprofile || duration >= cmdLine.slowMS ) { ss << " nscanned:" << nscanned << ' '; if ( ntoskip ) ss << " ntoskip:" << ntoskip; if ( dbprofile ) ss << " \nquery: "; ss << jsobj << ' '; } ss << " nreturned:" << n; return qr; } } // namespace mongo