// querytests.cpp : query.{h,cpp} unit tests. /** * 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 . * * As a special exception, the copyright holders give permission to link the * code of portions of this program with the OpenSSL library under certain * conditions as described in each individual source file and distribute * linked combinations including the program with the OpenSSL library. You * must comply with the GNU Affero General Public License in all respects * for all of the code used other than as permitted herein. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you do not * wish to do so, delete this exception statement from your version. If you * delete this exception statement from all source files in the program, * then also delete it in the license file. */ #include "mongo/platform/basic.h" #include #include "mongo/client/dbclientcursor.h" #include "mongo/db/catalog/collection.h" #include "mongo/db/clientcursor.h" #include "mongo/db/db_raii.h" #include "mongo/db/dbdirectclient.h" #include "mongo/db/dbhelpers.h" #include "mongo/db/global_environment_d.h" #include "mongo/db/global_environment_experiment.h" #include "mongo/db/global_optime.h" #include "mongo/db/json.h" #include "mongo/db/lasterror.h" #include "mongo/db/operation_context_impl.h" #include "mongo/db/query/find.h" #include "mongo/db/query/lite_parsed_query.h" #include "mongo/dbtests/dbtests.h" #include "mongo/util/timer.h" namespace mongo { void assembleRequest( const std::string &ns, BSONObj query, int nToReturn, int nToSkip, const BSONObj *fieldsToReturn, int queryOptions, Message &toSend ); } namespace QueryTests { using std::auto_ptr; using std::cout; using std::endl; using std::string; using std::vector; class Base { public: Base() : _scopedXact(&_txn, MODE_X), _lk(_txn.lockState()), _context(&_txn, ns()) { { WriteUnitOfWork wunit(&_txn); _database = _context.db(); _collection = _database->getCollection( ns() ); if ( _collection ) { _database->dropCollection( &_txn, ns() ); } _collection = _database->createCollection( &_txn, ns() ); wunit.commit(); } addIndex( fromjson( "{\"a\":1}" ) ); } ~Base() { try { WriteUnitOfWork wunit(&_txn); uassertStatusOK( _database->dropCollection( &_txn, ns() ) ); wunit.commit(); } catch ( ... ) { FAIL( "Exception while cleaning up collection" ); } } protected: static const char *ns() { return "unittests.querytests"; } void addIndex( const BSONObj &key ) { Helpers::ensureIndex(&_txn, _collection, key, false, key.firstElementFieldName()); } void insert( const char *s ) { insert( fromjson( s ) ); } void insert( const BSONObj &o ) { WriteUnitOfWork wunit(&_txn); if ( o["_id"].eoo() ) { BSONObjBuilder b; OID oid; oid.init(); b.appendOID( "_id", &oid ); b.appendElements( o ); _collection->insertDocument( &_txn, b.obj(), false ); } else { _collection->insertDocument( &_txn, o, false ); } wunit.commit(); } OperationContextImpl _txn; ScopedTransaction _scopedXact; Lock::GlobalWrite _lk; OldClientContext _context; Database* _database; Collection* _collection; }; class FindOneOr : public Base { public: void run() { addIndex( BSON( "b" << 1 ) ); addIndex( BSON( "c" << 1 ) ); insert( BSON( "b" << 2 << "_id" << 0 ) ); insert( BSON( "c" << 3 << "_id" << 1 ) ); BSONObj query = fromjson( "{$or:[{b:2},{c:3}]}" ); BSONObj ret; // Check findOne() returning object. ASSERT( Helpers::findOne( &_txn, _collection, query, ret, true ) ); ASSERT_EQUALS( string( "b" ), ret.firstElement().fieldName() ); // Cross check with findOne() returning location. ASSERT_EQUALS(ret, _collection->docFor(&_txn, Helpers::findOne(&_txn, _collection, query, true)).value()); } }; class FindOneRequireIndex : public Base { public: void run() { insert( BSON( "b" << 2 << "_id" << 0 ) ); BSONObj query = fromjson( "{b:2}" ); BSONObj ret; // Check findOne() returning object, allowing unindexed scan. ASSERT( Helpers::findOne( &_txn, _collection, query, ret, false ) ); // Check findOne() returning location, allowing unindexed scan. ASSERT_EQUALS(ret, _collection->docFor(&_txn, Helpers::findOne(&_txn, _collection, query, false)).value()); // Check findOne() returning object, requiring indexed scan without index. ASSERT_THROWS( Helpers::findOne( &_txn, _collection, query, ret, true ), MsgAssertionException ); // Check findOne() returning location, requiring indexed scan without index. ASSERT_THROWS( Helpers::findOne( &_txn, _collection, query, true ), MsgAssertionException ); addIndex( BSON( "b" << 1 ) ); // Check findOne() returning object, requiring indexed scan with index. ASSERT( Helpers::findOne( &_txn, _collection, query, ret, true ) ); // Check findOne() returning location, requiring indexed scan with index. ASSERT_EQUALS(ret, _collection->docFor(&_txn, Helpers::findOne(&_txn, _collection, query, true)).value()); } }; class FindOneEmptyObj : public Base { public: void run() { // We don't normally allow empty objects in the database, but test that we can find // an empty object (one might be allowed inside a reserved namespace at some point). ScopedTransaction transaction(&_txn, MODE_X); Lock::GlobalWrite lk(_txn.lockState()); OldClientContext ctx(&_txn, "unittests.querytests" ); { WriteUnitOfWork wunit(&_txn); Database* db = ctx.db(); if ( db->getCollection( ns() ) ) { _collection = NULL; db->dropCollection( &_txn, ns() ); } _collection = db->createCollection( &_txn, ns(), CollectionOptions(), true, false ); wunit.commit(); } ASSERT( _collection ); DBDirectClient cl(&_txn); BSONObj info; bool ok = cl.runCommand( "unittests", BSON( "godinsert" << "querytests" << "obj" << BSONObj() ), info ); ASSERT( ok ); insert( BSONObj() ); BSONObj query; BSONObj ret; ASSERT( Helpers::findOne( &_txn, _collection, query, ret, false ) ); ASSERT( ret.isEmpty() ); ASSERT_EQUALS(ret, _collection->docFor(&_txn, Helpers::findOne(&_txn, _collection, query, false)).value()); } }; class ClientBase { public: ClientBase() : _client(&_txn) { _prevError = mongo::lastError._get( false ); mongo::lastError.release(); mongo::lastError.reset( new LastError() ); _txn.getCurOp()->reset(); } virtual ~ClientBase() { mongo::lastError.reset( _prevError ); } protected: void insert( const char *ns, BSONObj o ) { _client.insert( ns, o ); } void update( const char *ns, BSONObj q, BSONObj o, bool upsert = 0 ) { _client.update( ns, Query( q ), o, upsert ); } bool error() { return !_client.getPrevError().getField( "err" ).isNull(); } OperationContextImpl _txn; DBDirectClient _client; private: LastError* _prevError; }; class BoundedKey : public ClientBase { public: ~BoundedKey() { _client.dropCollection( "unittests.querytests.BoundedKey" ); } void run() { const char *ns = "unittests.querytests.BoundedKey"; insert( ns, BSON( "a" << 1 ) ); BSONObjBuilder a; a.appendMaxKey( "$lt" ); BSONObj limit = a.done(); ASSERT( !_client.findOne( ns, QUERY( "a" << limit ) ).isEmpty() ); ASSERT_OK(dbtests::createIndex( &_txn, ns, BSON( "a" << 1 ) )); ASSERT( !_client.findOne( ns, QUERY( "a" << limit ).hint( BSON( "a" << 1 ) ) ).isEmpty() ); } }; class GetMore : public ClientBase { public: ~GetMore() { _client.dropCollection( "unittests.querytests.GetMore" ); } void run() { const char *ns = "unittests.querytests.GetMore"; insert( ns, BSON( "a" << 1 ) ); insert( ns, BSON( "a" << 2 ) ); insert( ns, BSON( "a" << 3 ) ); auto_ptr< DBClientCursor > cursor = _client.query( ns, BSONObj(), 2 ); long long cursorId = cursor->getCursorId(); cursor->decouple(); cursor.reset(); { // Check internal server handoff to getmore. OldClientWriteContext ctx(&_txn, ns); ClientCursorPin clientCursor( ctx.getCollection()->getCursorManager(), cursorId ); // pq doesn't exist if it's a runner inside of the clientcursor. // ASSERT( clientCursor.c()->pq ); // ASSERT_EQUALS( 2, clientCursor.c()->pq->getNumToReturn() ); ASSERT_EQUALS( 2, clientCursor.c()->pos() ); } cursor = _client.getMore( ns, cursorId ); ASSERT( cursor->more() ); ASSERT_EQUALS( 3, cursor->next().getIntField( "a" ) ); } }; /** * An exception triggered during a get more request destroys the ClientCursor used by the get * more, preventing further iteration of the cursor in subsequent get mores. */ class GetMoreKillOp : public ClientBase { public: ~GetMoreKillOp() { getGlobalEnvironment()->unsetKillAllOperations(); _client.dropCollection( "unittests.querytests.GetMoreKillOp" ); } void run() { // Create a collection with some data. const char* ns = "unittests.querytests.GetMoreKillOp"; for( int i = 0; i < 1000; ++i ) { insert( ns, BSON( "a" << i ) ); } // Create a cursor on the collection, with a batch size of 200. auto_ptr cursor = _client.query( ns, "", 0, 0, 0, 0, 200 ); CursorId cursorId = cursor->getCursorId(); // Count 500 results, spanning a few batches of documents. for( int i = 0; i < 500; ++i ) { ASSERT( cursor->more() ); cursor->next(); } // Set the killop kill all flag, forcing the next get more to fail with a kill op // exception. getGlobalEnvironment()->setKillAllOperations(); while( cursor->more() ) { cursor->next(); } // Revert the killop kill all flag. getGlobalEnvironment()->unsetKillAllOperations(); // Check that the cursor has been removed. { AutoGetCollectionForRead ctx(&_txn, ns); ASSERT(0 == ctx.getCollection()->getCursorManager()->numCursors()); } ASSERT_FALSE(CursorManager::eraseCursorGlobal(&_txn, cursorId)); // Check that a subsequent get more fails with the cursor removed. ASSERT_THROWS( _client.getMore( ns, cursorId ), UserException ); } }; /** * A get more exception caused by an invalid or unauthorized get more request does not cause * the get more's ClientCursor to be destroyed. This prevents an unauthorized user from * improperly killing a cursor by issuing an invalid get more request. */ class GetMoreInvalidRequest : public ClientBase { public: ~GetMoreInvalidRequest() { getGlobalEnvironment()->unsetKillAllOperations(); _client.dropCollection( "unittests.querytests.GetMoreInvalidRequest" ); } void run() { // Create a collection with some data. const char* ns = "unittests.querytests.GetMoreInvalidRequest"; for( int i = 0; i < 1000; ++i ) { insert( ns, BSON( "a" << i ) ); } // Create a cursor on the collection, with a batch size of 200. auto_ptr cursor = _client.query( ns, "", 0, 0, 0, 0, 200 ); CursorId cursorId = cursor->getCursorId(); // Count 500 results, spanning a few batches of documents. int count = 0; for( int i = 0; i < 500; ++i ) { ASSERT( cursor->more() ); cursor->next(); ++count; } // Send a get more with a namespace that is incorrect ('spoofed') for this cursor id. // This is the invalaid get more request described in the comment preceding this class. _client.getMore ( "unittests.querytests.GetMoreInvalidRequest_WRONG_NAMESPACE_FOR_CURSOR", cursor->getCursorId() ); // Check that the cursor still exists { AutoGetCollectionForRead ctx(&_txn, ns); ASSERT(1 == ctx.getCollection()->getCursorManager()->numCursors()); ASSERT(ctx.getCollection()->getCursorManager()->find(cursorId, false)); } // Check that the cursor can be iterated until all documents are returned. while( cursor->more() ) { cursor->next(); ++count; } ASSERT_EQUALS( 1000, count ); } }; class PositiveLimit : public ClientBase { public: const char* ns; PositiveLimit() : ns("unittests.querytests.PositiveLimit") {} ~PositiveLimit() { _client.dropCollection( ns ); } void testLimit(int limit) { ASSERT_EQUALS(_client.query( ns, BSONObj(), limit )->itcount(), limit); } void run() { for(int i=0; i<1000; i++) insert( ns, BSON( GENOID << "i" << i ) ); ASSERT_EQUALS( _client.query(ns, BSONObj(), 1 )->itcount(), 1); ASSERT_EQUALS( _client.query(ns, BSONObj(), 10 )->itcount(), 10); ASSERT_EQUALS( _client.query(ns, BSONObj(), 101 )->itcount(), 101); ASSERT_EQUALS( _client.query(ns, BSONObj(), 999 )->itcount(), 999); ASSERT_EQUALS( _client.query(ns, BSONObj(), 1000 )->itcount(), 1000); ASSERT_EQUALS( _client.query(ns, BSONObj(), 1001 )->itcount(), 1000); ASSERT_EQUALS( _client.query(ns, BSONObj(), 0 )->itcount(), 1000); } }; class ReturnOneOfManyAndTail : public ClientBase { public: ~ReturnOneOfManyAndTail() { _client.dropCollection( "unittests.querytests.ReturnOneOfManyAndTail" ); } void run() { const char *ns = "unittests.querytests.ReturnOneOfManyAndTail"; _client.createCollection( ns, 1024, true ); insert( ns, BSON( "a" << 0 ) ); insert( ns, BSON( "a" << 1 ) ); insert( ns, BSON( "a" << 2 ) ); auto_ptr< DBClientCursor > c = _client.query( ns, QUERY( "a" << GT << 0 ).hint( BSON( "$natural" << 1 ) ), 1, 0, 0, QueryOption_CursorTailable ); // If only one result requested, a cursor is not saved. ASSERT_EQUALS( 0, c->getCursorId() ); ASSERT( c->more() ); ASSERT_EQUALS( 1, c->next().getIntField( "a" ) ); } }; class TailNotAtEnd : public ClientBase { public: ~TailNotAtEnd() { _client.dropCollection( "unittests.querytests.TailNotAtEnd" ); } void run() { const char *ns = "unittests.querytests.TailNotAtEnd"; _client.createCollection( ns, 2047, true ); insert( ns, BSON( "a" << 0 ) ); insert( ns, BSON( "a" << 1 ) ); insert( ns, BSON( "a" << 2 ) ); auto_ptr< DBClientCursor > c = _client.query( ns, Query().hint( BSON( "$natural" << 1 ) ), 2, 0, 0, QueryOption_CursorTailable ); ASSERT( 0 != c->getCursorId() ); while( c->more() ) c->next(); ASSERT( 0 != c->getCursorId() ); insert( ns, BSON( "a" << 3 ) ); insert( ns, BSON( "a" << 4 ) ); insert( ns, BSON( "a" << 5 ) ); insert( ns, BSON( "a" << 6 ) ); ASSERT( c->more() ); ASSERT_EQUALS( 3, c->next().getIntField( "a" ) ); } }; class EmptyTail : public ClientBase { public: ~EmptyTail() { _client.dropCollection( "unittests.querytests.EmptyTail" ); } void run() { const char *ns = "unittests.querytests.EmptyTail"; _client.createCollection( ns, 1900, true ); auto_ptr< DBClientCursor > c = _client.query( ns, Query().hint( BSON( "$natural" << 1 ) ), 2, 0, 0, QueryOption_CursorTailable ); ASSERT_EQUALS( 0, c->getCursorId() ); ASSERT( c->isDead() ); insert( ns, BSON( "a" << 0 ) ); c = _client.query( ns, QUERY( "a" << 1 ).hint( BSON( "$natural" << 1 ) ), 2, 0, 0, QueryOption_CursorTailable ); ASSERT( 0 != c->getCursorId() ); ASSERT( !c->isDead() ); } }; class TailableDelete : public ClientBase { public: ~TailableDelete() { _client.dropCollection( "unittests.querytests.TailableDelete" ); } void run() { const char *ns = "unittests.querytests.TailableDelete"; _client.createCollection( ns, 8192, true, 2 ); insert( ns, BSON( "a" << 0 ) ); insert( ns, BSON( "a" << 1 ) ); auto_ptr< DBClientCursor > c = _client.query( ns, Query().hint( BSON( "$natural" << 1 ) ), 2, 0, 0, QueryOption_CursorTailable ); c->next(); c->next(); ASSERT( !c->more() ); insert( ns, BSON( "a" << 2 ) ); insert( ns, BSON( "a" << 3 ) ); // This can either have been killed, or jumped to the right thing. // Key is that it can't skip. if ( c->more() ) { BSONObj x = c->next(); ASSERT_EQUALS( 2, x["a"].numberInt() ); } // Inserting a document into a capped collection can force another document out. // In this case, the capped collection has 2 documents, so inserting two more clobbers // whatever RecordId that the underlying cursor had as its state. // // In the Cursor world, the ClientCursor was responsible for manipulating cursors. It // would detect that the cursor's "refloc" (translation: diskloc required to maintain // iteration state) was being clobbered and it would kill the cursor. // // In the Runner world there is no notion of a "refloc" and as such the invalidation // broadcast code doesn't know enough to know that the underlying collection iteration // can't proceed. // ASSERT_EQUALS( 0, c->getCursorId() ); } }; class TailableDelete2 : public ClientBase { public: ~TailableDelete2() { _client.dropCollection( "unittests.querytests.TailableDelete" ); } void run() { const char *ns = "unittests.querytests.TailableDelete"; _client.createCollection( ns, 8192, true, 2 ); insert( ns, BSON( "a" << 0 ) ); insert( ns, BSON( "a" << 1 ) ); auto_ptr< DBClientCursor > c = _client.query( ns, Query().hint( BSON( "$natural" << 1 ) ), 2, 0, 0, QueryOption_CursorTailable ); c->next(); c->next(); ASSERT( !c->more() ); insert( ns, BSON( "a" << 2 ) ); insert( ns, BSON( "a" << 3 ) ); insert( ns, BSON( "a" << 4 ) ); // This can either have been killed, or jumped to the right thing. // Key is that it can't skip. if ( c->more() ) { BSONObj x = c->next(); ASSERT_EQUALS( 2, x["a"].numberInt() ); } } }; class TailableInsertDelete : public ClientBase { public: ~TailableInsertDelete() { _client.dropCollection( "unittests.querytests.TailableInsertDelete" ); } void run() { const char *ns = "unittests.querytests.TailableInsertDelete"; _client.createCollection( ns, 1330, true ); insert( ns, BSON( "a" << 0 ) ); insert( ns, BSON( "a" << 1 ) ); auto_ptr< DBClientCursor > c = _client.query( ns, Query().hint( BSON( "$natural" << 1 ) ), 2, 0, 0, QueryOption_CursorTailable ); c->next(); c->next(); ASSERT( !c->more() ); insert( ns, BSON( "a" << 2 ) ); _client.remove( ns, QUERY( "a" << 1 ) ); ASSERT( c->more() ); ASSERT_EQUALS( 2, c->next().getIntField( "a" ) ); ASSERT( !c->more() ); } }; class TailCappedOnly : public ClientBase { public: ~TailCappedOnly() { _client.dropCollection( "unittest.querytests.TailCappedOnly" ); } void run() { const char *ns = "unittests.querytests.TailCappedOnly"; _client.insert( ns, BSONObj() ); auto_ptr< DBClientCursor > c = _client.query( ns, BSONObj(), 0, 0, 0, QueryOption_CursorTailable ); ASSERT( c->isDead() ); } }; class TailableQueryOnId : public ClientBase { public: ~TailableQueryOnId() { _client.dropCollection( "unittests.querytests.TailableQueryOnId" ); } void insertA(const char* ns, int a) { BSONObjBuilder b; b.appendOID("_id", 0, true); b.appendOID("value", 0, true); b.append("a", a); insert(ns, b.obj()); } void run() { const char *ns = "unittests.querytests.TailableQueryOnId"; BSONObj info; _client.runCommand( "unittests", BSON( "create" << "querytests.TailableQueryOnId" << "capped" << true << "size" << 8192 << "autoIndexId" << true ), info ); insertA( ns, 0 ); insertA( ns, 1 ); auto_ptr< DBClientCursor > c1 = _client.query( ns, QUERY( "a" << GT << -1 ), 0, 0, 0, QueryOption_CursorTailable ); OID id; id.init("000000000000000000000000"); auto_ptr< DBClientCursor > c2 = _client.query( ns, QUERY( "value" << GT << id ), 0, 0, 0, QueryOption_CursorTailable ); c1->next(); c1->next(); ASSERT( !c1->more() ); c2->next(); c2->next(); ASSERT( !c2->more() ); insertA( ns, 2 ); ASSERT( c1->more() ); ASSERT_EQUALS( 2, c1->next().getIntField( "a" ) ); ASSERT( !c1->more() ); ASSERT( c2->more() ); ASSERT_EQUALS( 2, c2->next().getIntField( "a" ) ); // SERVER-645 ASSERT( !c2->more() ); ASSERT( !c2->isDead() ); } }; class OplogReplayMode : public ClientBase { public: ~OplogReplayMode() { _client.dropCollection( "unittests.querytests.OplogReplayMode" ); } void run() { const char *ns = "unittests.querytests.OplogReplayMode"; insert( ns, BSON( "ts" << 0 ) ); insert( ns, BSON( "ts" << 1 ) ); insert( ns, BSON( "ts" << 2 ) ); auto_ptr< DBClientCursor > c = _client.query( ns, QUERY( "ts" << GT << 1 ).hint( BSON( "$natural" << 1 ) ), 0, 0, 0, QueryOption_OplogReplay ); ASSERT( c->more() ); ASSERT_EQUALS( 2, c->next().getIntField( "ts" ) ); ASSERT( !c->more() ); insert( ns, BSON( "ts" << 3 ) ); c = _client.query( ns, QUERY( "ts" << GT << 1 ).hint( BSON( "$natural" << 1 ) ), 0, 0, 0, QueryOption_OplogReplay ); ASSERT( c->more() ); ASSERT_EQUALS( 2, c->next().getIntField( "ts" ) ); ASSERT( c->more() ); } }; class OplogReplaySlaveReadTill : public ClientBase { public: ~OplogReplaySlaveReadTill() { _client.dropCollection( "unittests.querytests.OplogReplaySlaveReadTill" ); } void run() { const char *ns = "unittests.querytests.OplogReplaySlaveReadTill"; ScopedTransaction transaction(&_txn, MODE_IX); Lock::DBLock lk(_txn.lockState(), "unittests", MODE_X); OldClientContext ctx(&_txn, ns ); BSONObj info; _client.runCommand( "unittests", BSON( "create" << "querytests.OplogReplaySlaveReadTill" << "capped" << true << "size" << 8192 ), info ); Date_t one = getNextGlobalOptime().asDate(); Date_t two = getNextGlobalOptime().asDate(); Date_t three = getNextGlobalOptime().asDate(); insert( ns, BSON( "ts" << one ) ); insert( ns, BSON( "ts" << two ) ); insert( ns, BSON( "ts" << three ) ); auto_ptr c = _client.query( ns, QUERY( "ts" << GTE << two ).hint( BSON( "$natural" << 1 ) ), 0, 0, 0, QueryOption_OplogReplay | QueryOption_CursorTailable ); ASSERT( c->more() ); ASSERT_EQUALS( two, c->next()["ts"].Date() ); long long cursorId = c->getCursorId(); ClientCursorPin clientCursor( ctx.db()->getCollection( ns )->getCursorManager(), cursorId ); ASSERT_EQUALS( three.millis, clientCursor.c()->getSlaveReadTill().asDate() ); } }; class OplogReplayExplain : public ClientBase { public: ~OplogReplayExplain() { _client.dropCollection( "unittests.querytests.OplogReplayExplain" ); } void run() { const char *ns = "unittests.querytests.OplogReplayExplain"; insert( ns, BSON( "ts" << 0 ) ); insert( ns, BSON( "ts" << 1 ) ); insert( ns, BSON( "ts" << 2 ) ); auto_ptr< DBClientCursor > c = _client.query( ns, QUERY( "ts" << GT << 1 ).hint( BSON( "$natural" << 1 ) ).explain(), 0, 0, 0, QueryOption_OplogReplay ); ASSERT( c->more() ); // Check number of results and filterSet flag in explain. // filterSet is not available in oplog replay mode. BSONObj explainObj = c->next(); ASSERT( explainObj.hasField("executionStats") ); BSONObj execStats = explainObj["executionStats"].Obj(); ASSERT_EQUALS( 1, execStats.getIntField( "nReturned" ) ); ASSERT( !c->more() ); } }; class BasicCount : public ClientBase { public: ~BasicCount() { _client.dropCollection( "unittests.querytests.BasicCount" ); } void run() { const char *ns = "unittests.querytests.BasicCount"; ASSERT_OK(dbtests::createIndex(&_txn, ns, BSON( "a" << 1 ) )); count( 0 ); insert( ns, BSON( "a" << 3 ) ); count( 0 ); insert( ns, BSON( "a" << 4 ) ); count( 1 ); insert( ns, BSON( "a" << 5 ) ); count( 1 ); insert( ns, BSON( "a" << 4 ) ); count( 2 ); } private: void count( unsigned long long c ) { ASSERT_EQUALS( c, _client.count( "unittests.querytests.BasicCount", BSON( "a" << 4 ) ) ); } }; class ArrayId : public ClientBase { public: ~ArrayId() { _client.dropCollection( "unittests.querytests.ArrayId" ); } void run() { const char *ns = "unittests.querytests.ArrayId"; ASSERT_OK(dbtests::createIndex( &_txn, ns, BSON( "_id" << 1 ) )); ASSERT( !error() ); _client.insert( ns, fromjson( "{'_id':[1,2]}" ) ); ASSERT( error() ); } }; class UnderscoreNs : public ClientBase { public: ~UnderscoreNs() { _client.dropCollection( "unittests.querytests._UnderscoreNs" ); } void run() { ASSERT( !error() ); const char *ns = "unittests.querytests._UnderscoreNs"; ASSERT( _client.findOne( ns, "{}" ).isEmpty() ); _client.insert( ns, BSON( "a" << 1 ) ); ASSERT_EQUALS( 1, _client.findOne( ns, "{}" ).getIntField( "a" ) ); ASSERT( !error() ); } }; class EmptyFieldSpec : public ClientBase { public: ~EmptyFieldSpec() { _client.dropCollection( "unittests.querytests.EmptyFieldSpec" ); } void run() { const char *ns = "unittests.querytests.EmptyFieldSpec"; _client.insert( ns, BSON( "a" << 1 ) ); ASSERT( !_client.findOne( ns, "" ).isEmpty() ); BSONObj empty; ASSERT( !_client.findOne( ns, "", &empty ).isEmpty() ); } }; class MultiNe : public ClientBase { public: ~MultiNe() { _client.dropCollection( "unittests.querytests.Ne" ); } void run() { const char *ns = "unittests.querytests.Ne"; _client.insert( ns, fromjson( "{a:[1,2]}" ) ); ASSERT( _client.findOne( ns, fromjson( "{a:{$ne:1}}" ) ).isEmpty() ); BSONObj spec = fromjson( "{a:{$ne:1,$ne:2}}" ); ASSERT( _client.findOne( ns, spec ).isEmpty() ); } }; class EmbeddedNe : public ClientBase { public: ~EmbeddedNe() { _client.dropCollection( "unittests.querytests.NestedNe" ); } void run() { const char *ns = "unittests.querytests.NestedNe"; _client.insert( ns, fromjson( "{a:[{b:1},{b:2}]}" ) ); ASSERT( _client.findOne( ns, fromjson( "{'a.b':{$ne:1}}" ) ).isEmpty() ); } }; class EmbeddedNumericTypes : public ClientBase { public: ~EmbeddedNumericTypes() { _client.dropCollection( "unittests.querytests.NumericEmbedded" ); } void run() { const char *ns = "unittests.querytests.NumericEmbedded"; _client.insert( ns, BSON( "a" << BSON ( "b" << 1 ) ) ); ASSERT( ! _client.findOne( ns, BSON( "a" << BSON ( "b" << 1.0 ) ) ).isEmpty() ); ASSERT_OK(dbtests::createIndex( &_txn, ns , BSON( "a" << 1 ) )); ASSERT( ! _client.findOne( ns, BSON( "a" << BSON ( "b" << 1.0 ) ) ).isEmpty() ); } }; class AutoResetIndexCache : public ClientBase { public: ~AutoResetIndexCache() { _client.dropCollection( "unittests.querytests.AutoResetIndexCache" ); } static const char *ns() { return "unittests.querytests.AutoResetIndexCache"; } void index() { ASSERT_EQUALS(2u, _client.getIndexSpecs(ns()).size()); } void noIndex() { ASSERT_EQUALS(0u, _client.getIndexSpecs(ns()).size()); } void checkIndex() { ASSERT_OK(dbtests::createIndex( &_txn, ns() , BSON( "a" << 1 ) )); index(); } void run() { _client.dropDatabase( "unittests" ); noIndex(); checkIndex(); _client.dropCollection( ns() ); noIndex(); checkIndex(); _client.dropDatabase( "unittests" ); noIndex(); checkIndex(); } }; class UniqueIndex : public ClientBase { public: ~UniqueIndex() { _client.dropCollection( "unittests.querytests.UniqueIndex" ); } void run() { const char *ns = "unittests.querytests.UniqueIndex"; ASSERT_OK(dbtests::createIndex( &_txn, ns , BSON( "a" << 1 ), true )); _client.insert( ns, BSON( "a" << 4 << "b" << 2 ) ); _client.insert( ns, BSON( "a" << 4 << "b" << 3 ) ); ASSERT_EQUALS( 1U, _client.count( ns, BSONObj() ) ); _client.dropCollection( ns ); ASSERT_OK(dbtests::createIndex( &_txn, ns , BSON( "b" << 1 ), true )); _client.insert( ns, BSON( "a" << 4 << "b" << 2 ) ); _client.insert( ns, BSON( "a" << 4 << "b" << 3 ) ); ASSERT_EQUALS( 2U, _client.count( ns, BSONObj() ) ); } }; class UniqueIndexPreexistingData : public ClientBase { public: ~UniqueIndexPreexistingData() { _client.dropCollection( "unittests.querytests.UniqueIndexPreexistingData" ); } void run() { const char *ns = "unittests.querytests.UniqueIndexPreexistingData"; _client.insert( ns, BSON( "a" << 4 << "b" << 2 ) ); _client.insert( ns, BSON( "a" << 4 << "b" << 3 ) ); ASSERT_EQUALS(ErrorCodes::DuplicateKey, dbtests::createIndex( &_txn, ns , BSON( "a" << 1 ), true )); ASSERT_EQUALS( 0U, _client.count( "unittests.system.indexes", BSON( "ns" << ns << "name" << NE << "_id_" ) ) ); } }; class SubobjectInArray : public ClientBase { public: ~SubobjectInArray() { _client.dropCollection( "unittests.querytests.SubobjectInArray" ); } void run() { const char *ns = "unittests.querytests.SubobjectInArray"; _client.insert( ns, fromjson( "{a:[{b:{c:1}}]}" ) ); ASSERT( !_client.findOne( ns, BSON( "a.b.c" << 1 ) ).isEmpty() ); ASSERT( !_client.findOne( ns, fromjson( "{'a.c':null}" ) ).isEmpty() ); } }; class Size : public ClientBase { public: ~Size() { _client.dropCollection( "unittests.querytests.Size" ); } void run() { const char *ns = "unittests.querytests.Size"; _client.insert( ns, fromjson( "{a:[1,2,3]}" ) ); ASSERT_OK(dbtests::createIndex( &_txn, ns , BSON( "a" << 1 ) )); ASSERT( _client.query( ns, QUERY( "a" << mongo::BSIZE << 3 ).hint( BSON( "a" << 1 ) ) )->more() ); } }; class FullArray : public ClientBase { public: ~FullArray() { _client.dropCollection( "unittests.querytests.IndexedArray" ); } void run() { const char *ns = "unittests.querytests.IndexedArray"; _client.insert( ns, fromjson( "{a:[1,2,3]}" ) ); ASSERT( _client.query( ns, Query( "{a:[1,2,3]}" ) )->more() ); ASSERT_OK(dbtests::createIndex( &_txn, ns , BSON( "a" << 1 ) )); ASSERT( _client.query( ns, Query( "{a:{$in:[1,[1,2,3]]}}" ).hint( BSON( "a" << 1 ) ) )->more() ); ASSERT( _client.query( ns, Query( "{a:[1,2,3]}" ).hint( BSON( "a" << 1 ) ) )->more() ); // SERVER-146 } }; class InsideArray : public ClientBase { public: ~InsideArray() { _client.dropCollection( "unittests.querytests.InsideArray" ); } void run() { const char *ns = "unittests.querytests.InsideArray"; _client.insert( ns, fromjson( "{a:[[1],2]}" ) ); check( "$natural" ); ASSERT_OK(dbtests::createIndex( &_txn, ns , BSON( "a" << 1 ) )); check( "a" ); // SERVER-146 } private: void check( const string &hintField ) { const char *ns = "unittests.querytests.InsideArray"; ASSERT( _client.query( ns, Query( "{a:[[1],2]}" ).hint( BSON( hintField << 1 ) ) )->more() ); ASSERT( _client.query( ns, Query( "{a:[1]}" ).hint( BSON( hintField << 1 ) ) )->more() ); ASSERT( _client.query( ns, Query( "{a:2}" ).hint( BSON( hintField << 1 ) ) )->more() ); ASSERT( !_client.query( ns, Query( "{a:1}" ).hint( BSON( hintField << 1 ) ) )->more() ); } }; class IndexInsideArrayCorrect : public ClientBase { public: ~IndexInsideArrayCorrect() { _client.dropCollection( "unittests.querytests.IndexInsideArrayCorrect" ); } void run() { const char *ns = "unittests.querytests.IndexInsideArrayCorrect"; _client.insert( ns, fromjson( "{'_id':1,a:[1]}" ) ); _client.insert( ns, fromjson( "{'_id':2,a:[[1]]}" ) ); ASSERT_OK(dbtests::createIndex( &_txn, ns , BSON( "a" << 1 ) )); ASSERT_EQUALS( 1, _client.query( ns, Query( "{a:[1]}" ).hint( BSON( "a" << 1 ) ) )->next().getIntField( "_id" ) ); } }; class SubobjArr : public ClientBase { public: ~SubobjArr() { _client.dropCollection( "unittests.querytests.SubobjArr" ); } void run() { const char *ns = "unittests.querytests.SubobjArr"; _client.insert( ns, fromjson( "{a:[{b:[1]}]}" ) ); check( "$natural" ); ASSERT_OK(dbtests::createIndex( &_txn, ns , BSON( "a" << 1 ) )); check( "a" ); } private: void check( const string &hintField ) { const char *ns = "unittests.querytests.SubobjArr"; ASSERT( _client.query( ns, Query( "{'a.b':1}" ).hint( BSON( hintField << 1 ) ) )->more() ); ASSERT( _client.query( ns, Query( "{'a.b':[1]}" ).hint( BSON( hintField << 1 ) ) )->more() ); } }; class MinMax : public ClientBase { public: MinMax() : ns( "unittests.querytests.MinMax" ) {} ~MinMax() { _client.dropCollection( "unittests.querytests.MinMax" ); } void run() { ASSERT_OK(dbtests::createIndex( &_txn, ns, BSON( "a" << 1 << "b" << 1 ) )); _client.insert( ns, BSON( "a" << 1 << "b" << 1 ) ); _client.insert( ns, BSON( "a" << 1 << "b" << 2 ) ); _client.insert( ns, BSON( "a" << 2 << "b" << 1 ) ); _client.insert( ns, BSON( "a" << 2 << "b" << 2 ) ); ASSERT_EQUALS( 4, count( _client.query( ns, BSONObj() ) ) ); BSONObj hints[] = { BSONObj(), BSON( "a" << 1 << "b" << 1 ) }; for( int i = 0; i < 2; ++i ) { check( 0, 0, 3, 3, 4, hints[ i ] ); check( 1, 1, 2, 2, 3, hints[ i ] ); check( 1, 2, 2, 2, 2, hints[ i ] ); check( 1, 2, 2, 1, 1, hints[ i ] ); auto_ptr< DBClientCursor > c = query( 1, 2, 2, 2, hints[ i ] ); BSONObj obj = c->next(); ASSERT_EQUALS( 1, obj.getIntField( "a" ) ); ASSERT_EQUALS( 2, obj.getIntField( "b" ) ); obj = c->next(); ASSERT_EQUALS( 2, obj.getIntField( "a" ) ); ASSERT_EQUALS( 1, obj.getIntField( "b" ) ); ASSERT( !c->more() ); } } private: auto_ptr< DBClientCursor > query( int minA, int minB, int maxA, int maxB, const BSONObj &hint ) { Query q; q = q.minKey( BSON( "a" << minA << "b" << minB ) ).maxKey( BSON( "a" << maxA << "b" << maxB ) ); if ( !hint.isEmpty() ) q.hint( hint ); return _client.query( ns, q ); } void check( int minA, int minB, int maxA, int maxB, int expectedCount, const BSONObj &hint = empty_ ) { ASSERT_EQUALS( expectedCount, count( query( minA, minB, maxA, maxB, hint ) ) ); } int count( auto_ptr< DBClientCursor > c ) { int ret = 0; while( c->more() ) { ++ret; c->next(); } return ret; } const char *ns; static BSONObj empty_; }; BSONObj MinMax::empty_; class MatchCodeCodeWScope : public ClientBase { public: MatchCodeCodeWScope() : _ns( "unittests.querytests.MatchCodeCodeWScope" ) {} ~MatchCodeCodeWScope() { _client.dropCollection( "unittests.querytests.MatchCodeCodeWScope" ); } void run() { checkMatch(); ASSERT_OK(dbtests::createIndex( &_txn, _ns, BSON( "a" << 1 ) )); checkMatch(); } private: void checkMatch() { _client.remove( _ns, BSONObj() ); _client.insert( _ns, code() ); _client.insert( _ns, codeWScope() ); ASSERT_EQUALS( 1U, _client.count( _ns, code() ) ); ASSERT_EQUALS( 1U, _client.count( _ns, codeWScope() ) ); ASSERT_EQUALS( 1U, _client.count( _ns, BSON( "a" << BSON( "$type" << (int)Code ) ) ) ); ASSERT_EQUALS( 1U, _client.count( _ns, BSON( "a" << BSON( "$type" << (int)CodeWScope ) ) ) ); } BSONObj code() const { BSONObjBuilder codeBuilder; codeBuilder.appendCode( "a", "return 1;" ); return codeBuilder.obj(); } BSONObj codeWScope() const { BSONObjBuilder codeWScopeBuilder; codeWScopeBuilder.appendCodeWScope( "a", "return 1;", BSONObj() ); return codeWScopeBuilder.obj(); } const char *_ns; }; class MatchDBRefType : public ClientBase { public: MatchDBRefType() : _ns( "unittests.querytests.MatchDBRefType" ) {} ~MatchDBRefType() { _client.dropCollection( "unittests.querytests.MatchDBRefType" ); } void run() { checkMatch(); ASSERT_OK(dbtests::createIndex( &_txn, _ns, BSON( "a" << 1 ) )); checkMatch(); } private: void checkMatch() { _client.remove( _ns, BSONObj() ); _client.insert( _ns, dbref() ); ASSERT_EQUALS( 1U, _client.count( _ns, dbref() ) ); ASSERT_EQUALS( 1U, _client.count( _ns, BSON( "a" << BSON( "$type" << (int)DBRef ) ) ) ); } BSONObj dbref() const { BSONObjBuilder b; OID oid; b.appendDBRef( "a", "ns", oid ); return b.obj(); } const char *_ns; }; class DirectLocking : public ClientBase { public: void run() { ScopedTransaction transaction(&_txn, MODE_X); Lock::GlobalWrite lk(_txn.lockState()); OldClientContext ctx(&_txn, "unittests.DirectLocking"); _client.remove( "a.b", BSONObj() ); ASSERT_EQUALS( "unittests", ctx.db()->name() ); } const char *ns; }; class FastCountIn : public ClientBase { public: ~FastCountIn() { _client.dropCollection( "unittests.querytests.FastCountIn" ); } void run() { const char *ns = "unittests.querytests.FastCountIn"; _client.insert( ns, BSON( "i" << "a" ) ); ASSERT_OK(dbtests::createIndex( &_txn, ns, BSON( "i" << 1 ) )); ASSERT_EQUALS( 1U, _client.count( ns, fromjson( "{i:{$in:['a']}}" ) ) ); } }; class EmbeddedArray : public ClientBase { public: ~EmbeddedArray() { _client.dropCollection( "unittests.querytests.EmbeddedArray" ); } void run() { const char *ns = "unittests.querytests.EmbeddedArray"; _client.insert( ns, fromjson( "{foo:{bar:['spam']}}" ) ); _client.insert( ns, fromjson( "{foo:{bar:['spam','eggs']}}" ) ); _client.insert( ns, fromjson( "{bar:['spam']}" ) ); _client.insert( ns, fromjson( "{bar:['spam','eggs']}" ) ); ASSERT_EQUALS( 2U, _client.count( ns, BSON( "bar" << "spam" ) ) ); ASSERT_EQUALS( 2U, _client.count( ns, BSON( "foo.bar" << "spam" ) ) ); } }; class DifferentNumbers : public ClientBase { public: ~DifferentNumbers() { _client.dropCollection( "unittests.querytests.DifferentNumbers" ); } void t( const char * ns ) { auto_ptr< DBClientCursor > cursor = _client.query( ns, Query().sort( "7" ) ); while ( cursor->more() ) { BSONObj o = cursor->next(); verify( o.valid() ); //cout << " foo " << o << endl; } } void run() { const char *ns = "unittests.querytests.DifferentNumbers"; { BSONObjBuilder b; b.append( "7" , (int)4 ); _client.insert( ns , b.obj() ); } { BSONObjBuilder b; b.append( "7" , (long long)2 ); _client.insert( ns , b.obj() ); } { BSONObjBuilder b; b.appendNull( "7" ); _client.insert( ns , b.obj() ); } { BSONObjBuilder b; b.append( "7" , "b" ); _client.insert( ns , b.obj() ); } { BSONObjBuilder b; b.appendNull( "8" ); _client.insert( ns , b.obj() ); } { BSONObjBuilder b; b.append( "7" , (double)3.7 ); _client.insert( ns , b.obj() ); } t(ns); ASSERT_OK(dbtests::createIndex( &_txn, ns , BSON( "7" << 1 ) )); t(ns); } }; class CollectionBase : public ClientBase { public: CollectionBase( string leaf ) { _ns = "unittests.querytests."; _ns += leaf; _client.dropCollection( ns() ); } virtual ~CollectionBase() { _client.dropCollection( ns() ); } int count() { return (int) _client.count( ns() ); } size_t numCursorsOpen() { AutoGetCollectionForRead ctx(&_txn, _ns); Collection* collection = ctx.getCollection(); if ( !collection ) return 0; return collection->getCursorManager()->numCursors(); } const char * ns() { return _ns.c_str(); } private: string _ns; }; class SymbolStringSame : public CollectionBase { public: SymbolStringSame() : CollectionBase( "symbolstringsame" ) {} void run() { { BSONObjBuilder b; b.appendSymbol( "x" , "eliot" ); b.append( "z" , 17 ); _client.insert( ns() , b.obj() ); } ASSERT_EQUALS( 17 , _client.findOne( ns() , BSONObj() )["z"].number() ); { BSONObjBuilder b; b.appendSymbol( "x" , "eliot" ); ASSERT_EQUALS( 17 , _client.findOne( ns() , b.obj() )["z"].number() ); } ASSERT_EQUALS( 17 , _client.findOne( ns() , BSON( "x" << "eliot" ) )["z"].number() ); ASSERT_OK(dbtests::createIndex( &_txn, ns() , BSON( "x" << 1 ) )); ASSERT_EQUALS( 17 , _client.findOne( ns() , BSON( "x" << "eliot" ) )["z"].number() ); } }; class TailableCappedRaceCondition : public CollectionBase { public: TailableCappedRaceCondition() : CollectionBase( "tailablecappedrace" ) { _client.dropCollection( ns() ); _n = 0; } void run() { string err; OldClientWriteContext ctx(&_txn, ns()); // note that extents are always at least 4KB now - so this will get rounded up // a bit. { WriteUnitOfWork wunit(&_txn); ASSERT( userCreateNS(&_txn, ctx.db(), ns(), fromjson( "{ capped : true, size : 2000, max: 10000 }" ), false ).isOK() ); wunit.commit(); } for (int i = 0; i < 200; i++) { insertNext(); ASSERT(count() < 90); } int a = count(); auto_ptr< DBClientCursor > c = _client.query( ns() , QUERY( "i" << GT << 0 ).hint( BSON( "$natural" << 1 ) ), 0, 0, 0, QueryOption_CursorTailable ); int n=0; while ( c->more() ) { BSONObj z = c->next(); n++; } ASSERT_EQUALS( a , n ); insertNext(); ASSERT( c->more() ); for ( int i=0; i<90; i++ ) { insertNext(); } while ( c->more() ) { c->next(); } } void insertNext() { BSONObjBuilder b; b.appendOID("_id", 0, true); b.append("i", _n++); insert( ns() , b.obj() ); } int _n; }; class HelperTest : public CollectionBase { public: HelperTest() : CollectionBase( "helpertest" ) { } void run() { OldClientWriteContext ctx(&_txn, ns()); for ( int i=0; i<50; i++ ) { insert( ns() , BSON( "_id" << i << "x" << i * 2 ) ); } ASSERT_EQUALS( 50 , count() ); BSONObj res; ASSERT( Helpers::findOne(&_txn, ctx.getCollection(), BSON("_id" << 20) , res , true)); ASSERT_EQUALS( 40 , res["x"].numberInt() ); ASSERT( Helpers::findById( &_txn, ctx.db(), ns() , BSON( "_id" << 20 ) , res ) ); ASSERT_EQUALS( 40 , res["x"].numberInt() ); ASSERT( ! Helpers::findById( &_txn, ctx.db(), ns() , BSON( "_id" << 200 ) , res ) ); long long slow; long long fast; int n = 10000; DEV n = 1000; { Timer t; for ( int i=0; i max ) max = newCount; } for( int k = 0; k < 5; ++k ) { _client.insert( ns(), BSON( "ts" << i++ ) ); int min = _client.query( ns(), Query().sort( BSON( "$natural" << 1 ) ) )->next()[ "ts" ].numberInt(); for( int j = -1; j < i; ++j ) { auto_ptr< DBClientCursor > c = _client.query( ns(), QUERY( "ts" << GTE << j ), 0, 0, 0, QueryOption_OplogReplay ); ASSERT( c->more() ); BSONObj next = c->next(); ASSERT( !next[ "ts" ].eoo() ); ASSERT_EQUALS( ( j > min ? j : min ), next[ "ts" ].numberInt() ); } cout << k << endl; } } }; class FindingStartPartiallyFull : public CollectionBase { public: FindingStartPartiallyFull() : CollectionBase( "findingstart" ) { } void run() { cout << "2 ;kljsdf" << endl; size_t startNumCursors = numCursorsOpen(); BSONObj info; ASSERT( _client.runCommand( "unittests", BSON( "create" << "querytests.findingstart" << "capped" << true << "$nExtents" << 5 << "autoIndexId" << false ), info ) ); int i = 0; for( ; i < 150; _client.insert( ns(), BSON( "ts" << i++ ) ) ); for( int k = 0; k < 5; ++k ) { _client.insert( ns(), BSON( "ts" << i++ ) ); int min = _client.query( ns(), Query().sort( BSON( "$natural" << 1 ) ) )->next()[ "ts" ].numberInt(); for( int j = -1; j < i; ++j ) { auto_ptr< DBClientCursor > c = _client.query( ns(), QUERY( "ts" << GTE << j ), 0, 0, 0, QueryOption_OplogReplay ); ASSERT( c->more() ); BSONObj next = c->next(); ASSERT( !next[ "ts" ].eoo() ); ASSERT_EQUALS( ( j > min ? j : min ), next[ "ts" ].numberInt() ); } cout << k << endl; } ASSERT_EQUALS( startNumCursors, numCursorsOpen() ); } }; /** * Check OplogReplay mode where query timestamp is earlier than the earliest * entry in the collection. */ class FindingStartStale : public CollectionBase { public: FindingStartStale() : CollectionBase( "findingstart" ) {} void run() { cout << "3 xcxcv" << endl; size_t startNumCursors = numCursorsOpen(); // Check OplogReplay mode with missing collection. auto_ptr< DBClientCursor > c0 = _client.query( ns(), QUERY( "ts" << GTE << 50 ), 0, 0, 0, QueryOption_OplogReplay ); ASSERT( !c0->more() ); BSONObj info; ASSERT( _client.runCommand( "unittests", BSON( "create" << "querytests.findingstart" << "capped" << true << "$nExtents" << 5 << "autoIndexId" << false ), info ) ); // Check OplogReplay mode with empty collection. auto_ptr< DBClientCursor > c = _client.query( ns(), QUERY( "ts" << GTE << 50 ), 0, 0, 0, QueryOption_OplogReplay ); ASSERT( !c->more() ); // Check with some docs in the collection. for( int i = 100; i < 150; _client.insert( ns(), BSON( "ts" << i++ ) ) ); c = _client.query( ns(), QUERY( "ts" << GTE << 50 ), 0, 0, 0, QueryOption_OplogReplay ); ASSERT( c->more() ); ASSERT_EQUALS( 100, c->next()[ "ts" ].numberInt() ); // Check that no persistent cursors outlast our queries above. ASSERT_EQUALS( startNumCursors, numCursorsOpen() ); } }; class WhatsMyUri : public CollectionBase { public: WhatsMyUri() : CollectionBase( "whatsmyuri" ) {} void run() { BSONObj result; _client.runCommand( "admin", BSON( "whatsmyuri" << 1 ), result ); SockAddr unknownAddress("0.0.0.0", 0); ASSERT_EQUALS( unknownAddress.toString(), result[ "you" ].str() ); } }; class CollectionInternalBase : public CollectionBase { public: CollectionInternalBase( const char *nsLeaf ) : CollectionBase( nsLeaf ), _scopedXact(&_txn, MODE_IX), _lk(_txn.lockState(), "unittests", MODE_X), _ctx(&_txn, ns()) { } private: ScopedTransaction _scopedXact; Lock::DBLock _lk; OldClientContext _ctx; }; class Exhaust : public CollectionInternalBase { public: Exhaust() : CollectionInternalBase( "exhaust" ) {} void run() { BSONObj info; ASSERT( _client.runCommand( "unittests", BSON( "create" << "querytests.exhaust" << "capped" << true << "size" << 8192 ), info ) ); _client.insert( ns(), BSON( "ts" << 0 ) ); Message message; assembleRequest( ns(), BSON( "ts" << GTE << 0 ), 0, 0, 0, QueryOption_OplogReplay | QueryOption_CursorTailable | QueryOption_Exhaust, message ); DbMessage dbMessage( message ); QueryMessage queryMessage( dbMessage ); Message result; string exhaust = runQuery(&_txn, queryMessage, NamespaceString(ns()), *cc().curop(), result); ASSERT( exhaust.size() ); ASSERT_EQUALS( string( ns() ), exhaust ); } }; class QueryCursorTimeout : public CollectionInternalBase { public: QueryCursorTimeout() : CollectionInternalBase( "querycursortimeout" ) {} void run() { for( int i = 0; i < 150; ++i ) { insert( ns(), BSONObj() ); } auto_ptr c = _client.query( ns(), Query() ); ASSERT( c->more() ); long long cursorId = c->getCursorId(); ClientCursor *clientCursor = 0; { AutoGetCollectionForRead ctx(&_txn, ns()); ClientCursorPin clientCursorPointer(ctx.getCollection()->getCursorManager(), cursorId); clientCursor = clientCursorPointer.c(); // clientCursorPointer destructor unpins the cursor. } ASSERT( clientCursor->shouldTimeout( 600001 ) ); } }; class QueryReadsAll : public CollectionBase { public: QueryReadsAll() : CollectionBase( "queryreadsall" ) {} void run() { for( int i = 0; i < 5; ++i ) { insert( ns(), BSONObj() ); } auto_ptr c = _client.query( ns(), Query(), 5 ); ASSERT( c->more() ); // With five results and a batch size of 5, no cursor is created. ASSERT_EQUALS( 0, c->getCursorId() ); } }; /** * Check that an attempt to kill a pinned cursor fails and produces an appropriate assertion. */ class KillPinnedCursor : public CollectionBase { public: KillPinnedCursor() : CollectionBase( "killpinnedcursor" ) { } void run() { _client.insert( ns(), vector( 3, BSONObj() ) ); auto_ptr cursor = _client.query( ns(), BSONObj(), 0, 0, 0, 0, 2 ); ASSERT_EQUALS( 2, cursor->objsLeftInBatch() ); long long cursorId = cursor->getCursorId(); { OldClientWriteContext ctx(&_txn, ns() ); ClientCursorPin pinCursor( ctx.db()->getCollection( ns() ) ->getCursorManager(), cursorId ); string expectedAssertion = str::stream() << "Cannot kill active cursor " << cursorId; ASSERT_THROWS_WHAT(CursorManager::eraseCursorGlobal(&_txn, cursorId), MsgAssertionException, expectedAssertion); } // Verify that the remaining document is read from the cursor. ASSERT_EQUALS( 3, cursor->itcount() ); } }; namespace queryobjecttests { class names1 { public: void run() { ASSERT_EQUALS( BSON( "x" << 1 ) , QUERY( "query" << BSON( "x" << 1 ) ).getFilter() ); ASSERT_EQUALS( BSON( "x" << 1 ) , QUERY( "$query" << BSON( "x" << 1 ) ).getFilter() ); } }; } class OrderingTest { public: void run() { { Ordering o = Ordering::make( BSON( "a" << 1 << "b" << -1 << "c" << 1 ) ); ASSERT_EQUALS( 1 , o.get(0) ); ASSERT_EQUALS( -1 , o.get(1) ); ASSERT_EQUALS( 1 , o.get(2) ); ASSERT( ! o.descending( 1 ) ); ASSERT( o.descending( 1 << 1 ) ); ASSERT( ! o.descending( 1 << 2 ) ); } { Ordering o = Ordering::make( BSON( "a.d" << 1 << "a" << 1 << "e" << -1 ) ); ASSERT_EQUALS( 1 , o.get(0) ); ASSERT_EQUALS( 1 , o.get(1) ); ASSERT_EQUALS( -1 , o.get(2) ); ASSERT( ! o.descending( 1 ) ); ASSERT( ! o.descending( 1 << 1 ) ); ASSERT( o.descending( 1 << 2 ) ); } } }; class All : public Suite { public: All() : Suite( "query" ) { } void setupTests() { add< FindingStart >(); add< FindOneOr >(); add< FindOneRequireIndex >(); add< FindOneEmptyObj >(); add< BoundedKey >(); add< GetMore >(); add< GetMoreKillOp >(); add< GetMoreInvalidRequest >(); add< PositiveLimit >(); add< ReturnOneOfManyAndTail >(); add< TailNotAtEnd >(); add< EmptyTail >(); add< TailableDelete >(); add< TailableDelete2 >(); add< TailableInsertDelete >(); add< TailCappedOnly >(); add< TailableQueryOnId >(); add< OplogReplayMode >(); add< OplogReplaySlaveReadTill >(); add< OplogReplayExplain >(); add< ArrayId >(); add< UnderscoreNs >(); add< EmptyFieldSpec >(); add< MultiNe >(); add< EmbeddedNe >(); add< EmbeddedNumericTypes >(); add< AutoResetIndexCache >(); add< UniqueIndex >(); add< UniqueIndexPreexistingData >(); add< SubobjectInArray >(); add< Size >(); add< FullArray >(); add< InsideArray >(); add< IndexInsideArrayCorrect >(); add< SubobjArr >(); add< MinMax >(); add< MatchCodeCodeWScope >(); add< MatchDBRefType >(); add< DirectLocking >(); add< FastCountIn >(); add< EmbeddedArray >(); add< DifferentNumbers >(); add< SymbolStringSame >(); add< TailableCappedRaceCondition >(); add< HelperTest >(); add< HelperByIdTest >(); add< FindingStartPartiallyFull >(); add< FindingStartStale >(); add< WhatsMyUri >(); add< Exhaust >(); add< QueryCursorTimeout >(); add< QueryReadsAll >(); add< KillPinnedCursor >(); add< queryobjecttests::names1 >(); add< OrderingTest >(); } }; SuiteInstance myall; } // namespace QueryTests