diff --git a/db/btreecursor.cpp b/db/btreecursor.cpp index a6a0ad9a41a..934440098d4 100644 --- a/db/btreecursor.cpp +++ b/db/btreecursor.cpp @@ -78,7 +78,7 @@ namespace mongo { indexDetails.head.btree()->dump(); } } - + bucket = indexDetails.head.btree()-> locate(indexDetails, indexDetails.head, startKey, order, keyOfs, found, direction > 0 ? minDiskLoc : maxDiskLoc, direction); diff --git a/db/instance.cpp b/db/instance.cpp index 061c459ce5f..401f4964eb5 100644 --- a/db/instance.cpp +++ b/db/instance.cpp @@ -348,6 +348,7 @@ namespace mongo { setClient( q.ns ); strncpy(currentOp.ns, q.ns, Namespace::MaxNsLen); +// msgdata = runQuery(m, ss ).release(); msgdata = runQuery(m, q.ns, q.ntoskip, q.ntoreturn, q.query, q.fields, ss, q.queryOptions); } catch ( AssertionException& e ) { diff --git a/db/matcher.cpp b/db/matcher.cpp index 8f8cad947da..bbd38f140f9 100644 --- a/db/matcher.cpp +++ b/db/matcher.cpp @@ -447,6 +447,7 @@ namespace mongo { return false; // didn't compile } #if !defined(NOJNI) + /**if( 1 || jsobj.objsize() < 200 || where->fullObject ) */ { if ( where->jsScope ) { diff --git a/db/query.cpp b/db/query.cpp index d93f61ae072..72b5f910bb3 100644 --- a/db/query.cpp +++ b/db/query.cpp @@ -526,37 +526,37 @@ namespace mongo { BSONObj empty_obj = fromjson("{}"); /* { count: "collectionname"[, query: ] } - returns -1 on ns does not exist error. - -2 on other errors - */ - int runCount(const char *ns, BSONObj& cmd, string& err) { + returns -1 on ns does not exist error. + -2 on other errors + */ + int runCount(const char *ns, const BSONObj& cmd, string& err) { NamespaceDetails *d = nsdetails(ns); if ( d == 0 ) { err = "ns does not exist"; return -1; } - + BSONObj query = cmd.getObjectField("query"); set< string > fields; cmd.getObjectField("fields").getFieldNames( fields ); - + if ( query.isEmpty() && fields.empty() ) { // count of all objects return (int) d->nrecords; } - + auto_ptr c; - + bool simpleKeyToMatch = false; c = getIndexCursor(ns, query, empty_obj, &simpleKeyToMatch); - + if ( c.get() ) { // TODO We could check if all fields in the key are in 'fields' if ( simpleKeyToMatch && fields.empty() ) { /* Here we only look at the btree keys to determine if a match, instead of looking - into the records, which would be much slower. - */ + into the records, which would be much slower. + */ int count = 0; BtreeCursor *bc = dynamic_cast(c.get()); if ( c->ok() && !query.woCompare( bc->currKeyNode().key, BSONObj(), false ) ) { @@ -573,7 +573,7 @@ namespace mongo { } else { c = findTableScan(ns, empty_obj); } - + int count = 0; auto_ptr matcher(new JSMatcher(query, c->indexKeyPattern())); while ( c->ok() ) { @@ -595,7 +595,7 @@ namespace mongo { c->advance(); } return count; - } + } /* This is for languages whose "objects" are not well ordered (JSON is well ordered). [ { a : ... } , { b : ... } ] -> { a : ..., b : ... } @@ -990,18 +990,30 @@ namespace mongo { int count_; }; + /* { count: "collectionname"[, query: ] } + returns -1 on ns does not exist error. + */ int doCount( const char *ns, const BSONObj &cmd, string &err ) { + NamespaceDetails *d = nsdetails( ns ); + if ( !d ) { + err = "ns missing"; + return -1; + } BSONObj query = cmd.getObjectField("query"); BSONObj fields = cmd.getObjectField("fields"); // count of all objects if ( query.isEmpty() && fields.isEmpty() ) { - NamespaceDetails *d = nsdetails( ns ); - massert( "ns missing", d ); return d->nrecords; } QueryPlanSet qps( ns, query, emptyObj ); auto_ptr< CountOp > original( new CountOp( 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(); } @@ -1020,15 +1032,16 @@ namespace mongo { nscanned_(), queryOptions_( queryOptions ), n_(), - soSize_() {} + soSize_(), + saveClientCursor_() {} virtual void run( const QueryPlan &qp, const QueryAborter &qa ) { b_.skip( sizeof( QueryResult ) ); - auto_ptr< Cursor > c = qp.newCursor(); + c_ = qp.newCursor(); auto_ptr so; - auto_ptr matcher(new JSMatcher(qp.query(), c->indexKeyPattern())); + matcher_.reset(new JSMatcher(qp.query(), c_->indexKeyPattern())); if ( qp.scanAndOrderRequired() ) { ordering_ = true; @@ -1037,16 +1050,15 @@ namespace mongo { // scanAndOrder(b, c.get(), order, ntoreturn); } - bool saveCursor = false; - - while ( c->ok() ) { + while ( c_->ok() ) { qa.mayAbort(); - BSONObj js = c->current(); + BSONObj js = c_->current(); nscanned_++; bool deep; - if ( !matcher->matches(js, &deep) ) { + if ( !matcher_->matches(js, &deep) ) { } - else if ( !deep || !c->getsetdup(c->currLoc()) ) { // i.e., check for dups on deep items only + else if ( !deep || !c_->getsetdup(c_->currLoc()) ) { // i.e., check for dups on deep items only + out() << "c_: " << c_->toString() << " match: " << js << endl; // got a match. assert( js.objsize() >= 0 ); //defensive for segfaults if ( ordering_ ) { @@ -1077,10 +1089,10 @@ namespace mongo { /* if only 1 requested, no cursor saved for efficiency...we assume it is findOne() */ if ( wantMore_ && ntoreturn_ != 1 ) { if ( useCursors ) { - c->advance(); - if ( c->ok() ) { + c_->advance(); + if ( c_->ok() ) { // more...so save a cursor - saveCursor = true; + saveClientCursor_ = true; } } } @@ -1090,23 +1102,18 @@ namespace mongo { } } } - c->advance(); + c_->advance(); } // end while if ( explain_ ) { - soSize_ = so->size(); + n_ = ordering_ ? so->size() : n_; } else if ( ordering_ ) { // TODO Allow operation to abort during fill. so->fill(b_, &filter_, n_); } - else if ( !saveCursor && (queryOptions_ & Option_CursorTailable) && c->tailable() ) { - c->setAtTail(); - saveCursor = true; - } - - if ( saveCursor ) { - c_ = c; - matcher_ = matcher; + else if ( !saveClientCursor_ && (queryOptions_ & Option_CursorTailable) && c_->tailable() ) { + c_->setAtTail(); + saveClientCursor_ = true; } } virtual QueryOp *clone() const { @@ -1117,7 +1124,8 @@ namespace mongo { auto_ptr< Cursor > cursor() { return c_; } auto_ptr< JSMatcher > matcher() { return matcher_; } int n() const { return n_; } - int soSize() const { return soSize_; } + int nscanned() const { return nscanned_; } + bool saveClientCursor() const { return saveClientCursor_; } private: BufBuilder b_; int ntoskip_; @@ -1133,9 +1141,10 @@ namespace mongo { auto_ptr< JSMatcher > matcher_; int n_; int soSize_; + bool saveClientCursor_; }; - auto_ptr< QueryResult > doQuery(Message& m, stringstream& ss ) { + auto_ptr< QueryResult > runQuery(Message& m, stringstream& ss ) { DbMessage d( m ); QueryMessage q( d ); const char *ns = q.ns; @@ -1234,15 +1243,16 @@ namespace mongo { uassert("bad query object", false); } - QueryPlanSet qps( ns, query, order ); + QueryPlanSet qps( ns, query, order, &hint ); auto_ptr< DoQueryOp > original( new DoQueryOp( ntoskip, ntoreturn, order, wantMore, explain, *filter, queryOptions ) ); shared_ptr< DoQueryOp > o = qps.runOp( *original ); DoQueryOp &dqo = *o; + massert( dqo.exceptionMessage(), dqo.complete() ); n = dqo.n(); if ( dqo.scanAndOrderRequired() ) ss << " scanAndOrder "; auto_ptr< Cursor > c = dqo.cursor(); - if ( c.get() ) { + if ( dqo.saveClientCursor() ) { ClientCursor *cc = new ClientCursor(); cc->c = c; cursorid = cc->cursorid; @@ -1253,7 +1263,7 @@ namespace mongo { cc->filter = filter; cc->originalMessage = m; cc->updateLocation(); - if ( c->tailing() ) + if ( cc->c->tailing() ) DEV out() << " query has no more but tailable, cursorid: " << cursorid << endl; else DEV out() << " query has more, cursorid: " << cursorid << endl; @@ -1263,8 +1273,8 @@ namespace mongo { builder.append("cursor", c->toString()); builder.append("startKey", c->prettyStartKey()); builder.append("endKey", c->prettyEndKey()); - builder.append("nscanned", nscanned); - builder.append("n", dqo.scanAndOrderRequired() ? dqo.soSize() : n); + builder.append("nscanned", dqo.nscanned()); + builder.append("n", n); if ( dqo.scanAndOrderRequired() ) builder.append("scanAndOrder", true); builder.append("millis", t.millis()); diff --git a/db/query.h b/db/query.h index df951e07418..1b6a156be00 100644 --- a/db/query.h +++ b/db/query.h @@ -85,11 +85,11 @@ namespace mongo { class Cursor; auto_ptr getIndexCursor(const char *ns, const BSONObj& query, const BSONObj& order, bool *simpleKeyMatch = 0, bool *isSorted = 0, BSONElement *hint = 0); - int runCount(const char *ns, BSONObj& cmd, string& err); + int runCount(const char *ns, const BSONObj& cmd, string& err); // Using new query optimizer: - int doCount( const char *ns, const BSONObj &cmd, string &err ); - auto_ptr< QueryResult > doQuery(Message& m, stringstream& ss ); + int doCount(const char *ns, const BSONObj& cmd, string& err); + auto_ptr< QueryResult > runQuery(Message& m, stringstream& ss ); } // namespace mongo diff --git a/db/queryoptimizer.cpp b/db/queryoptimizer.cpp index cff5b8b5977..1f8d46fc610 100644 --- a/db/queryoptimizer.cpp +++ b/db/queryoptimizer.cpp @@ -79,7 +79,6 @@ namespace mongo { lower_ = other.lower_; for( vector< BSONObj >::const_iterator i = other.objData_.begin(); i != other.objData_.end(); ++i ) objData_.push_back( *i ); - massert( "Incompatible bounds", lower_.woCompare( upper_, false ) <= 0 ); return *this; } @@ -131,10 +130,11 @@ namespace mongo { scanAndOrderRequired_( true ), keyMatch_( false ), exactKeyMatch_( false ), - direction_( 0 ) { + direction_( 0 ), + unhelpful_( false ) { // full table scan case if ( !index_ ) { - if ( order_.isEmpty() ) + if ( order_.isEmpty() || !strcmp( order_.firstElement().fieldName(), "$natural" ) ) scanAndOrderRequired_ = false; return; } @@ -178,15 +178,17 @@ namespace mongo { bool stillOptimalIndexedQueryCount = true; set< string > orderFieldsUnindexed; order.getFieldNames( orderFieldsUnindexed ); - BSONObjBuilder lowKeyBuilder; - BSONObjBuilder highKeyBuilder; + BSONObjBuilder startKeyBuilder; + BSONObjBuilder endKeyBuilder; while( i.more() ) { BSONElement e = i.next(); if ( e.eoo() ) break; const FieldBound &fb = fbs.bound( e.fieldName() ); - lowKeyBuilder.appendAs( fb.lower(), "" ); - highKeyBuilder.appendAs( fb.upper(), "" ); + int number = (int) e.number(); // returns 0.0 if not numeric + bool forward = ( ( number >= 0 ? 1 : -1 ) * ( direction_ >= 0 ? 1 : -1 ) > 0 ); + startKeyBuilder.appendAs( forward ? fb.lower() : fb.upper(), "" ); + endKeyBuilder.appendAs( forward ? fb.upper() : fb.lower(), "" ); if ( fb.nontrivial() ) ++indexedQueryCount; if ( stillOptimalIndexedQueryCount ) { @@ -214,18 +216,21 @@ namespace mongo { if ( exactIndexedQueryCount == fbs.nNontrivialBounds() ) exactKeyMatch_ = true; } - BSONObj lowKey = lowKeyBuilder.obj(); - BSONObj highKey = highKeyBuilder.obj(); - startKey_ = ( direction_ >= 0 ) ? lowKey : highKey; - endKey_ = ( direction_ >= 0 ) ? highKey : lowKey; + startKey_ = startKeyBuilder.obj(); + endKey_ = endKeyBuilder.obj(); + if ( !keyMatch_ && + ( scanAndOrderRequired_ || order_.isEmpty() ) && + !fbs.bound( idxKey.firstElement().fieldName() ).nontrivial() ) + unhelpful_ = true; } auto_ptr< Cursor > QueryPlan::newCursor() const { + if ( !fbs_.matchPossible() ) + return auto_ptr< Cursor >( new BasicCursor( DiskLoc() ) ); if ( !index_ ) - return theDataFileMgr.findAll( fbs_.ns() ); - else - return auto_ptr< Cursor >( new BtreeCursor( *const_cast< IndexDetails* >( index_ ), startKey_, endKey_, direction_ >= 0 ? 1 : -1 ) ); - //TODO This constructor should really take a const ref to the index details. + return findTableScan( fbs_.ns(), order_, 0 ); + //TODO This constructor should really take a const ref to the index details. + return auto_ptr< Cursor >( new BtreeCursor( *const_cast< IndexDetails* >( index_ ), startKey_, endKey_, direction_ >= 0 ? 1 : -1 ) ); } BSONObj QueryPlan::indexKey() const { @@ -235,8 +240,12 @@ namespace mongo { QueryPlanSet::QueryPlanSet( const char *ns, const BSONObj &query, const BSONObj &order, const BSONElement *hint ) : fbs_( ns, query ) { NamespaceDetails *d = nsdetails( ns ); - assert( d ); - + if ( !d || !fbs_.matchPossible() ) { + // Table scan plan only + plans_.push_back( PlanPtr( new QueryPlan( fbs_, order ) ) ); + return; + } + if ( hint && !hint->eoo() ) { if( hint->type() == String ) { string hintstr = hint->valuestr(); @@ -250,6 +259,12 @@ namespace mongo { } else if( hint->type() == Object ) { BSONObj hintobj = hint->embeddedObject(); + uassert( "bad hint", !hintobj.isEmpty() ); + if ( !strcmp( hintobj.firstElement().fieldName(), "$natural" ) ) { + // Table scan plan + plans_.push_back( PlanPtr( new QueryPlan( fbs_, order ) ) ); + return; + } for (int i = 0; i < d->nIndexes; i++ ) { IndexDetails& ii = d->indexes[i]; if( ii.keyPattern().woCompare(hintobj) == 0 ) { @@ -268,14 +283,19 @@ namespace mongo { if ( fbs_.nNontrivialBounds() == 0 && order.isEmpty() ) return; + // Only table scan can give natural order. + if ( !order.isEmpty() && !strcmp( order.firstElement().fieldName(), "$natural" ) ) + return; + PlanSet plans; for( int i = 0; i < d->nIndexes; ++i ) { PlanPtr p( new QueryPlan( fbs_, order, &d->indexes[ i ] ) ); if ( p->optimal() ) { plans_.push_back( p ); return; + } else if ( !p->unhelpful() ) { + plans.push_back( p ); } - plans.push_back( p ); } for( PlanSet::iterator i = plans.begin(); i != plans.end(); ++i ) plans_.push_back( *i ); @@ -294,6 +314,14 @@ namespace mongo { } shared_ptr< QueryOp > QueryPlanSet::RunnerSet::run() { + massert( "no plans", plans_.plans_.size() > 0 ); + if ( plans_.plans_.size() == 1 ) { + shared_ptr< QueryOp > op( op_.clone() ); + Runner r( *plans_.plans_[ 0 ], *this, *op ); + r(); + return op; + } + boost::thread_group threads; vector< shared_ptr< QueryOp > > ops; for( PlanSet::iterator i = plans_.plans_.begin(); i != plans_.plans_.end(); ++i ) { @@ -303,10 +331,9 @@ namespace mongo { } threads.join_all(); for( vector< shared_ptr< QueryOp > >::iterator i = ops.begin(); i != ops.end(); ++i ) - if ( (*i)->done() ) + if ( (*i)->complete() ) return *i; - assert( false ); - return shared_ptr< QueryOp >(); + return ops[ 0 ]; } diff --git a/db/queryoptimizer.h b/db/queryoptimizer.h index 59bd1c695aa..e37f8211a72 100644 --- a/db/queryoptimizer.h +++ b/db/queryoptimizer.h @@ -67,6 +67,12 @@ namespace mongo { } const char *ns() const { return ns_; } BSONObj query() const { return query_; } + bool matchPossible() const { + for( map< string, FieldBound >::const_iterator i = bounds_.begin(); i != bounds_.end(); ++i ) + if ( i->second.lower().woCompare( i->second.upper(), false ) > 0 ) + return false; + return true; + } private: static FieldBound *trivialBound_; static FieldBound &trivialBound(); @@ -90,6 +96,9 @@ namespace mongo { bool keyMatch() const { return keyMatch_; } /* True if keyMatch() is true, and all matches will be equal according to woEqual() */ bool exactKeyMatch() const { return exactKeyMatch_; } + /* If true, the startKey and endKey are unhelpful, the index order doesn't match the + requested sort order, and keyMatch is false */ + bool unhelpful() const { return unhelpful_; } int direction() const { return direction_; } BSONObj startKey() const { return startKey_; } BSONObj endKey() const { return endKey_; } @@ -108,6 +117,7 @@ namespace mongo { int direction_; BSONObj startKey_; BSONObj endKey_; + bool unhelpful_; }; class QueryAborter { @@ -127,7 +137,7 @@ namespace mongo { // Inherit from this interface to implement a new query operation. class QueryOp { public: - QueryOp() : done_() {} + QueryOp() : complete_() {} virtual ~QueryOp() {} // Called by the runner, to execute this query operation using the // given query plan. The implementation should call qa.mayAbort() @@ -137,11 +147,15 @@ namespace mongo { // Return a copy of the inheriting class, which will be run with its own // query plan. virtual QueryOp *clone() const = 0; - bool done() const { return done_; } + bool complete() const { return complete_; } + string exceptionMessage() const { return exceptionMessage_; } // To be called by the runner only. - void setDone() { done_ = true; } + void setComplete() { complete_ = true; } + // To be called by the runner only. + void setExceptionMessage( const string &exceptionMessage ) { exceptionMessage_ = exceptionMessage; } private: - bool done_; + bool complete_; + string exceptionMessage_; }; class QueryPlanSet { @@ -173,13 +187,18 @@ namespace mongo { QueryAborter aborter( set_.firstDone_ ); op_.run( plan_, aborter ); set_.firstDone_ = true; - op_.setDone(); + op_.setComplete(); } catch ( const QueryAborter::AbortException & ) { + } catch ( const std::exception &e ) { + exceptionMessage_ = e.what(); + } catch ( ... ) { + exceptionMessage_ = "Caught unknown exception"; } } QueryPlan &plan_; RunnerSet &set_; QueryOp &op_; + string exceptionMessage_; }; FieldBoundSet fbs_; typedef boost::shared_ptr< QueryPlan > PlanPtr; diff --git a/dbtests/queryoptimizertests.cpp b/dbtests/queryoptimizertests.cpp index bd617e52455..5c775c573e9 100644 --- a/dbtests/queryoptimizertests.cpp +++ b/dbtests/queryoptimizertests.cpp @@ -51,16 +51,6 @@ namespace QueryOptimizerTests { } }; - class Bad { - public: - virtual ~Bad() {} - void run() { - ASSERT_EXCEPTION( FieldBoundSet f( "ns", query() ), AssertionException ); - } - protected: - virtual BSONObj query() = 0; - }; - class Empty : public Base { virtual BSONObj query() { return emptyObj; } }; @@ -115,8 +105,12 @@ namespace QueryOptimizerTests { virtual BSONObj query() { return BSON( "a" << 1 << "a" << GTE << 1 ); } }; - class EqGteInvalid : public Bad { - virtual BSONObj query() { return BSON( "a" << 1 << "a" << GTE << 2 ); } + class EqGteInvalid { + public: + void run() { + FieldBoundSet fbs( "ns", BSON( "a" << 1 << "a" << GTE << 2 ) ); + ASSERT( !fbs.matchPossible() ); + } }; class Regex : public Base { @@ -266,17 +260,17 @@ namespace QueryOptimizerTests { void run() { BSONObjBuilder b; b.appendMinKey( "" ); - b.appendMinKey( "" ); - BSONObj low = b.obj(); + b.appendMaxKey( "" ); + BSONObj start = b.obj(); BSONObjBuilder b2; b2.appendMaxKey( "" ); - b2.appendMaxKey( "" ); - BSONObj high = b2.obj(); + b2.appendMinKey( "" ); + BSONObj end = b2.obj(); QueryPlan p( FBS( emptyObj ), BSON( "a" << 1 << "b" << -1 ), INDEX( "a" << -1 << "b" << 1 ) ); ASSERT( !p.scanAndOrderRequired() ); ASSERT_EQUALS( -1, p.direction() ); - ASSERT( !p.endKey().woCompare( low ) ); - ASSERT( !p.startKey().woCompare( high ) ); + ASSERT( !p.startKey().woCompare( start ) ); + ASSERT( !p.endKey().woCompare( end ) ); QueryPlan p2( FBS( emptyObj ), BSON( "a" << -1 << "b" << -1 ), INDEX( "a" << 1 << "b" << 1 ) ); ASSERT( !p2.scanAndOrderRequired() ); ASSERT_EQUALS( -1, p2.direction() ); @@ -415,6 +409,8 @@ namespace QueryOptimizerTests { setClient( ns() ); string err; userCreateNS( ns(), emptyObj, err, false ); + AuthenticationInfo *ai = new AuthenticationInfo(); + authInfo.reset( ai ); } ~Base() { if ( !nsd() ) @@ -422,6 +418,20 @@ namespace QueryOptimizerTests { string s( ns() ); dropNS( s ); } + static void assembleRequest( const string &ns, BSONObj query, int nToReturn, int nToSkip, BSONObj *fieldsToReturn, int queryOptions, Message &toSend ) { + // see query.h for the protocol we are using here. + BufBuilder b; + int opts = queryOptions; + assert( (opts&Option_ALLMASK) == opts ); + b.append(opts); + b.append(ns.c_str()); + b.append(nToSkip); + b.append(nToReturn); + query.appendSelfToBufBuilder(b); + if ( fieldsToReturn ) + fieldsToReturn->appendSelfToBufBuilder(b); + toSend.setData(dbQuery, b.buf(), b.len()); + } protected: static const char *ns() { return "QueryPlanSetTests.coll"; } static NamespaceDetails *nsd() { return nsdetails( ns() ); } @@ -491,6 +501,28 @@ namespace QueryOptimizerTests { } }; + class NaturalHint : public Base { + public: + void run() { + Helpers::ensureIndex( ns(), BSON( "a" << 1 ), "a_1" ); + Helpers::ensureIndex( ns(), BSON( "b" << 1 ), "b_1" ); + BSONObj b = BSON( "hint" << BSON( "$natural" << 1 ) ); + BSONElement e = b.firstElement(); + QueryPlanSet s( ns(), BSON( "a" << 1 ), BSON( "b" << 1 ), &e ); + ASSERT_EQUALS( 1, s.nPlans() ); + } + }; + + class NaturalSort : public Base { + public: + void run() { + Helpers::ensureIndex( ns(), BSON( "a" << 1 ), "a_1" ); + Helpers::ensureIndex( ns(), BSON( "a" << 1 ), "b_2" ); + QueryPlanSet s( ns(), BSON( "a" << 1 ), BSON( "$natural" << 1 ) ); + ASSERT_EQUALS( 1, s.nPlans() ); + } + }; + class BadHint : public Base { public: void run() { @@ -518,9 +550,33 @@ namespace QueryOptimizerTests { ASSERT_EQUALS( 2, doCount( ns(), BSON( "query" << BSON( "a" << 4 ) ), err ) ); ASSERT_EQUALS( 3, doCount( ns(), BSON( "query" << emptyObj ), err ) ); ASSERT_EQUALS( 3, doCount( ns(), BSON( "query" << BSON( "a" << GT << 0 ) ), err ) ); + // missing ns + ASSERT_EQUALS( -1, doCount( "missingNS", emptyObj, err ) ); + // impossible match + ASSERT_EQUALS( 0, doCount( ns(), BSON( "query" << BSON( "a" << GT << 0 << LT << -1 ) ), err ) ); } }; + class QueryMissingNs : public Base { + public: + void run() { + Message m; + assembleRequest( "missingNS", emptyObj, 0, 0, &emptyObj, 0, m ); + stringstream ss; + ASSERT_EQUALS( 0, runQuery( m, ss )->nReturned ); + } + }; + + class UnhelpfulIndex : public Base { + public: + void run() { + Helpers::ensureIndex( ns(), BSON( "a" << 1 ), "a_1" ); + Helpers::ensureIndex( ns(), BSON( "b" << 1 ), "b_1" ); + QueryPlanSet s( ns(), BSON( "a" << 1 << "c" << 2 ), emptyObj ); + ASSERT_EQUALS( 2, s.nPlans() ); + } + }; + } // namespace QueryPlanSetTests class All : public UnitTest::Suite { @@ -557,8 +613,12 @@ namespace QueryOptimizerTests { add< QueryPlanSetTests::NoSpec >(); add< QueryPlanSetTests::HintSpec >(); add< QueryPlanSetTests::HintName >(); + add< QueryPlanSetTests::NaturalHint >(); + add< QueryPlanSetTests::NaturalSort >(); add< QueryPlanSetTests::BadHint >(); add< QueryPlanSetTests::Count >(); + add< QueryPlanSetTests::QueryMissingNs >(); + add< QueryPlanSetTests::UnhelpfulIndex >(); } }; diff --git a/jstests/jni3.js b/jstests/jni3.js index f7df9acb99d..e0f0d103c42 100644 --- a/jstests/jni3.js +++ b/jstests/jni3.js @@ -2,7 +2,7 @@ t = db.jni3; debug = function( s ){ - //print( s ); + //printjson( s ); } for( z = 0; z < 2; z++ ) { @@ -45,16 +45,23 @@ for( z = 0; z < 2; z++ ) { catch(e) { ok = true; } + debug( ok ); assert(ok); t.ensureIndex({z:1}); t.ensureIndex({q:1}); + + debug( "before indexed find" ); - assert( 2 == t.find( { $where : - function(){ - return obj.i == 7 || obj.i == 8; - } - } ).length() ); + arr = t.find( { $where : + function(){ + return obj.i == 7 || obj.i == 8; + } + } ).toArray(); + debug( arr ); + assert.eq( 2, arr.length ); + + debug( "after indexed find" ); for( i = 1000; i < 2000; i++ ) t.save( { i:i, z: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" } ); @@ -63,4 +70,5 @@ for( z = 0; z < 2; z++ ) { assert( t.validate().valid ); + debug( "done iter" ); }