// rollbacktests.cpp
/**
* Copyright (C) 2014 MongoDB 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/bson/bsonobj.h"
#include "mongo/db/catalog/collection.h"
#include "mongo/db/catalog/database_catalog_entry.h"
#include "mongo/db/catalog/database_holder.h"
#include "mongo/db/catalog/head_manager.h"
#include "mongo/db/catalog/index_create.h"
#include "mongo/db/db_raii.h"
#include "mongo/db/operation_context_impl.h"
#include "mongo/db/record_id.h"
#include "mongo/dbtests/dbtests.h"
#include "mongo/unittest/unittest.h"
using std::unique_ptr;
using std::list;
using std::string;
namespace RollbackTests {
namespace {
void dropDatabase( OperationContext* txn, const NamespaceString& nss ) {
ScopedTransaction transaction(txn, MODE_X);
Lock::GlobalWrite globalWriteLock( txn->lockState() );
Database* db = dbHolder().get( txn, nss.db() );
if ( db ) {
dropDatabase( txn, db );
}
}
bool collectionExists( OldClientContext* ctx, const string& ns ) {
const DatabaseCatalogEntry* dbEntry = ctx->db()->getDatabaseCatalogEntry();
list names;
dbEntry->getCollectionNamespaces( &names );
return std::find( names.begin(), names.end(), ns ) != names.end();
}
void createCollection( OperationContext* txn, const NamespaceString& nss ) {
ScopedTransaction transaction( txn, MODE_IX );
Lock::DBLock dbXLock( txn->lockState(), nss.db(), MODE_X );
OldClientContext ctx( txn, nss.ns() );
{
WriteUnitOfWork uow( txn );
ASSERT( !collectionExists( &ctx, nss.ns() ) );
ASSERT_OK( userCreateNS( txn, ctx.db(), nss.ns(), BSONObj(), false ) );
ASSERT( collectionExists( &ctx, nss.ns() ) );
uow.commit();
}
}
Status renameCollection( OperationContext* txn,
const NamespaceString& source,
const NamespaceString& target ) {
ASSERT_EQ( source.db(), target.db() );
Database* db = dbHolder().get( txn, source.db() );
return db->renameCollection( txn, source.ns(), target.ns(), false );
}
Status truncateCollection( OperationContext* txn, const NamespaceString& nss ) {
Collection* coll = dbHolder().get( txn, nss.db() )->getCollection(nss.ns() );
return coll->truncate( txn );
}
RecordId insertRecord( OperationContext* txn,
const NamespaceString& nss,
const BSONObj& data ) {
Collection* coll = dbHolder().get( txn, nss.db() )->getCollection(nss.ns() );
StatusWith status = coll->insertDocument( txn, data, false );
ASSERT_OK( status.getStatus() );
return status.getValue();
}
void assertOnlyRecord( OperationContext* txn,
const NamespaceString& nss,
const BSONObj& data ) {
Collection* coll = dbHolder().get( txn, nss.db() )->getCollection(nss.ns() );
auto cursor = coll->getCursor(txn);
auto record = cursor->next();
ASSERT(record);
ASSERT_EQ(data, record->data.releaseToBson());
ASSERT(!cursor->next());
}
void assertEmpty( OperationContext* txn, const NamespaceString& nss ) {
Collection* coll = dbHolder().get( txn, nss.db() )->getCollection(nss.ns() );
ASSERT(!coll->getCursor(txn)->next());
}
bool indexExists( OperationContext* txn, const NamespaceString& nss, const string& idxName ) {
Collection* coll = dbHolder().get( txn, nss.db() )->getCollection(nss.ns() );
return coll->getIndexCatalog()->findIndexByName( txn, idxName, true ) != NULL;
}
bool indexReady( OperationContext* txn, const NamespaceString& nss, const string& idxName ) {
Collection* coll = dbHolder().get( txn, nss.db() )->getCollection(nss.ns() );
return coll->getIndexCatalog()->findIndexByName( txn, idxName, false ) != NULL;
}
size_t getNumIndexEntries( OperationContext* txn,
const NamespaceString& nss,
const string& idxName ) {
size_t numEntries = 0;
Collection* coll = dbHolder().get( txn, nss.db() )->getCollection(nss.ns() );
IndexCatalog* catalog = coll->getIndexCatalog();
IndexDescriptor* desc = catalog->findIndexByName( txn, idxName, false );
if ( desc ) {
auto cursor = catalog->getIndex(desc)->newCursor(txn);
for (auto kv = cursor->seek(minKey, true); kv; kv = cursor->next()) {
numEntries++;
}
}
return numEntries;
}
void dropIndex( OperationContext* txn, const NamespaceString& nss, const string& idxName ) {
Collection* coll = dbHolder().get( txn, nss.db() )->getCollection(nss.ns() );
IndexDescriptor* desc = coll->getIndexCatalog()->findIndexByName( txn, idxName );
ASSERT( desc );
ASSERT_OK( coll->getIndexCatalog()->dropIndex( txn, desc ) );
}
} // namespace
template
class CreateCollection {
public:
void run() {
string ns = "unittests.rollback_create_collection";
OperationContextImpl txn;
NamespaceString nss( ns );
dropDatabase( &txn, nss );
ScopedTransaction transaction(&txn, MODE_IX);
Lock::DBLock dbXLock( txn.lockState(), nss.db(), MODE_X );
OldClientContext ctx( &txn, ns );
{
WriteUnitOfWork uow( &txn );
ASSERT( !collectionExists( &ctx, ns ) );
ASSERT_OK( userCreateNS( &txn, ctx.db(), ns, BSONObj(), defaultIndexes ) );
ASSERT( collectionExists( &ctx, ns ) );
if ( !rollback ) {
uow.commit();
}
}
if ( rollback ) {
ASSERT( !collectionExists( &ctx, ns ) );
}
else {
ASSERT( collectionExists( &ctx, ns ) );
}
}
};
template
class DropCollection {
public:
void run() {
string ns = "unittests.rollback_drop_collection";
OperationContextImpl txn;
NamespaceString nss( ns );
dropDatabase( &txn, nss );
ScopedTransaction transaction(&txn, MODE_IX);
Lock::DBLock dbXLock( txn.lockState(), nss.db(), MODE_X );
OldClientContext ctx( &txn, ns );
{
WriteUnitOfWork uow( &txn );
ASSERT( !collectionExists( &ctx, ns ) );
ASSERT_OK( userCreateNS( &txn, ctx.db(), ns, BSONObj(), defaultIndexes ) );
uow.commit();
}
ASSERT( collectionExists( &ctx, ns ) );
// END OF SETUP / START OF TEST
{
WriteUnitOfWork uow( &txn );
ASSERT( collectionExists( &ctx, ns ) );
ASSERT_OK( ctx.db()->dropCollection( &txn, ns ) );
ASSERT( !collectionExists( &ctx, ns ) );
if ( !rollback ) {
uow.commit();
}
}
if ( rollback ) {
ASSERT( collectionExists( &ctx, ns ) );
}
else {
ASSERT( !collectionExists( &ctx, ns ) );
}
}
};
template
class RenameCollection {
public:
void run() {
NamespaceString source( "unittests.rollback_rename_collection_src" );
NamespaceString target( "unittests.rollback_rename_collection_dest" );
OperationContextImpl txn;
dropDatabase( &txn, source );
dropDatabase( &txn, target );
ScopedTransaction transaction(&txn, MODE_X);
Lock::GlobalWrite globalWriteLock( txn.lockState() );
OldClientContext ctx( &txn, source );
{
WriteUnitOfWork uow( &txn );
ASSERT( !collectionExists( &ctx, source ) );
ASSERT( !collectionExists( &ctx, target ) );
ASSERT_OK( userCreateNS( &txn, ctx.db(), source.ns(), BSONObj(), defaultIndexes ) );
uow.commit();
}
ASSERT( collectionExists( &ctx, source ) );
ASSERT( !collectionExists( &ctx, target ) );
// END OF SETUP / START OF TEST
{
WriteUnitOfWork uow( &txn );
ASSERT_OK( renameCollection( &txn, source, target ) );
ASSERT( !collectionExists( &ctx, source ) );
ASSERT( collectionExists( &ctx, target ) );
if ( !rollback ) {
uow.commit();
}
}
if ( rollback ) {
ASSERT( collectionExists( &ctx, source ) );
ASSERT( !collectionExists( &ctx, target ) );
}
else {
ASSERT( !collectionExists( &ctx, source ) );
ASSERT( collectionExists( &ctx, target ) );
}
}
};
template
class RenameDropTargetCollection {
public:
void run() {
NamespaceString source( "unittests.rollback_rename_droptarget_collection_src" );
NamespaceString target( "unittests.rollback_rename_droptarget_collection_dest" );
OperationContextImpl txn;
dropDatabase( &txn, source );
dropDatabase( &txn, target );
ScopedTransaction transaction(&txn, MODE_X);
Lock::GlobalWrite globalWriteLock( txn.lockState() );
OldClientContext ctx( &txn, source );
BSONObj sourceDoc = BSON( "_id" << "source" );
BSONObj targetDoc = BSON( "_id" << "target" );
{
WriteUnitOfWork uow( &txn );
ASSERT( !collectionExists( &ctx, source ) );
ASSERT( !collectionExists( &ctx, target ) );
ASSERT_OK( userCreateNS( &txn, ctx.db(), source.ns(), BSONObj(), defaultIndexes ) );
ASSERT_OK( userCreateNS( &txn, ctx.db(), target.ns(), BSONObj(), defaultIndexes ) );
insertRecord( &txn, source, sourceDoc );
insertRecord( &txn, target, targetDoc );
uow.commit();
}
ASSERT( collectionExists( &ctx, source ) );
ASSERT( collectionExists( &ctx, target ) );
assertOnlyRecord( &txn, source, sourceDoc );
assertOnlyRecord( &txn, target, targetDoc );
// END OF SETUP / START OF TEST
{
WriteUnitOfWork uow( &txn );
ASSERT_OK( ctx.db()->dropCollection( &txn, target.ns() ) );
ASSERT_OK( renameCollection( &txn, source, target ) );
ASSERT( !collectionExists( &ctx, source ) );
ASSERT( collectionExists( &ctx, target ) );
assertOnlyRecord( &txn, target, sourceDoc );
if ( !rollback ) {
uow.commit();
}
}
if ( rollback ) {
ASSERT( collectionExists( &ctx, source ) );
ASSERT( collectionExists( &ctx, target ) );
assertOnlyRecord( &txn, source, sourceDoc );
assertOnlyRecord( &txn, target, targetDoc );
}
else {
ASSERT( !collectionExists( &ctx, source ) );
ASSERT( collectionExists( &ctx, target ) );
assertOnlyRecord( &txn, target, sourceDoc );
}
}
};
template
class ReplaceCollection {
public:
void run() {
NamespaceString nss( "unittests.rollback_replace_collection" );
OperationContextImpl txn;
dropDatabase( &txn, nss );
ScopedTransaction transaction(&txn, MODE_IX);
Lock::DBLock dbXLock( txn.lockState(), nss.db(), MODE_X );
OldClientContext ctx( &txn, nss );
BSONObj oldDoc = BSON( "_id" << "old" );
BSONObj newDoc = BSON( "_id" << "new" );
{
WriteUnitOfWork uow( &txn );
ASSERT( !collectionExists( &ctx, nss ) );
ASSERT_OK( userCreateNS( &txn, ctx.db(), nss.ns(), BSONObj(), defaultIndexes ) );
insertRecord( &txn, nss, oldDoc );
uow.commit();
}
ASSERT( collectionExists( &ctx, nss ) );
assertOnlyRecord( &txn, nss, oldDoc );
// END OF SETUP / START OF TEST
{
WriteUnitOfWork uow( &txn );
ASSERT_OK( ctx.db()->dropCollection( &txn, nss.ns() ) );
ASSERT( !collectionExists( &ctx, nss ) );
ASSERT_OK( userCreateNS( &txn, ctx.db(), nss.ns(), BSONObj(), defaultIndexes ) );
ASSERT( collectionExists( &ctx, nss ) );
insertRecord( &txn, nss, newDoc );
assertOnlyRecord( &txn, nss, newDoc );
if ( !rollback ) {
uow.commit();
}
}
ASSERT( collectionExists( &ctx, nss ) );
if ( rollback ) {
assertOnlyRecord( &txn, nss, oldDoc );
}
else {
assertOnlyRecord( &txn, nss, newDoc );
}
}
};
template
class CreateDropCollection {
public:
void run() {
NamespaceString nss( "unittests.rollback_create_drop_collection" );
OperationContextImpl txn;
dropDatabase( &txn, nss );
ScopedTransaction transaction(&txn, MODE_IX);
Lock::DBLock dbXLock( txn.lockState(), nss.db(), MODE_X );
OldClientContext ctx( &txn, nss );
BSONObj doc = BSON( "_id" << "example string" );
ASSERT( !collectionExists( &ctx, nss ) );
{
WriteUnitOfWork uow( &txn );
ASSERT_OK( userCreateNS( &txn, ctx.db(), nss.ns(), BSONObj(), defaultIndexes ) );
ASSERT( collectionExists( &ctx, nss ) );
insertRecord( &txn, nss, doc );
assertOnlyRecord( &txn, nss, doc );
ASSERT_OK( ctx.db()->dropCollection( &txn, nss.ns() ) );
ASSERT( !collectionExists( &ctx, nss ) );
if ( !rollback ) {
uow.commit();
}
}
ASSERT( !collectionExists( &ctx, nss ) );
}
};
template
class TruncateCollection {
public:
void run() {
NamespaceString nss( "unittests.rollback_truncate_collection" );
OperationContextImpl txn;
dropDatabase( &txn, nss );
ScopedTransaction transaction(&txn, MODE_IX);
Lock::DBLock dbXLock( txn.lockState(), nss.db(), MODE_X );
OldClientContext ctx( &txn, nss );
BSONObj doc = BSON( "_id" << "foo" );
ASSERT( !collectionExists( &ctx, nss ) );
{
WriteUnitOfWork uow( &txn );
ASSERT_OK( userCreateNS( &txn, ctx.db(), nss.ns(), BSONObj(), defaultIndexes ) );
ASSERT( collectionExists( &ctx, nss ) );
insertRecord( &txn, nss, doc );
assertOnlyRecord( &txn, nss, doc );
uow.commit();
}
assertOnlyRecord( &txn, nss, doc );
// END OF SETUP / START OF TEST
{
WriteUnitOfWork uow( &txn );
ASSERT_OK( truncateCollection( &txn, nss ) );
ASSERT( collectionExists( &ctx, nss ) );
assertEmpty( &txn, nss );
if ( !rollback ) {
uow.commit();
}
}
ASSERT( collectionExists( &ctx, nss ) );
if ( rollback ) {
assertOnlyRecord( &txn, nss, doc );
}
else {
assertEmpty( &txn, nss );
}
}
};
template
class CreateIndex {
public:
void run() {
string ns = "unittests.rollback_create_index";
OperationContextImpl txn;
NamespaceString nss( ns );
dropDatabase( &txn, nss );
createCollection( &txn, nss );
ScopedTransaction transaction(&txn, MODE_IX);
AutoGetDb autoDb(&txn, nss.db(), MODE_X);
Collection* coll = autoDb.getDb()->getCollection( ns );
IndexCatalog* catalog = coll->getIndexCatalog();
string idxName = "a";
BSONObj spec = BSON( "ns" << ns << "key" << BSON( "a" << 1 ) << "name" << idxName );
// END SETUP / START TEST
{
WriteUnitOfWork uow( &txn );
ASSERT_OK( catalog->createIndexOnEmptyCollection( &txn, spec ) );
insertRecord( &txn, nss, BSON( "a" << 1 ) );
insertRecord( &txn, nss, BSON( "a" << 2 ) );
insertRecord( &txn, nss, BSON( "a" << 3 ) );
if ( !rollback ) {
uow.commit();
}
}
if ( rollback ) {
ASSERT( !indexExists( &txn, nss, idxName ) );
}
else {
ASSERT( indexReady( &txn, nss, idxName ) );
}
}
};
template
class DropIndex {
public:
void run() {
string ns = "unittests.rollback_drop_index";
OperationContextImpl txn;
NamespaceString nss( ns );
dropDatabase( &txn, nss );
createCollection( &txn, nss );
ScopedTransaction transaction(&txn, MODE_IX);
AutoGetDb autoDb(&txn, nss.db(), MODE_X);
Collection* coll = autoDb.getDb()->getCollection(ns);
IndexCatalog* catalog = coll->getIndexCatalog();
string idxName = "a";
BSONObj spec = BSON( "ns" << ns << "key" << BSON( "a" << 1 ) << "name" << idxName );
{
WriteUnitOfWork uow( &txn );
ASSERT_OK( catalog->createIndexOnEmptyCollection( &txn, spec ) );
insertRecord( &txn, nss, BSON( "a" << 1 ) );
insertRecord( &txn, nss, BSON( "a" << 2 ) );
insertRecord( &txn, nss, BSON( "a" << 3 ) );
uow.commit();
}
ASSERT( indexReady( &txn, nss, idxName ) );
ASSERT_EQ( 3u, getNumIndexEntries( &txn, nss, idxName ) );
// END SETUP / START TEST
{
WriteUnitOfWork uow( &txn );
dropIndex( &txn, nss, idxName );
ASSERT( !indexExists( &txn, nss, idxName ) );
if ( !rollback ) {
uow.commit();
}
}
if ( rollback ) {
ASSERT( indexExists( &txn, nss, idxName ) );
ASSERT( indexReady( &txn, nss, idxName ) );
ASSERT_EQ( 3u, getNumIndexEntries( &txn, nss, idxName ) );
}
else {
ASSERT( !indexExists( &txn, nss, idxName ) );
}
}
};
template
class CreateDropIndex {
public:
void run() {
string ns = "unittests.rollback_create_drop_index";
OperationContextImpl txn;
NamespaceString nss( ns );
dropDatabase( &txn, nss );
createCollection( &txn, nss );
ScopedTransaction transaction(&txn, MODE_IX);
AutoGetDb autoDb(&txn, nss.db(), MODE_X);
Collection* coll = autoDb.getDb()->getCollection(ns);
IndexCatalog* catalog = coll->getIndexCatalog();
string idxName = "a";
BSONObj spec = BSON( "ns" << ns << "key" << BSON( "a" << 1 ) << "name" << idxName );
// END SETUP / START TEST
{
WriteUnitOfWork uow( &txn );
ASSERT_OK( catalog->createIndexOnEmptyCollection( &txn, spec ) );
insertRecord( &txn, nss, BSON( "a" << 1 ) );
insertRecord( &txn, nss, BSON( "a" << 2 ) );
insertRecord( &txn, nss, BSON( "a" << 3 ) );
ASSERT( indexExists( &txn, nss, idxName ) );
ASSERT_EQ( 3u, getNumIndexEntries( &txn, nss, idxName ) );
dropIndex( &txn, nss, idxName );
ASSERT( !indexExists( &txn, nss, idxName ) );
if ( !rollback ) {
uow.commit();
}
}
ASSERT( !indexExists( &txn, nss, idxName ) );
}
};
template
class SetIndexHead {
public:
void run() {
string ns = "unittests.rollback_set_index_head";
OperationContextImpl txn;
NamespaceString nss( ns );
dropDatabase( &txn, nss );
createCollection( &txn, nss );
ScopedTransaction transaction(&txn, MODE_IX);
AutoGetDb autoDb(&txn, nss.db(), MODE_X);
Collection* coll = autoDb.getDb()->getCollection(ns);
IndexCatalog* catalog = coll->getIndexCatalog();
string idxName = "a";
BSONObj spec = BSON( "ns" << ns << "key" << BSON( "a" << 1 ) << "name" << idxName );
{
WriteUnitOfWork uow( &txn );
ASSERT_OK( catalog->createIndexOnEmptyCollection( &txn, spec ) );
uow.commit();
}
IndexDescriptor* indexDesc = catalog->findIndexByName(&txn, idxName);
invariant(indexDesc);
const IndexCatalogEntry* ice = catalog->getEntry(indexDesc);
invariant(ice);
HeadManager* headManager = ice->headManager();
const RecordId oldHead = headManager->getHead(&txn);
ASSERT_EQ(oldHead, ice->head(&txn));
const RecordId dummyHead(123, 456);
ASSERT_NE(oldHead, dummyHead);
// END SETUP / START TEST
{
WriteUnitOfWork uow( &txn );
headManager->setHead(&txn, dummyHead);
ASSERT_EQ(ice->head(&txn), dummyHead);
ASSERT_EQ(headManager->getHead(&txn), dummyHead);
if ( !rollback ) {
uow.commit();
}
}
if ( rollback ) {
ASSERT_EQ(ice->head(&txn), oldHead);
ASSERT_EQ(headManager->getHead(&txn), oldHead);
}
else {
ASSERT_EQ(ice->head(&txn), dummyHead);
ASSERT_EQ(headManager->getHead(&txn), dummyHead);
}
}
};
template
class CreateCollectionAndIndexes {
public:
void run() {
string ns = "unittests.rollback_create_collection_and_indexes";
OperationContextImpl txn;
NamespaceString nss( ns );
dropDatabase( &txn, nss );
ScopedTransaction transaction(&txn, MODE_IX);
Lock::DBLock dbXLock( txn.lockState(), nss.db(), MODE_X );
OldClientContext ctx( &txn, nss.ns() );
string idxNameA = "indexA";
string idxNameB = "indexB";
string idxNameC = "indexC";
BSONObj specA = BSON( "ns" << ns << "key" << BSON( "a" << 1 ) << "name" << idxNameA );
BSONObj specB = BSON( "ns" << ns << "key" << BSON( "b" << 1 ) << "name" << idxNameB );
BSONObj specC = BSON( "ns" << ns << "key" << BSON( "c" << 1 ) << "name" << idxNameC );
// END SETUP / START TEST
{
WriteUnitOfWork uow( &txn );
ASSERT( !collectionExists( &ctx, nss.ns() ) );
ASSERT_OK( userCreateNS( &txn, ctx.db(), nss.ns(), BSONObj(), false ) );
ASSERT( collectionExists( &ctx, nss.ns() ) );
Collection* coll = ctx.db()->getCollection( ns );
IndexCatalog* catalog = coll->getIndexCatalog();
ASSERT_OK( catalog->createIndexOnEmptyCollection( &txn, specA ) );
ASSERT_OK( catalog->createIndexOnEmptyCollection( &txn, specB ) );
ASSERT_OK( catalog->createIndexOnEmptyCollection( &txn, specC ) );
if ( !rollback ) {
uow.commit();
}
} // uow
if ( rollback ) {
ASSERT( !collectionExists( &ctx, ns ) );
}
else {
ASSERT( collectionExists( &ctx, ns ) );
ASSERT( indexReady( &txn, nss, idxNameA ) );
ASSERT( indexReady( &txn, nss, idxNameB ) );
ASSERT( indexReady( &txn, nss, idxNameC ) );
}
}
};
class All : public Suite {
public:
All() : Suite( "rollback" ) {
}
template< template class T >
void addAll() {
add< T >();
add< T >();
}
template< template class T >
void addAll() {
add< T >();
add< T >();
add< T >();
add< T >();
}
void setupTests() {
addAll< CreateCollection >();
addAll< RenameCollection >();
addAll< DropCollection >();
addAll< RenameDropTargetCollection >();
addAll< ReplaceCollection >();
addAll< CreateDropCollection >();
addAll< TruncateCollection >();
addAll< CreateIndex >();
addAll< DropIndex >();
addAll< CreateDropIndex >();
addAll< SetIndexHead >();
addAll< CreateCollectionAndIndexes >();
}
};
SuiteInstance all;
} // namespace RollbackTests