//@file indexupdatetests.cpp : mongo/db/index_update.{h,cpp} tests /** * Copyright (C) 2012 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 #include "mongo/db/catalog/collection.h" #include "mongo/db/catalog/index_catalog.h" #include "mongo/db/catalog/index_create.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/index/index_descriptor.h" #include "mongo/db/operation_context_impl.h" #include "mongo/platform/cstdint.h" #include "mongo/dbtests/dbtests.h" namespace IndexUpdateTests { using boost::scoped_ptr; static const char* const _ns = "unittests.indexupdate"; /** * Test fixture for a write locked test using collection _ns. Includes functionality to * partially construct a new IndexDetails in a manner that supports proper cleanup in * dropCollection(). */ class IndexBuildBase { public: IndexBuildBase() : _ctx(&_txn, _ns), _client(&_txn) { _client.createCollection( _ns ); } ~IndexBuildBase() { _client.dropCollection( _ns ); getGlobalEnvironment()->unsetKillAllOperations(); } Collection* collection() { return _ctx.getCollection(); } protected: // QUERY_MIGRATION #if 0 /** @return IndexDetails for a new index on a:1, with the info field populated. */ IndexDescriptor* addIndexWithInfo() { BSONObj indexInfo = BSON( "v" << 1 << "key" << BSON( "a" << 1 ) << "ns" << _ns << "name" << "a_1" ); int32_t lenWHdr = indexInfo.objsize() + Record::HeaderSize; const char* systemIndexes = "unittests.system.indexes"; RecordId infoLoc = allocateSpaceForANewRecord( systemIndexes, nsdetails( systemIndexes ), lenWHdr, false ); Record* infoRecord = reinterpret_cast( getDur().writingPtr( infoLoc.rec(), lenWHdr ) ); memcpy( infoRecord->data(), indexInfo.objdata(), indexInfo.objsize() ); addRecordToRecListInExtent( infoRecord, infoLoc ); IndexCatalog::IndexBuildBlock blk( collection()->getIndexCatalog(), "a_1", infoLoc ); blk.success(); return collection()->getIndexCatalog()->findIndexByName( "a_1" ); } #endif Status createIndex(const std::string& dbname, const BSONObj& indexSpec); bool buildIndexInterrupted(const BSONObj& key, bool allowInterruption) { try { MultiIndexBlock indexer(&_txn, collection()); if (allowInterruption) indexer.allowInterruption(); uassertStatusOK(indexer.init(key)); uassertStatusOK(indexer.insertAllDocumentsInCollection()); WriteUnitOfWork wunit(&_txn); indexer.commit(); wunit.commit(); } catch (const DBException& e) { if (ErrorCodes::isInterruption(ErrorCodes::Error(e.getCode()))) return true; throw; } return false; } OperationContextImpl _txn; Client::WriteContext _ctx; DBDirectClient _client; }; /** addKeysToPhaseOne() adds keys from a collection's documents to an external sorter. */ // QUERY_MIGRATION #if 0 class AddKeysToPhaseOne : public IndexBuildBase { public: void run() { // Add some data to the collection. int32_t nDocs = 130; for( int32_t i = 0; i < nDocs; ++i ) { _client.insert( _ns, BSON( "a" << i ) ); } IndexDescriptor* id = addIndexWithInfo(); // Create a SortPhaseOne. SortPhaseOne phaseOne; ProgressMeterHolder pm (txn->setMessage("AddKeysToPhaseOne", "AddKeysToPhaseOne Progress", nDocs, nDocs)); // Add keys to phaseOne. BtreeBasedBuilder::addKeysToPhaseOne( collection(), id, BSON( "a" << 1 ), &phaseOne, pm.get(), true ); // Keys for all documents were added to phaseOne. ASSERT_EQUALS( static_cast( nDocs ), phaseOne.n ); } }; /** addKeysToPhaseOne() aborts if the current operation is killed. */ class InterruptAddKeysToPhaseOne : public IndexBuildBase { public: InterruptAddKeysToPhaseOne( bool mayInterrupt ) : _mayInterrupt( mayInterrupt ) { } void run() { // It's necessary to index sufficient keys that a RARELY condition will be triggered. int32_t nDocs = 130; // Add some data to the collection. for( int32_t i = 0; i < nDocs; ++i ) { _client.insert( _ns, BSON( "a" << i ) ); } IndexDescriptor* id = addIndexWithInfo(); // Create a SortPhaseOne. SortPhaseOne phaseOne; ProgressMeterHolder pm (txn->setMessage("InterruptAddKeysToPhaseOne", "InterruptAddKeysToPhaseOne Progress", nDocs, nDocs)); // Register a request to kill the current operation. txn.getCurOp()->kill(); if ( _mayInterrupt ) { // Add keys to phaseOne. ASSERT_THROWS( BtreeBasedBuilder::addKeysToPhaseOne( collection(), id, BSON( "a" << 1 ), &phaseOne, pm.get(), _mayInterrupt ), UserException ); // Not all keys were added to phaseOne due to the interrupt. ASSERT( static_cast( nDocs ) > phaseOne.n ); } else { // Add keys to phaseOne. BtreeBasedBuilder::addKeysToPhaseOne( collection(), id, BSON( "a" << 1 ), &phaseOne, pm.get(), _mayInterrupt ); // All keys were added to phaseOne despite to the kill request, because // mayInterrupt == false. ASSERT_EQUALS( static_cast( nDocs ), phaseOne.n ); } } private: bool _mayInterrupt; }; #endif // QUERY_MIGRATION #if 0 /** buildBottomUpPhases2And3() builds a btree from the keys in an external sorter. */ class BuildBottomUp : public IndexBuildBase { public: void run() { IndexDescriptor* id = addIndexWithInfo(); // Create a SortPhaseOne. SortPhaseOne phaseOne; phaseOne.sorter.reset( new BSONObjExternalSorter(_aFirstSort)); // Add index keys to the phaseOne. int32_t nKeys = 130; for( int32_t i = 0; i < nKeys; ++i ) { phaseOne.sorter->add( BSON( "a" << i ), /* dummy disk loc */ RecordId(), false ); } phaseOne.nkeys = phaseOne.n = nKeys; phaseOne.sorter->sort( false ); // Set up remaining arguments. set dups; CurOp* op = txn.getCurOp(); ProgressMeterHolder pm (op->setMessage("BuildBottomUp", "BuildBottomUp Progress", nKeys, nKeys)); pm.finished(); Timer timer; // The index's root has not yet been set. ASSERT( id->getHead().isNull() ); // Finish building the index. buildBottomUpPhases2And3( true, id, *phaseOne.sorter, false, dups, op, &phaseOne, pm, timer, true ); // The index's root is set after the build is complete. ASSERT( !id->getHead().isNull() ); // Create a cursor over the index. scoped_ptr cursor( BtreeCursor::make( nsdetails( _ns ), id->getOnDisk(), BSON( "" << -1 ), // startKey below minimum key. BSON( "" << nKeys ), // endKey above maximum key. true, // endKeyInclusive true. 1 // direction forward. ) ); // Check that the keys in the index are the expected ones. int32_t expectedKey = 0; for( ; cursor->ok(); cursor->advance(), ++expectedKey ) { ASSERT_EQUALS( expectedKey, cursor->currKey().firstElement().number() ); } ASSERT_EQUALS( nKeys, expectedKey ); } }; #endif // QUERY_MIGRATION #if 0 /** buildBottomUpPhases2And3() aborts if the current operation is interrupted. */ class InterruptBuildBottomUp : public IndexBuildBase { public: InterruptBuildBottomUp( bool mayInterrupt ) : _mayInterrupt( mayInterrupt ) { } void run() { IndexDescriptor* id = addIndexWithInfo(); // Create a SortPhaseOne. SortPhaseOne phaseOne; phaseOne.sorter.reset(new BSONObjExternalSorter(_aFirstSort)); // It's necessary to index sufficient keys that a RARELY condition will be triggered, // but few enough keys that the btree builder will not create an internal node and check // for an interrupt internally (which would cause this test to pass spuriously). int32_t nKeys = 130; // Add index keys to the phaseOne. for( int32_t i = 0; i < nKeys; ++i ) { phaseOne.sorter->add( BSON( "a" << i ), /* dummy disk loc */ RecordId(), false ); } phaseOne.nkeys = phaseOne.n = nKeys; phaseOne.sorter->sort( false ); // Set up remaining arguments. set dups; CurOp* op = txn.getCurOp(); ProgressMeterHolder pm (op->setMessage("InterruptBuildBottomUp", "InterruptBuildBottomUp Progress", nKeys, nKeys)); pm.finished(); Timer timer; // The index's root has not yet been set. ASSERT( id->getHead().isNull() ); // Register a request to kill the current operation. txn.getCurOp()->kill(); if ( _mayInterrupt ) { // The build is aborted due to the kill request. ASSERT_THROWS ( buildBottomUpPhases2And3( true, id, *phaseOne.sorter, false, dups, op, &phaseOne, pm, timer, _mayInterrupt ), UserException ); // The root of the index is not set because the build did not complete. ASSERT( id->getHead().isNull() ); } else { // The build is aborted despite the kill request because mayInterrupt == false. buildBottomUpPhases2And3( true, id, *phaseOne.sorter, false, dups, op, &phaseOne, pm, timer, _mayInterrupt ); // The index's root is set after the build is complete. ASSERT( !id->getHead().isNull() ); } } private: bool _mayInterrupt; }; #endif /** Index creation ignores unique constraints when told to. */ template class InsertBuildIgnoreUnique : public IndexBuildBase { public: void run() { // Create a new collection. Database* db = _ctx.db(); Collection* coll; { WriteUnitOfWork wunit(&_txn); db->dropCollection( &_txn, _ns ); coll = db->createCollection( &_txn, _ns ); coll->insertDocument( &_txn, BSON( "_id" << 1 << "a" << "dup" ), true ); coll->insertDocument( &_txn, BSON( "_id" << 2 << "a" << "dup" ), true ); wunit.commit(); } MultiIndexBlock indexer(&_txn, coll); indexer.allowBackgroundBuilding(); indexer.allowInterruption(); indexer.ignoreUniqueConstraint(); const BSONObj spec = BSON("name" << "a" << "ns" << coll->ns().ns() << "key" << BSON("a" << 1) << "unique" << true << "background" << background); ASSERT_OK(indexer.init(spec)); ASSERT_OK(indexer.insertAllDocumentsInCollection()); WriteUnitOfWork wunit(&_txn); indexer.commit(); wunit.commit(); } }; /** Index creation enforces unique constraints unless told not to. */ template class InsertBuildEnforceUnique : public IndexBuildBase { public: void run() { // Create a new collection. Database* db = _ctx.db(); Collection* coll; { WriteUnitOfWork wunit(&_txn); db->dropCollection( &_txn, _ns ); coll = db->createCollection( &_txn, _ns ); coll->insertDocument( &_txn, BSON( "_id" << 1 << "a" << "dup" ), true ); coll->insertDocument( &_txn, BSON( "_id" << 2 << "a" << "dup" ), true ); wunit.commit(); } MultiIndexBlock indexer(&_txn, coll); indexer.allowBackgroundBuilding(); indexer.allowInterruption(); // indexer.ignoreUniqueConstraint(); // not calling this const BSONObj spec = BSON("name" << "a" << "ns" << coll->ns().ns() << "key" << BSON("a" << 1) << "unique" << true << "background" << background); ASSERT_OK(indexer.init(spec)); const Status status = indexer.insertAllDocumentsInCollection(); ASSERT_EQUALS(status.code(), ErrorCodes::DuplicateKey); } }; /** Index creation fills a passed-in set of dups rather than failing. */ template class InsertBuildFillDups : public IndexBuildBase { public: void run() { // Create a new collection. Database* db = _ctx.db(); Collection* coll; RecordId loc1; RecordId loc2; { WriteUnitOfWork wunit(&_txn); db->dropCollection( &_txn, _ns ); coll = db->createCollection( &_txn, _ns ); StatusWith swLoc1 = coll->insertDocument(&_txn, BSON("_id" << 1 << "a" << "dup"), true); StatusWith swLoc2 = coll->insertDocument(&_txn, BSON("_id" << 2 << "a" << "dup"), true); ASSERT_OK(swLoc1.getStatus()); ASSERT_OK(swLoc2.getStatus()); loc1 = swLoc1.getValue(); loc2 = swLoc2.getValue(); wunit.commit(); } MultiIndexBlock indexer(&_txn, coll); indexer.allowBackgroundBuilding(); indexer.allowInterruption(); // indexer.ignoreUniqueConstraint(); // not calling this const BSONObj spec = BSON("name" << "a" << "ns" << coll->ns().ns() << "key" << BSON("a" << 1) << "unique" << true << "background" << background); ASSERT_OK(indexer.init(spec)); std::set dups; ASSERT_OK(indexer.insertAllDocumentsInCollection(&dups)); // either loc1 or loc2 should be in dups but not both. ASSERT_EQUALS(dups.size(), 1U); ASSERT(dups.count(loc1) || dups.count(loc2)); } }; /** Index creation is killed if mayInterrupt is true. */ class InsertBuildIndexInterrupt : public IndexBuildBase { public: void run() { // Create a new collection. Database* db = _ctx.db(); Collection* coll; { WriteUnitOfWork wunit(&_txn); db->dropCollection( &_txn, _ns ); coll = db->createCollection( &_txn, _ns ); // Drop all indexes including id index. coll->getIndexCatalog()->dropAllIndexes(&_txn, true ); // Insert some documents with enforceQuota=true. int32_t nDocs = 1000; for( int32_t i = 0; i < nDocs; ++i ) { coll->insertDocument( &_txn, BSON( "a" << i ), true ); } wunit.commit(); } // Initialize curop. _txn.getCurOp()->reset(); // Request an interrupt. getGlobalEnvironment()->setKillAllOperations(); BSONObj indexInfo = BSON( "key" << BSON( "a" << 1 ) << "ns" << _ns << "name" << "a_1" ); // The call is interrupted because mayInterrupt == true. ASSERT_TRUE(buildIndexInterrupted(indexInfo, true)); // only want to interrupt the index build getGlobalEnvironment()->unsetKillAllOperations(); // The new index is not listed in the index catalog because the index build failed. ASSERT( !coll->getIndexCatalog()->findIndexByName( &_txn, "a_1" ) ); } }; /** Index creation is not killed if mayInterrupt is false. */ class InsertBuildIndexInterruptDisallowed : public IndexBuildBase { public: void run() { // Create a new collection. Database* db = _ctx.db(); Collection* coll; { WriteUnitOfWork wunit(&_txn); db->dropCollection( &_txn, _ns ); coll = db->createCollection( &_txn, _ns ); coll->getIndexCatalog()->dropAllIndexes(&_txn, true ); // Insert some documents. int32_t nDocs = 1000; for( int32_t i = 0; i < nDocs; ++i ) { coll->insertDocument( &_txn, BSON( "a" << i ), true ); } wunit.commit(); } // Initialize curop. _txn.getCurOp()->reset(); // Request an interrupt. getGlobalEnvironment()->setKillAllOperations(); BSONObj indexInfo = BSON( "key" << BSON( "a" << 1 ) << "ns" << _ns << "name" << "a_1" ); // The call is not interrupted because mayInterrupt == false. ASSERT_FALSE(buildIndexInterrupted(indexInfo, false)); // only want to interrupt the index build getGlobalEnvironment()->unsetKillAllOperations(); // The new index is listed in the index catalog because the index build completed. ASSERT( coll->getIndexCatalog()->findIndexByName( &_txn, "a_1" ) ); } }; /** Index creation is killed when building the _id index. */ class InsertBuildIdIndexInterrupt : public IndexBuildBase { public: void run() { // Recreate the collection as capped, without an _id index. Database* db = _ctx.db(); Collection* coll; { WriteUnitOfWork wunit(&_txn); db->dropCollection( &_txn, _ns ); CollectionOptions options; options.capped = true; options.cappedSize = 10 * 1024; coll = db->createCollection( &_txn, _ns, options ); coll->getIndexCatalog()->dropAllIndexes(&_txn, true ); // Insert some documents. int32_t nDocs = 1000; for( int32_t i = 0; i < nDocs; ++i ) { coll->insertDocument( &_txn, BSON( "_id" << i ), true ); } wunit.commit(); } // Initialize curop. _txn.getCurOp()->reset(); // Request an interrupt. getGlobalEnvironment()->setKillAllOperations(); BSONObj indexInfo = BSON( "key" << BSON( "_id" << 1 ) << "ns" << _ns << "name" << "_id_" ); // The call is interrupted because mayInterrupt == true. ASSERT_TRUE(buildIndexInterrupted(indexInfo, true)); // only want to interrupt the index build getGlobalEnvironment()->unsetKillAllOperations(); // The new index is not listed in the index catalog because the index build failed. ASSERT( !coll->getIndexCatalog()->findIndexByName( &_txn, "_id_" ) ); } }; /** Index creation is not killed when building the _id index if mayInterrupt is false. */ class InsertBuildIdIndexInterruptDisallowed : public IndexBuildBase { public: void run() { // Recreate the collection as capped, without an _id index. Database* db = _ctx.db(); Collection* coll; { WriteUnitOfWork wunit(&_txn); db->dropCollection( &_txn, _ns ); CollectionOptions options; options.capped = true; options.cappedSize = 10 * 1024; coll = db->createCollection( &_txn, _ns, options ); coll->getIndexCatalog()->dropAllIndexes(&_txn, true ); // Insert some documents. int32_t nDocs = 1000; for( int32_t i = 0; i < nDocs; ++i ) { coll->insertDocument( &_txn, BSON( "_id" << i ), true ); } wunit.commit(); } // Initialize curop. _txn.getCurOp()->reset(); // Request an interrupt. getGlobalEnvironment()->setKillAllOperations(); BSONObj indexInfo = BSON( "key" << BSON( "_id" << 1 ) << "ns" << _ns << "name" << "_id_" ); // The call is not interrupted because mayInterrupt == false. ASSERT_FALSE(buildIndexInterrupted(indexInfo, false)); // only want to interrupt the index build getGlobalEnvironment()->unsetKillAllOperations(); // The new index is listed in the index catalog because the index build succeeded. ASSERT( coll->getIndexCatalog()->findIndexByName( &_txn, "_id_" ) ); } }; /** Helpers::ensureIndex() is not interrupted. */ class HelpersEnsureIndexInterruptDisallowed : public IndexBuildBase { public: void run() { // Insert some documents. int32_t nDocs = 1000; for( int32_t i = 0; i < nDocs; ++i ) { _client.insert( _ns, BSON( "a" << i ) ); } // Start with just _id ASSERT_EQUALS( 1U, _client.getIndexSpecs(_ns).size()); // Initialize curop. _txn.getCurOp()->reset(); // Request an interrupt. getGlobalEnvironment()->setKillAllOperations(); // The call is not interrupted. Helpers::ensureIndex( &_txn, collection(), BSON( "a" << 1 ), false, "a_1" ); // only want to interrupt the index build getGlobalEnvironment()->unsetKillAllOperations(); // The new index is listed in getIndexSpecs because the index build completed. ASSERT_EQUALS( 2U, _client.getIndexSpecs(_ns).size()); } }; // QUERY_MIGRATION #if 0 class IndexBuildInProgressTest : public IndexBuildBase { public: void run() { NamespaceDetails* nsd = nsdetails( _ns ); // _id_ is at 0, so nIndexes == 1 IndexCatalog::IndexBuildBlock* a = halfAddIndex("a"); IndexCatalog::IndexBuildBlock* b = halfAddIndex("b"); IndexCatalog::IndexBuildBlock* c = halfAddIndex("c"); IndexCatalog::IndexBuildBlock* d = halfAddIndex("d"); int offset = nsd->_catalogFindIndexByName( "b_1", true ); ASSERT_EQUALS(2, offset); delete b; ASSERT_EQUALS(2, nsd->_catalogFindIndexByName( "c_1", true ) ); ASSERT_EQUALS(3, nsd->_catalogFindIndexByName( "d_1", true ) ); offset = nsd->_catalogFindIndexByName( "d_1", true ); delete d; ASSERT_EQUALS(2, nsd->_catalogFindIndexByName( "c_1", true ) ); ASSERT( nsd->_catalogFindIndexByName( "d_1", true ) < 0 ); offset = nsd->_catalogFindIndexByName( "a_1", true ); delete a; ASSERT_EQUALS(1, nsd->_catalogFindIndexByName( "c_1", true )); delete c; } private: IndexCatalog::IndexBuildBlock* halfAddIndex(const std::string& key) { string name = key + "_1"; BSONObj indexInfo = BSON( "v" << 1 << "key" << BSON( key << 1 ) << "ns" << _ns << "name" << name ); int32_t lenWHdr = indexInfo.objsize() + Record::HeaderSize; const char* systemIndexes = "unittests.system.indexes"; RecordId infoLoc = allocateSpaceForANewRecord( systemIndexes, nsdetails( systemIndexes ), lenWHdr, false ); Record* infoRecord = reinterpret_cast( getDur().writingPtr( infoLoc.rec(), lenWHdr ) ); memcpy( infoRecord->data(), indexInfo.objdata(), indexInfo.objsize() ); addRecordToRecListInExtent( infoRecord, infoLoc ); return new IndexCatalog::IndexBuildBlock( _ctx.getCollection()->getIndexCatalog(), name, infoLoc ); } }; #endif Status IndexBuildBase::createIndex(const std::string& dbname, const BSONObj& indexSpec) { MultiIndexBlock indexer(&_txn, collection()); Status status = indexer.init(indexSpec); if (status == ErrorCodes::IndexAlreadyExists) { return Status::OK(); } if (!status.isOK()) { return status; } status = indexer.insertAllDocumentsInCollection(); if (!status.isOK()) { return status; } WriteUnitOfWork wunit(&_txn); indexer.commit(); wunit.commit(); return Status::OK(); } /** * Fixture class that has a basic compound index. */ class SimpleCompoundIndex: public IndexBuildBase { public: SimpleCompoundIndex() { ASSERT_OK( createIndex( "unittest", BSON("name" << "x" << "ns" << _ns << "key" << BSON("x" << 1 << "y" << 1)))); } }; class SameSpecDifferentOption: public SimpleCompoundIndex { public: void run() { // Cannot have same key spec with an option different from the existing one. ASSERT_EQUALS( ErrorCodes::IndexOptionsConflict, createIndex( "unittest", BSON("name" << "x" << "ns" << _ns << "unique" << true << "key" << BSON("x" << 1 << "y" << 1)))); } }; class SameSpecSameOptions: public SimpleCompoundIndex { public: void run() { ASSERT_OK( createIndex( "unittest", BSON("name" << "x" << "ns" << _ns << "key" << BSON("x" << 1 << "y" << 1)))); } }; class DifferentSpecSameName: public SimpleCompoundIndex { public: void run() { // Cannot create a different index with the same name as the existing one. ASSERT_EQUALS( ErrorCodes::IndexKeySpecsConflict, createIndex( "unittest", BSON("name" << "x" << "ns" << _ns << "key" << BSON("y" << 1 << "x" << 1)))); } }; /** * Fixture class for indexes with complex options. */ class ComplexIndex: public IndexBuildBase { public: ComplexIndex() { ASSERT_OK( createIndex( "unittests", BSON("name" << "super" << "ns" << _ns << "unique" << 1 << "sparse" << true << "expireAfterSeconds" << 3600 << "key" << BSON("superIdx" << "2d")))); } }; class SameSpecSameOptionDifferentOrder: public ComplexIndex { public: void run() { // Exactly the same specs with the existing one, only // specified in a different order than the original. ASSERT_OK( createIndex( "unittests", BSON("name" << "super2" << "ns" << _ns << "expireAfterSeconds" << 3600 << "sparse" << true << "unique" << 1 << "key" << BSON("superIdx" << "2d")))); } }; // The following tests tries to create an index with almost the same // specs as the original, except for one option. class SameSpecDifferentUnique: public ComplexIndex { public: void run() { ASSERT_EQUALS( ErrorCodes::IndexOptionsConflict, createIndex( "unittest", BSON("name" << "super2" << "ns" << _ns << "unique" << false << "sparse" << true << "expireAfterSeconds" << 3600 << "key" << BSON("superIdx" << "2d")))); } }; class SameSpecDifferentSparse: public ComplexIndex { public: void run() { ASSERT_EQUALS( ErrorCodes::IndexOptionsConflict, createIndex( "unittest", BSON("name" << "super2" << "ns" << _ns << "unique" << 1 << "sparse" << false << "background" << true << "expireAfterSeconds" << 3600 << "key" << BSON("superIdx" << "2d")))); } }; class SameSpecDifferentTTL: public ComplexIndex { public: void run() { ASSERT_EQUALS( ErrorCodes::IndexOptionsConflict, createIndex( "unittest", BSON("name" << "super2" << "ns" << _ns << "unique" << 1 << "sparse" << true << "expireAfterSeconds" << 2400 << "key" << BSON("superIdx" << "2d")))); } }; class StorageEngineOptions : public IndexBuildBase { public: void run() { // "storageEngine" field has to be an object if present. ASSERT_NOT_OK(createIndex("unittest", _createSpec(12345))); // 'storageEngine' must not be empty. ASSERT_NOT_OK(createIndex("unittest", _createSpec(BSONObj()))); // Every field under "storageEngine" must match a registered storage engine. ASSERT_NOT_OK(createIndex("unittest", _createSpec(BSON("unknownEngine" << BSONObj())))); // Testing with 'wiredTiger' because the registered storage engine factory // supports custom index options under 'storageEngine'. const std::string storageEngineName = "wiredTiger"; // Run 'wiredTiger' tests if the storage engine is supported. if (getGlobalEnvironment()->isRegisteredStorageEngine(storageEngineName)) { // Every field under "storageEngine" has to be an object. ASSERT_NOT_OK(createIndex("unittest", _createSpec(BSON(storageEngineName << 1)))); // Storage engine options must pass validation by the storage engine factory. // For 'wiredTiger', embedded document must contain 'configString'. ASSERT_NOT_OK(createIndex("unittest", _createSpec( BSON(storageEngineName << BSON("unknown" << 1))))); // Configuration string for 'wiredTiger' must be a string. ASSERT_NOT_OK(createIndex("unittest", _createSpec( BSON(storageEngineName << BSON("configString" << 1))))); // Valid 'wiredTiger' configuration. ASSERT_OK(createIndex("unittest", _createSpec( BSON(storageEngineName << BSON("configString" << "block_compressor=zlib"))))); } } protected: template BSONObj _createSpec(T storageEngineValue) { return BSON("name" << "super2" << "ns" << _ns << "key" << BSON("a" << 1) << "storageEngine" << storageEngineValue); } }; class IndexCatatalogFixIndexKey { public: void run() { ASSERT_EQUALS( BSON( "x" << 1 ), IndexCatalog::fixIndexKey( BSON( "x" << 1 ) ) ); ASSERT_EQUALS( BSON( "_id" << 1 ), IndexCatalog::fixIndexKey( BSON( "_id" << 1 ) ) ); ASSERT_EQUALS( BSON( "_id" << 1 ), IndexCatalog::fixIndexKey( BSON( "_id" << true ) ) ); } }; class IndexUpdateTests : public Suite { public: IndexUpdateTests() : Suite( "indexupdate" ) { } void setupTests() { //add(); //add( false ); //add( true ); // QUERY_MIGRATION //add(); //add( false ); //add( true ); add >(); add >(); add >(); add >(); add >(); add >(); add(); add(); add(); add(); add(); //add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); } } indexUpdateTests; } // namespace IndexUpdateTests