/** * 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 #include "mongo/client/dbclient_cursor.h" #include "mongo/db/catalog/collection.h" #include "mongo/db/client.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/exec/queued_data_stage.h" #include "mongo/db/json.h" #include "mongo/db/lasterror.h" #include "mongo/db/logical_clock.h" #include "mongo/db/logical_time.h" #include "mongo/db/namespace_string.h" #include "mongo/db/query/find.h" #include "mongo/db/service_context.h" #include "mongo/dbtests/dbtests.h" #include "mongo/util/timer.h" namespace { namespace QueryTests { using std::unique_ptr; using std::endl; using std::string; using std::vector; class Base { public: Base() : _lk(&_opCtx), _context(&_opCtx, ns()) { { WriteUnitOfWork wunit(&_opCtx); _database = _context.db(); _collection = _database->getCollection(&_opCtx, ns()); if (_collection) { _database->dropCollection(&_opCtx, ns()).transitional_ignore(); } _collection = _database->createCollection(&_opCtx, ns()); wunit.commit(); } addIndex(IndexSpec().addKey("a").unique(false)); } ~Base() { try { WriteUnitOfWork wunit(&_opCtx); uassertStatusOK(_database->dropCollection(&_opCtx, ns())); wunit.commit(); } catch (...) { FAIL("Exception while cleaning up collection"); } } protected: static const char* ns() { return "unittests.querytests"; } void addIndex(const IndexSpec& spec) { DBDirectClient client(&_opCtx); client.createIndex(ns(), spec); client.getLastError(); } void insert(const char* s) { insert(fromjson(s)); } void insert(const BSONObj& o) { WriteUnitOfWork wunit(&_opCtx); OpDebug* const nullOpDebug = nullptr; if (o["_id"].eoo()) { BSONObjBuilder b; OID oid; oid.init(); b.appendOID("_id", &oid); b.appendElements(o); _collection->insertDocument(&_opCtx, InsertStatement(b.obj()), nullOpDebug, false) .transitional_ignore(); } else { _collection->insertDocument(&_opCtx, InsertStatement(o), nullOpDebug, false) .transitional_ignore(); } wunit.commit(); } const ServiceContext::UniqueOperationContext _opCtxPtr = cc().makeOperationContext(); OperationContext& _opCtx = *_opCtxPtr; Lock::GlobalWrite _lk; OldClientContext _context; Database* _database; Collection* _collection; }; class FindOneOr : public Base { public: void run() { addIndex(IndexSpec().addKey("b").unique(false)); addIndex(IndexSpec().addKey("c").unique(false)); 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(&_opCtx, _collection, query, ret, true)); ASSERT_EQUALS(string("b"), ret.firstElement().fieldName()); // Cross check with findOne() returning location. ASSERT_BSONOBJ_EQ( ret, _collection->docFor(&_opCtx, Helpers::findOne(&_opCtx, _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(&_opCtx, _collection, query, ret, false)); // Check findOne() returning location, allowing unindexed scan. ASSERT_BSONOBJ_EQ( ret, _collection->docFor(&_opCtx, Helpers::findOne(&_opCtx, _collection, query, false)) .value()); // Check findOne() returning object, requiring indexed scan without index. ASSERT_THROWS(Helpers::findOne(&_opCtx, _collection, query, ret, true), AssertionException); // Check findOne() returning location, requiring indexed scan without index. ASSERT_THROWS(Helpers::findOne(&_opCtx, _collection, query, true), AssertionException); addIndex(IndexSpec().addKey("b").unique(false)); // Check findOne() returning object, requiring indexed scan with index. ASSERT(Helpers::findOne(&_opCtx, _collection, query, ret, true)); // Check findOne() returning location, requiring indexed scan with index. ASSERT_BSONOBJ_EQ( ret, _collection->docFor(&_opCtx, Helpers::findOne(&_opCtx, _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). Lock::GlobalWrite lk(&_opCtx); OldClientContext ctx(&_opCtx, "unittests.querytests"); { WriteUnitOfWork wunit(&_opCtx); Database* db = ctx.db(); if (db->getCollection(&_opCtx, ns())) { _collection = NULL; db->dropCollection(&_opCtx, ns()).transitional_ignore(); } _collection = db->createCollection(&_opCtx, ns(), CollectionOptions(), false); wunit.commit(); } ASSERT(_collection); DBDirectClient cl(&_opCtx); BSONObj info; bool ok = cl.runCommand("unittests", BSON("godinsert" << "querytests" << "obj" << BSONObj()), info); ASSERT(ok); insert(BSONObj()); BSONObj query; BSONObj ret; ASSERT(Helpers::findOne(&_opCtx, _collection, query, ret, false)); ASSERT(ret.isEmpty()); ASSERT_BSONOBJ_EQ( ret, _collection->docFor(&_opCtx, Helpers::findOne(&_opCtx, _collection, query, false)) .value()); } }; class ClientBase { public: ClientBase() : _client(&_opCtx) { mongo::LastError::get(_opCtx.getClient()).reset(); } virtual ~ClientBase() { mongo::LastError::get(_opCtx.getClient()).reset(); } 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(); } const ServiceContext::UniqueOperationContext _opCtxPtr = cc().makeOperationContext(); OperationContext& _opCtx = *_opCtxPtr; DBDirectClient _client; }; 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(&_opCtx, 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)); unique_ptr cursor = _client.query(NamespaceString(ns), BSONObj(), 2); long long cursorId = cursor->getCursorId(); cursor->decouple(); cursor.reset(); { // Check internal server handoff to getmore. dbtests::WriteContextForTests ctx(&_opCtx, ns); auto pinnedCursor = unittest::assertGet( ctx.getCollection()->getCursorManager()->pinCursor(&_opCtx, cursorId)); ASSERT_EQUALS(2, pinnedCursor.getCursor()->pos()); } cursor = _client.getMore(ns, cursorId); ASSERT(cursor->more()); ASSERT_EQUALS(3, cursor->next().getIntField("a")); } }; /** * Setting killAllOperations causes further getmores to fail. */ class GetMoreKillOp : public ClientBase { public: ~GetMoreKillOp() { getGlobalServiceContext()->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. unique_ptr cursor = _client.query(NamespaceString(ns), "", 0, 0, 0, 0, 200); // 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. getGlobalServiceContext()->setKillAllOperations(); ASSERT_THROWS_CODE(([&] { while (cursor->more()) { cursor->next(); } }()), AssertionException, ErrorCodes::InterruptedAtShutdown); // Revert the killop kill all flag. getGlobalServiceContext()->unsetKillAllOperations(); } }; /** * 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() { getGlobalServiceContext()->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. unique_ptr cursor = _client.query(NamespaceString(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. ASSERT_THROWS( _client.getMore("unittests.querytests.GetMoreInvalidRequest_WRONG_NAMESPACE_FOR_CURSOR", cursor->getCursorId()), AssertionException); // Check that the cursor still exists { AutoGetCollectionForReadCommand ctx(&_opCtx, NamespaceString(ns)); ASSERT(1 == ctx.getCollection()->getCursorManager()->numCursors()); ASSERT_OK( ctx.getCollection()->getCursorManager()->pinCursor(&_opCtx, cursorId).getStatus()); } // 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(NamespaceString(ns), BSONObj(), limit)->itcount(), limit); } void run() { for (int i = 0; i < 1000; i++) insert(ns, BSON(GENOID << "i" << i)); ASSERT_EQUALS(_client.query(NamespaceString(ns), BSONObj(), 1)->itcount(), 1); ASSERT_EQUALS(_client.query(NamespaceString(ns), BSONObj(), 10)->itcount(), 10); ASSERT_EQUALS(_client.query(NamespaceString(ns), BSONObj(), 101)->itcount(), 101); ASSERT_EQUALS(_client.query(NamespaceString(ns), BSONObj(), 999)->itcount(), 999); ASSERT_EQUALS(_client.query(NamespaceString(ns), BSONObj(), 1000)->itcount(), 1000); ASSERT_EQUALS(_client.query(NamespaceString(ns), BSONObj(), 1001)->itcount(), 1000); ASSERT_EQUALS(_client.query(NamespaceString(ns), BSONObj(), 0)->itcount(), 1000); } }; class TailNotAtEnd : public ClientBase { public: ~TailNotAtEnd() { _client.dropCollection("unittests.querytests.TailNotAtEnd"); } void run() { // Skip the test if the storage engine doesn't support capped collections. if (!getGlobalServiceContext()->getStorageEngine()->supportsCappedCollections()) { return; } 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)); unique_ptr c = _client.query(NamespaceString(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() { // Skip the test if the storage engine doesn't support capped collections. if (!getGlobalServiceContext()->getStorageEngine()->supportsCappedCollections()) { return; } const char* ns = "unittests.querytests.EmptyTail"; _client.createCollection(ns, 1900, true); unique_ptr c = _client.query(NamespaceString(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(NamespaceString(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() { // Skip the test if the storage engine doesn't support capped collections. if (!getGlobalServiceContext()->getStorageEngine()->supportsCappedCollections()) { return; } const char* ns = "unittests.querytests.TailableDelete"; _client.createCollection(ns, 8192, true, 2); insert(ns, BSON("a" << 0)); insert(ns, BSON("a" << 1)); unique_ptr c = _client.query(NamespaceString(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)); // We have overwritten the previous cursor position and should encounter a dead cursor. ASSERT_THROWS(c->more() ? c->nextSafe() : BSONObj(), AssertionException); } }; class TailableDelete2 : public ClientBase { public: ~TailableDelete2() { _client.dropCollection("unittests.querytests.TailableDelete"); } void run() { // Skip the test if the storage engine doesn't support capped collections. if (!getGlobalServiceContext()->getStorageEngine()->supportsCappedCollections()) { return; } const char* ns = "unittests.querytests.TailableDelete"; _client.createCollection(ns, 8192, true, 2); insert(ns, BSON("a" << 0)); insert(ns, BSON("a" << 1)); unique_ptr c = _client.query(NamespaceString(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)); // We have overwritten the previous cursor position and should encounter a dead cursor. ASSERT_THROWS(c->more() ? c->nextSafe() : BSONObj(), AssertionException); } }; class TailableInsertDelete : public ClientBase { public: ~TailableInsertDelete() { _client.dropCollection("unittests.querytests.TailableInsertDelete"); } void run() { // Skip the test if the storage engine doesn't support capped collections. if (!getGlobalServiceContext()->getStorageEngine()->supportsCappedCollections()) { return; } const char* ns = "unittests.querytests.TailableInsertDelete"; _client.createCollection(ns, 1330, true); insert(ns, BSON("a" << 0)); insert(ns, BSON("a" << 1)); unique_ptr c = _client.query(NamespaceString(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()); ASSERT_THROWS( _client.query(NamespaceString(ns), BSONObj(), 0, 0, 0, QueryOption_CursorTailable), AssertionException); } }; 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() { // Skip the test if the storage engine doesn't support capped collections. if (!getGlobalServiceContext()->getStorageEngine()->supportsCappedCollections()) { return; } 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); unique_ptr c1 = _client.query( NamespaceString(ns), QUERY("a" << GT << -1), 0, 0, 0, QueryOption_CursorTailable); OID id; id.init("000000000000000000000000"); unique_ptr c2 = _client.query( NamespaceString(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() { // Skip the test if the storage engine doesn't support capped collections. if (!getGlobalServiceContext()->getStorageEngine()->supportsCappedCollections()) { return; } const char* ns = "unittests.querytests.OplogReplayMode"; // Create a capped collection of size 10. _client.dropCollection(ns); _client.createCollection(ns, 10, true); insert(ns, BSON("ts" << Timestamp(1000, 0))); insert(ns, BSON("ts" << Timestamp(1000, 1))); insert(ns, BSON("ts" << Timestamp(1000, 2))); unique_ptr c = _client.query(NamespaceString(ns), QUERY("ts" << GT << Timestamp(1000, 1)).hint(BSON("$natural" << 1)), 0, 0, 0, QueryOption_OplogReplay); ASSERT(c->more()); ASSERT_EQUALS(2u, c->next()["ts"].timestamp().getInc()); ASSERT(!c->more()); insert(ns, BSON("ts" << Timestamp(1000, 3))); c = _client.query(NamespaceString(ns), QUERY("ts" << GT << Timestamp(1000, 1)).hint(BSON("$natural" << 1)), 0, 0, 0, QueryOption_OplogReplay); ASSERT(c->more()); ASSERT_EQUALS(2u, c->next()["ts"].timestamp().getInc()); ASSERT(c->more()); } }; class OplogReplayExplain : public ClientBase { public: ~OplogReplayExplain() { _client.dropCollection("unittests.querytests.OplogReplayExplain"); } void run() { // Skip the test if the storage engine doesn't support capped collections. if (!getGlobalServiceContext()->getStorageEngine()->supportsCappedCollections()) { return; } const char* ns = "unittests.querytests.OplogReplayExplain"; // Create a capped collection of size 10. _client.dropCollection(ns); _client.createCollection(ns, 10, true); insert(ns, BSON("ts" << Timestamp(1000, 0))); insert(ns, BSON("ts" << Timestamp(1000, 1))); insert(ns, BSON("ts" << Timestamp(1000, 2))); unique_ptr c = _client.query( NamespaceString(ns), QUERY("ts" << GT << Timestamp(1000, 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")) << explainObj; 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(&_opCtx, 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(&_opCtx, 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(&_opCtx, 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(&_opCtx, 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(&_opCtx, 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(&_opCtx, 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(&_opCtx, ns, BSON("a" << 1), true)); } }; 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(&_opCtx, ns, BSON("a" << 1))); ASSERT(_client .query(NamespaceString(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(NamespaceString(ns), Query("{a:[1,2,3]}"))->more()); ASSERT_OK(dbtests::createIndex(&_opCtx, ns, BSON("a" << 1))); ASSERT( _client.query(NamespaceString(ns), Query("{a:{$in:[1,[1,2,3]]}}").hint(BSON("a" << 1))) ->more()); ASSERT(_client.query(NamespaceString(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(&_opCtx, ns, BSON("a" << 1))); check("a"); // SERVER-146 } private: void check(const string& hintField) { const char* ns = "unittests.querytests.InsideArray"; ASSERT(_client.query(NamespaceString(ns), Query("{a:[[1],2]}").hint(BSON(hintField << 1))) ->more()); ASSERT(_client.query(NamespaceString(ns), Query("{a:[1]}").hint(BSON(hintField << 1))) ->more()); ASSERT( _client.query(NamespaceString(ns), Query("{a:2}").hint(BSON(hintField << 1)))->more()); ASSERT( !_client.query(NamespaceString(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(&_opCtx, ns, BSON("a" << 1))); ASSERT_EQUALS(1, _client.query(NamespaceString(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(&_opCtx, ns, BSON("a" << 1))); check("a"); } private: void check(const string& hintField) { const char* ns = "unittests.querytests.SubobjArr"; ASSERT(_client.query(NamespaceString(ns), Query("{'a.b':1}").hint(BSON(hintField << 1))) ->more()); ASSERT(_client.query(NamespaceString(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(&_opCtx, 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(NamespaceString(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]); unique_ptr 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: unique_ptr 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(NamespaceString(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(unique_ptr 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(&_opCtx, _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(&_opCtx, _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() { Lock::GlobalWrite lk(&_opCtx); OldClientContext ctx(&_opCtx, "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(&_opCtx, 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) { unique_ptr cursor = _client.query(NamespaceString(ns), Query().sort("7")); while (cursor->more()) { BSONObj o = cursor->next(); verify(o.valid(BSONVersion::kLatest)); } } 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(&_opCtx, 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() { AutoGetCollectionForReadCommand ctx(&_opCtx, NamespaceString(_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(&_opCtx, 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() { // Skip the test if the storage engine doesn't support capped collections. if (!getGlobalServiceContext()->getStorageEngine()->supportsCappedCollections()) { return; } string err; dbtests::WriteContextForTests ctx(&_opCtx, ns()); // note that extents are always at least 4KB now - so this will get rounded up // a bit. { WriteUnitOfWork wunit(&_opCtx); CollectionOptions collectionOptions; ASSERT_OK( collectionOptions.parse(fromjson("{ capped : true, size : 2000, max: 10000 }"), CollectionOptions::parseForCommand)); ASSERT( Database::userCreateNS(&_opCtx, ctx.db(), ns(), collectionOptions, false).isOK()); wunit.commit(); } for (int i = 0; i < 200; i++) { insertNext(); ASSERT(count() < 90); } int a = count(); unique_ptr c = _client.query(NamespaceString(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(); } ASSERT_THROWS(([&] { while (c->more()) { c->nextSafe(); } }()), AssertionException); } 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() { dbtests::WriteContextForTests ctx(&_opCtx, 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(&_opCtx, ctx.getCollection(), BSON("_id" << 20), res, true)); ASSERT_EQUALS(40, res["x"].numberInt()); ASSERT(Helpers::findById(&_opCtx, ctx.db(), ns(), BSON("_id" << 20), res)); ASSERT_EQUALS(40, res["x"].numberInt()); ASSERT(!Helpers::findById(&_opCtx, 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 < n; i++) { ASSERT( Helpers::findOne(&_opCtx, ctx.getCollection(), BSON("_id" << 20), res, true)); } slow = t.micros(); } { Timer t; for (int i = 0; i < n; i++) { ASSERT(Helpers::findById(&_opCtx, ctx.db(), ns(), BSON("_id" << 20), res)); } fast = t.micros(); } std::cout << "HelperTest slow:" << slow << " fast:" << fast << endl; } }; class HelperByIdTest : public CollectionBase { public: HelperByIdTest() : CollectionBase("helpertestbyid") {} void run() { dbtests::WriteContextForTests ctx(&_opCtx, ns()); for (int i = 0; i < 1000; i++) { insert(ns(), BSON("_id" << i << "x" << i * 2)); } for (int i = 0; i < 1000; i += 2) { _client.remove(ns(), BSON("_id" << i)); } BSONObj res; for (int i = 0; i < 1000; i++) { bool found = Helpers::findById(&_opCtx, ctx.db(), ns(), BSON("_id" << i), res); ASSERT_EQUALS(i % 2, int(found)); } } }; class ClientCursorTest : public CollectionBase { ClientCursorTest() : CollectionBase("clientcursortest") {} void run() { dbtests::WriteContextForTests ctx(&_opCtx, ns()); for (int i = 0; i < 1000; i++) { insert(ns(), BSON("_id" << i << "x" << i * 2)); } } }; class FindingStart : public CollectionBase { public: FindingStart() : CollectionBase("findingstart") {} static const char* ns() { return "local.querytests.findingstart"; } void run() { // Skip the test if the storage engine doesn't support capped collections. if (!getGlobalServiceContext()->getStorageEngine()->supportsCappedCollections()) { return; } BSONObj info; // Must use local db so that the collection is not replicated, to allow autoIndexId:false. ASSERT(_client.runCommand("local", BSON("create" << "querytests.findingstart" << "capped" << true << "$nExtents" << 5 << "autoIndexId" << false), info)); unsigned i = 0; int max = 1; while (1) { int oldCount = count(); _client.insert(ns(), BSON("ts" << Timestamp(1000, i++))); int newCount = count(); if (oldCount == newCount || newCount < max) break; if (newCount > max) max = newCount; } for (int k = 0; k < 5; ++k) { _client.insert(ns(), BSON("ts" << Timestamp(1000, i++))); unsigned min = _client.query(NamespaceString(ns()), Query().sort(BSON("$natural" << 1))) ->next()["ts"] .timestamp() .getInc(); for (unsigned j = -1; j < i; ++j) { unique_ptr c = _client.query(NamespaceString(ns()), QUERY("ts" << GTE << Timestamp(1000, 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"].timestamp().getInc()); } } ASSERT(_client.dropCollection(ns())); } }; class FindingStartPartiallyFull : public CollectionBase { public: FindingStartPartiallyFull() : CollectionBase("findingstart") {} static const char* ns() { return "local.querytests.findingstart"; } void run() { // Skip the test if the storage engine doesn't support capped collections. if (!getGlobalServiceContext()->getStorageEngine()->supportsCappedCollections()) { return; } size_t startNumCursors = numCursorsOpen(); BSONObj info; // Must use local db so that the collection is not replicated, to allow autoIndexId:false. ASSERT(_client.runCommand("local", BSON("create" << "querytests.findingstart" << "capped" << true << "$nExtents" << 5 << "autoIndexId" << false), info)); unsigned i = 0; for (; i < 150; _client.insert(ns(), BSON("ts" << Timestamp(1000, i++)))) ; for (int k = 0; k < 5; ++k) { _client.insert(ns(), BSON("ts" << Timestamp(1000, i++))); unsigned min = _client.query(NamespaceString(ns()), Query().sort(BSON("$natural" << 1))) ->next()["ts"] .timestamp() .getInc(); for (unsigned j = -1; j < i; ++j) { unique_ptr c = _client.query(NamespaceString(ns()), QUERY("ts" << GTE << Timestamp(1000, 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"].timestamp().getInc()); } } ASSERT_EQUALS(startNumCursors, numCursorsOpen()); ASSERT(_client.dropCollection(ns())); } }; /** * Check OplogReplay mode where query timestamp is earlier than the earliest * entry in the collection. */ class FindingStartStale : public CollectionBase { public: FindingStartStale() : CollectionBase("findingstart") {} static const char* ns() { return "local.querytests.findingstart"; } void run() { // Skip the test if the storage engine doesn't support capped collections. if (!getGlobalServiceContext()->getStorageEngine()->supportsCappedCollections()) { return; } size_t startNumCursors = numCursorsOpen(); // Check OplogReplay mode with missing collection. unique_ptr c0 = _client.query(NamespaceString(ns()), QUERY("ts" << GTE << Timestamp(1000, 50)), 0, 0, 0, QueryOption_OplogReplay); ASSERT(!c0->more()); BSONObj info; // Must use local db so that the collection is not replicated, to allow autoIndexId:false. ASSERT(_client.runCommand("local", BSON("create" << "querytests.findingstart" << "capped" << true << "$nExtents" << 5 << "autoIndexId" << false), info)); // Check OplogReplay mode with empty collection. unique_ptr c = _client.query(NamespaceString(ns()), QUERY("ts" << GTE << Timestamp(1000, 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" << Timestamp(1000, i++)))) ; c = _client.query(NamespaceString(ns()), QUERY("ts" << GTE << Timestamp(1000, 50)), 0, 0, 0, QueryOption_OplogReplay); ASSERT(c->more()); ASSERT_EQUALS(100u, c->next()["ts"].timestamp().getInc()); // Check that no persistent cursors outlast our queries above. ASSERT_EQUALS(startNumCursors, numCursorsOpen()); ASSERT(_client.dropCollection(ns())); } }; class WhatsMyUri : public CollectionBase { public: WhatsMyUri() : CollectionBase("whatsmyuri") {} void run() { BSONObj result; _client.runCommand("admin", BSON("whatsmyuri" << 1), result); ASSERT_EQUALS("", result["you"].str()); } }; class QueryByUuid : public CollectionBase { public: QueryByUuid() : CollectionBase("QueryByUuid") {} void run() { CollectionOptions coll_opts; coll_opts.uuid = UUID::gen(); { Lock::GlobalWrite lk(&_opCtx); OldClientContext context(&_opCtx, ns()); WriteUnitOfWork wunit(&_opCtx); context.db()->createCollection(&_opCtx, ns(), coll_opts, false); wunit.commit(); } insert(ns(), BSON("a" << 1)); insert(ns(), BSON("a" << 2)); insert(ns(), BSON("a" << 3)); unique_ptr cursor = _client.query(NamespaceStringOrUUID("unittests", *coll_opts.uuid), BSONObj()); ASSERT_EQUALS(string(ns()), cursor->getns()); for (int i = 1; i <= 3; ++i) { ASSERT(cursor->more()); BSONObj obj(cursor->next()); ASSERT_EQUALS(obj["a"].Int(), i); } ASSERT(!cursor->more()); } }; class CollectionInternalBase : public CollectionBase { public: CollectionInternalBase(const char* nsLeaf) : CollectionBase(nsLeaf), _lk(&_opCtx, "unittests", MODE_X), _ctx(&_opCtx, ns()) {} private: Lock::DBLock _lk; OldClientContext _ctx; }; class Exhaust : public CollectionInternalBase { public: Exhaust() : CollectionInternalBase("exhaust") {} void run() { // Skip the test if the storage engine doesn't support capped collections. if (!getGlobalServiceContext()->getStorageEngine()->supportsCappedCollections()) { return; } BSONObj info; ASSERT(_client.runCommand("unittests", BSON("create" << "querytests.exhaust" << "capped" << true << "size" << 8192), info)); _client.insert(ns(), BSON("ts" << Timestamp(1000, 0))); Message message; assembleQueryRequest(ns(), BSON("ts" << GTE << Timestamp(1000, 0)), 0, 0, 0, QueryOption_OplogReplay | QueryOption_CursorTailable | QueryOption_Exhaust, message); DbMessage dbMessage(message); QueryMessage queryMessage(dbMessage); Message result; string exhaust = runQuery(&_opCtx, queryMessage, NamespaceString(ns()), result); ASSERT(exhaust.size()); ASSERT_EQUALS(string(ns()), exhaust); } }; class QueryReadsAll : public CollectionBase { public: QueryReadsAll() : CollectionBase("queryreadsall") {} void run() { for (int i = 0; i < 5; ++i) { insert(ns(), BSONObj()); } { // With five results and a batch size of 5, a cursor is created since we don't know // there are no more results. std::unique_ptr c = _client.query(NamespaceString(ns()), Query(), 5); ASSERT(c->more()); ASSERT_NE(0, c->getCursorId()); for (int i = 0; i < 5; ++i) { ASSERT(c->more()); c->next(); } ASSERT(!c->more()); } { // With a batchsize of 6 we know there are no more results so we don't create a // cursor. std::unique_ptr c = _client.query(NamespaceString(ns()), Query(), 6); ASSERT(c->more()); ASSERT_EQ(0, c->getCursorId()); } } }; namespace queryobjecttests { class names1 { public: void run() { ASSERT_BSONOBJ_EQ(BSON("x" << 1), QUERY("query" << BSON("x" << 1)).getFilter()); ASSERT_BSONOBJ_EQ(BSON("x" << 1), QUERY("$query" << BSON("x" << 1)).getFilter()); } }; } // namespace queryobjecttests 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(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); } }; SuiteInstance myall; } // namespace QueryTests } // namespace