summaryrefslogtreecommitdiff
path: root/db/query.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'db/query.cpp')
-rw-r--r--db/query.cpp921
1 files changed, 921 insertions, 0 deletions
diff --git a/db/query.cpp b/db/query.cpp
new file mode 100644
index 00000000000..9c826092c05
--- /dev/null
+++ b/db/query.cpp
@@ -0,0 +1,921 @@
+// 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "stdafx.h"
+#include "query.h"
+#include "pdfile.h"
+#include "jsobjmanipulator.h"
+#include "../util/builder.h"
+#include <time.h>
+#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<BSONObj,DiskLoc,5> 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. <database>.<collection>
+ pattern: the "where" clause / criteria
+ justOne: stop after 1 match
+ */
+ int 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 );
+
+ int 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<ClientCursor> cc;
+ cc.reset( new ClientCursor() );
+ cc->c = creal;
+ cc->ns = ns;
+ cc->noTimeout();
+ 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\":ObjectId( \"000000000000000000000000\" )}");
+ BSONObj empty_obj = fromjson("{}");
+
+ /* This is for languages whose "objects" are not well ordered (JSON is well ordered).
+ [ { a : ... } , { b : ... } ] -> { a : ..., b : ... }
+ */
+ inline BSONObj transformOrderFromArrayFormat(BSONObj order) {
+ /* note: this is slow, but that is ok as order will have very few pieces */
+ BSONObjBuilder b;
+ char p[2] = "0";
+
+ while ( 1 ) {
+ BSONObj j = order.getObjectField(p);
+ if ( j.isEmpty() )
+ break;
+ BSONElement e = j.firstElement();
+ uassert( 10102 , "bad order array", !e.eoo());
+ uassert( 10103 , "bad order array [2]", e.isNumber());
+ b.append(e);
+ (*p)++;
+ uassert( 10104 , "too many ordering elements", *p <= '9');
+ }
+
+ return b.obj();
+ }
+
+
+ //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->filter.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: <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();
+ }
+
+ // Implements database 'query' requests using the query optimizer's QueryOp interface
+ class UserQueryOp : public QueryOp {
+ public:
+ UserQueryOp( int ntoskip, int ntoreturn, const BSONObj &order, bool wantMore,
+ bool explain, FieldMatcher *filter, int queryOptions ) :
+ b_( 32768 ),
+ ntoskip_( ntoskip ),
+ ntoreturn_( ntoreturn ),
+ order_( order ),
+ wantMore_( wantMore ),
+ explain_( explain ),
+ filter_( filter ),
+ ordering_(),
+ nscanned_(),
+ queryOptions_( queryOptions ),
+ n_(),
+ soSize_(),
+ saveClientCursor_(),
+ findingStart_( (queryOptions & QueryOption_OplogReplay) != 0 ),
+ findingStartCursor_()
+ {
+ uassert( 10105 , "bad skip value in query", ntoskip >= 0);
+ }
+
+ virtual void init() {
+ b_.skip( sizeof( QueryResult ) );
+
+ // findingStart mode is used to find the first operation of interest when
+ // we are scanning through a repl log. For efficiency in the common case,
+ // where the first operation of interest is closer to the tail than the head,
+ // we start from the tail of the log and work backwards until we find the
+ // first operation of interest. Then we scan forward from that first operation,
+ // actually returning results to the client. During the findingStart phase,
+ // we release the db mutex occasionally to avoid blocking the db process for
+ // an extended period of time.
+ if ( findingStart_ ) {
+ // Use a ClientCursor here so we can release db mutex while scanning
+ // oplog (can take quite a while with large oplogs).
+ findingStartCursor_ = new ClientCursor();
+ findingStartCursor_->noTimeout();
+ findingStartCursor_->c = qp().newReverseCursor();
+ findingStartCursor_->ns = qp().ns();
+ } else {
+ c_ = qp().newCursor();
+ }
+
+ matcher_.reset(new CoveredIndexMatcher(qp().query(), qp().indexKey()));
+
+ if ( qp().scanAndOrderRequired() ) {
+ ordering_ = true;
+ so_.reset( new ScanAndOrder( ntoskip_, ntoreturn_, order_ ) );
+ wantMore_ = false;
+ }
+ }
+ virtual void next() {
+ if ( findingStart_ ) {
+ if ( !findingStartCursor_ || !findingStartCursor_->c->ok() ) {
+ findingStart_ = false;
+ c_ = qp().newCursor();
+ } else if ( !matcher_->matches( findingStartCursor_->c->currKey(), findingStartCursor_->c->currLoc() ) ) {
+ findingStart_ = false;
+ c_ = qp().newCursor( findingStartCursor_->c->currLoc() );
+ } else {
+ findingStartCursor_->c->advance();
+ RARELY {
+ CursorId id = findingStartCursor_->cursorid;
+ findingStartCursor_->updateLocation();
+ {
+ dbtemprelease t;
+ }
+ findingStartCursor_ = ClientCursor::find( id, false );
+ }
+ return;
+ }
+ }
+
+ if ( findingStartCursor_ ) {
+ ClientCursor::erase( findingStartCursor_->cursorid );
+ findingStartCursor_ = 0;
+ }
+
+ if ( !c_->ok() ) {
+ finish();
+ return;
+ }
+
+ bool mayCreateCursor1 = wantMore_ && ntoreturn_ != 1 && useCursors;
+
+ if( 0 ) {
+ BSONObj js = c_->current();
+ cout << "SCANNING " << js << endl;
+ }
+
+ nscanned_++;
+ if ( !matcher_->matches(c_->currKey(), c_->currLoc() ) ) {
+ ;
+ }
+ else {
+ DiskLoc cl = c_->currLoc();
+ if( !c_->getsetdup(cl) ) {
+ BSONObj js = c_->current();
+ // got a match.
+ assert( js.objsize() >= 0 ); //defensive for segfaults
+ if ( ordering_ ) {
+ // note: no cursors for non-indexed, ordered results. results must be fairly small.
+ so_->add(js);
+ }
+ else if ( ntoskip_ > 0 ) {
+ ntoskip_--;
+ } else {
+ if ( explain_ ) {
+ n_++;
+ if ( n_ >= ntoreturn_ && !wantMore_ ) {
+ // .limit() was used, show just that much.
+ finish();
+ return;
+ }
+ }
+ else {
+ fillQueryResultFromObj(b_, filter_, js);
+ n_++;
+ if ( (ntoreturn_>0 && (n_ >= ntoreturn_ || b_.len() > MaxBytesToReturnToClientAtOnce)) ||
+ (ntoreturn_==0 && (b_.len()>1*1024*1024 || n_>=101)) ) {
+ /* if ntoreturn is zero, we return up to 101 objects. on the subsequent getmore, there
+ is only a size limit. The idea is that on a find() where one doesn't use much results,
+ we don't return much, but once getmore kicks in, we start pushing significant quantities.
+
+ The n limit (vs. size) is important when someone fetches only one small field from big
+ objects, which causes massive scanning server-side.
+ */
+ /* 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 ( explain_ ) {
+ n_ = ordering_ ? so_->size() : n_;
+ } else if ( ordering_ ) {
+ so_->fill(b_, filter_, n_);
+ }
+ if ( mayCreateCursor2() ) {
+ c_->setTailable();
+ }
+ // If the tailing request succeeded.
+ if ( c_->tailable() ) {
+ saveClientCursor_ = true;
+ }
+ setComplete();
+ }
+ virtual bool mayRecordPlan() const { return ntoreturn_ != 1; }
+ virtual QueryOp *clone() const {
+ return new UserQueryOp( ntoskip_, ntoreturn_, order_, wantMore_, explain_, filter_, queryOptions_ );
+ }
+ BufBuilder &builder() { return b_; }
+ bool scanAndOrderRequired() const { return ordering_; }
+ 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_; }
+ bool mayCreateCursor2() const { return ( queryOptions_ & QueryOption_CursorTailable ) && ntoreturn_ != 1; }
+ private:
+ BufBuilder b_;
+ int ntoskip_;
+ int ntoreturn_;
+ BSONObj order_;
+ bool wantMore_;
+ bool explain_;
+ FieldMatcher *filter_;
+ bool ordering_;
+ auto_ptr< Cursor > c_;
+ long long nscanned_;
+ int queryOptions_;
+ auto_ptr< CoveredIndexMatcher > matcher_;
+ int n_;
+ int soSize_;
+ bool saveClientCursor_;
+ auto_ptr< ScanAndOrder > so_;
+ bool findingStart_;
+ ClientCursor * findingStartCursor_;
+ };
+
+ /* 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;
+ const char *ns = q.ns;
+ int ntoskip = q.ntoskip;
+ int _ntoreturn = q.ntoreturn;
+ BSONObj jsobj = q.query;
+ auto_ptr< FieldMatcher > filter = q.fields; // what fields to return (unspecified = full object)
+ int queryOptions = q.queryOptions;
+ BSONObj snapshotHint;
+
+ Timer t;
+ if( logLevel >= 2 )
+ log() << "runQuery: " << ns << jsobj << endl;
+
+ long long nscanned = 0;
+ bool wantMore = true;
+ int ntoreturn = _ntoreturn;
+ 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.
+ */
+ ntoreturn = -_ntoreturn;
+ wantMore = false;
+ }
+ ss << "query " << ns << " ntoreturn:" << ntoreturn;
+ curop.setQuery(jsobj);
+
+ BufBuilder bb;
+ BSONObjBuilder cmdResBuf;
+ long long cursorid = 0;
+
+ bb.skip(sizeof(QueryResult));
+
+ auto_ptr< QueryResult > qr;
+ int n = 0;
+
+ Client& c = cc();
+ /* we assume you are using findOne() for running a cmd... */
+ if ( ntoreturn == 1 && runCommands(ns, jsobj, curop, bb, cmdResBuf, false, queryOptions) ) {
+ 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;
+ }
+ else {
+ /* regular query */
+
+ AuthenticationInfo *ai = currentClient.get()->ai;
+ uassert( 10106 , "unauthorized", ai->isAuthorized(c.database()->name.c_str()));
+
+ /* 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() || (queryOptions & QueryOption_SlaveOk) || slave == SimpleSlave );
+
+ BSONElement hint;
+ BSONObj min;
+ BSONObj max;
+ bool explain = false;
+ bool _gotquery = false;
+ bool snapshot = false;
+ BSONObj query;
+ {
+ BSONElement e = jsobj.findElement("$query");
+ if ( e.eoo() )
+ e = jsobj.findElement("query");
+ if ( !e.eoo() && (e.type() == Object || e.type() == Array) ) {
+ query = e.embeddedObject();
+ _gotquery = true;
+ }
+ }
+ BSONObj order;
+ {
+ BSONElement e = jsobj.findElement("$orderby");
+ if ( e.eoo() )
+ e = jsobj.findElement("orderby");
+ if ( !e.eoo() ) {
+ order = e.embeddedObjectUserCheck();
+ if ( e.type() == Array )
+ order = transformOrderFromArrayFormat(order);
+ }
+ }
+ if ( !_gotquery && order.isEmpty() )
+ query = jsobj;
+ else {
+ explain = jsobj.getBoolField("$explain");
+ if ( useHints )
+ hint = jsobj.getField("$hint");
+ min = jsobj.getObjectField("$min");
+ max = jsobj.getObjectField("$max");
+ BSONElement e = jsobj.getField("$snapshot");
+ snapshot = !e.eoo() && e.trueValue();
+ if( snapshot ) {
+ uassert( 12001 , "E12001 can't sort with $snapshot", order.isEmpty());
+ uassert( 12002 , "E12002 can't use hint with $snapshot", hint.eoo());
+ 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);
+ }
+
+ bool idHackWorked = false;
+
+ if ( strcmp( query.firstElement().fieldName() , "_id" ) == 0 && query.nFields() == 1 && query.firstElement().isSimpleType() ){
+ 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 ){
+ idHackWorked = true;
+ if ( found ){
+ n = 1;
+ fillQueryResultFromObj( bb , filter.get() , 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;
+ }
+ }
+
+ if ( ! idHackWorked ){ // non-simple _id lookup
+ BSONObj oldPlan;
+ if ( explain && hint.eoo() && min.isEmpty() && max.isEmpty() ) {
+ QueryPlanSet qps( ns, query, order );
+ if ( qps.usingPrerecordedPlan() )
+ oldPlan = qps.explain();
+ }
+ QueryPlanSet qps( ns, query, order, &hint, !explain, min, max );
+ UserQueryOp original( ntoskip, ntoreturn, order, wantMore, explain, filter.get(), queryOptions );
+ 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 > c = dqo.cursor();
+ log( 5 ) << " used cursor: " << c.get() << endl;
+ if ( dqo.saveClientCursor() ) {
+ ClientCursor *cc = new ClientCursor();
+ if ( queryOptions & QueryOption_NoCursorTimeout )
+ cc->noTimeout();
+ cc->c = c;
+ cursorid = cc->cursorid;
+ cc->query = jsobj.getOwned();
+ DEV out() << " query has more, cursorid: " << cursorid << endl;
+ cc->matcher = dqo.matcher();
+ cc->ns = ns;
+ cc->pos = n;
+ cc->filter = filter;
+ 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", c->toString());
+ builder.append("startKey", c->prettyStartKey());
+ builder.append("endKey", c->prettyEndKey());
+ builder.append("nscanned", double( dqo.nscanned() ) );
+ builder.append("n", n);
+ if ( dqo.scanAndOrderRequired() )
+ builder.append("scanAndOrder", true);
+ builder.append("millis", t.millis());
+ 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 = t.millis();
+ Database *database = c.database();
+ if ( (database && database->profile) || duration >= 100 ) {
+ ss << " nscanned:" << nscanned << ' ';
+ if ( ntoskip )
+ ss << " ntoskip:" << ntoskip;
+ if ( database && database->profile )
+ ss << " \nquery: ";
+ ss << jsobj << ' ';
+ }
+ ss << " nreturned:" << n;
+ return qr;
+ }
+
+} // namespace mongo