bool run(OperationContext* txn, const string& dbname, BSONObj& jsobj, int, string& errmsg, BSONObjBuilder& result, bool fromRepl ) { // calls renamecollection which does a global lock, so we must too: // Lock::GlobalWrite globalWriteLock(txn->lockState()); WriteUnitOfWork wunit(txn->recoveryUnit()); Client::Context ctx(txn, dbname); Database* db = ctx.db(); stopIndexBuilds(txn, db, jsobj); BackgroundOperation::assertNoBgOpInProgForDb(dbname.c_str()); string shortSource = jsobj.getStringField( "convertToCapped" ); string longSource = dbname + "." + shortSource; double size = jsobj.getField( "size" ).number(); if ( shortSource.empty() || size == 0 ) { errmsg = "invalid command spec"; return false; } string shortTmpName = str::stream() << "tmp.convertToCapped." << shortSource; string longTmpName = str::stream() << dbname << "." << shortTmpName; if ( db->getCollection( txn, longTmpName ) ) { Status status = db->dropCollection( txn, longTmpName ); if ( !status.isOK() ) return appendCommandStatus( result, status ); } Status status = cloneCollectionAsCapped( txn, db, shortSource, shortTmpName, size, true, false ); if ( !status.isOK() ) return appendCommandStatus( result, status ); verify( db->getCollection( txn, longTmpName ) ); status = db->dropCollection( txn, longSource ); if ( !status.isOK() ) return appendCommandStatus( result, status ); status = db->renameCollection( txn, longTmpName, longSource, false ); if ( !status.isOK() ) return appendCommandStatus( result, status ); if (!fromRepl) repl::logOp(txn, "c",(dbname + ".$cmd").c_str(), jsobj); wunit.commit(); return true; }
void run() { NamespaceString source("unittests.rollback_rename_collection_src"); NamespaceString target("unittests.rollback_rename_collection_dest"); const ServiceContext::UniqueOperationContext opCtxPtr = cc().makeOperationContext(); OperationContext& opCtx = *opCtxPtr; dropDatabase(&opCtx, source); dropDatabase(&opCtx, target); Lock::GlobalWrite globalWriteLock(&opCtx); OldClientContext ctx(&opCtx, source.ns()); { WriteUnitOfWork uow(&opCtx); ASSERT(!collectionExists(&ctx, source.ns())); ASSERT(!collectionExists(&ctx, target.ns())); auto options = capped ? BSON("capped" << true << "size" << 1000) : BSONObj(); ASSERT_OK(userCreateNS(&opCtx, ctx.db(), source.ns(), options, CollectionOptions::parseForCommand, defaultIndexes)); uow.commit(); } ASSERT(collectionExists(&ctx, source.ns())); ASSERT(!collectionExists(&ctx, target.ns())); // END OF SETUP / START OF TEST { WriteUnitOfWork uow(&opCtx); ASSERT_OK(renameCollection(&opCtx, source, target)); ASSERT(!collectionExists(&ctx, source.ns())); ASSERT(collectionExists(&ctx, target.ns())); if (!rollback) { uow.commit(); } } if (rollback) { ASSERT(collectionExists(&ctx, source.ns())); ASSERT(!collectionExists(&ctx, target.ns())); } else { ASSERT(!collectionExists(&ctx, source.ns())); ASSERT(collectionExists(&ctx, target.ns())); } }
virtual bool run(OperationContext* txn, const string& dbname, BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result, bool fromRepl) { ScopedTransaction transaction(txn, MODE_X); Lock::GlobalWrite globalWriteLock(txn->lockState()); ReplicationCoordinator* replCoord = getGlobalReplicationCoordinator(); if (getGlobalReplicationCoordinator()->getSettings().usingReplSets()) { const MemberState memberState = replCoord->getMemberState(); if (memberState.startup()) { return appendCommandStatus(result, Status(ErrorCodes::NotYetInitialized, "no replication yet active")); } if (memberState.primary() || !replCoord->setFollowerMode(MemberState::RS_STARTUP2)) { return appendCommandStatus(result, Status(ErrorCodes::NotSecondary, "primaries cannot resync")); } BackgroundSync::get()->setInitialSyncRequestedFlag(true); return true; } // below this comment pertains only to master/slave replication if ( cmdObj.getBoolField( "force" ) ) { if ( !waitForSyncToFinish(txn, errmsg ) ) return false; replAllDead = "resync forced"; } // TODO(dannenberg) replAllDead is bad and should be removed when masterslave is removed if (!replAllDead) { errmsg = "not dead, no need to resync"; return false; } if ( !waitForSyncToFinish(txn, errmsg ) ) return false; ReplSource::forceResyncDead( txn, "client" ); result.append( "info", "triggered resync for all sources" ); return true; }
virtual bool run(OperationContext* txn, const string& dbname, BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result, bool fromRepl) { if ( cmdObj.firstElement().type() != Array ) { errmsg = "ops has to be an array"; return false; } BSONObj ops = cmdObj.firstElement().Obj(); { // check input BSONObjIterator i( ops ); while ( i.more() ) { BSONElement e = i.next(); if (!_checkOperation(e, errmsg)) { return false; } } } // SERVER-4328 todo : is global ok or does this take a long time? i believe multiple // ns used so locking individually requires more analysis Lock::GlobalWrite globalWriteLock(txn->lockState()); DBDirectClient db(txn); // Preconditions check reads the database state, so needs to be done locked if ( cmdObj["preCondition"].type() == Array ) { BSONObjIterator i( cmdObj["preCondition"].Obj() ); while ( i.more() ) { BSONObj f = i.next().Obj(); DBDirectClient db( txn ); BSONObj realres = db.findOne( f["ns"].String() , f["q"].Obj() ); // Apply-ops would never have a $where matcher, so use the default callback, // which will throw an error if $where is found. Matcher m(f["res"].Obj()); if ( ! m.matches( realres ) ) { result.append( "got" , realres ); result.append( "whatFailed" , f ); errmsg = "pre-condition failed"; return false; } } } // apply int num = 0; int errors = 0; BSONObjIterator i( ops ); BSONArrayBuilder ab; const bool alwaysUpsert = cmdObj.hasField("alwaysUpsert") ? cmdObj["alwaysUpsert"].trueValue() : true; while ( i.more() ) { BSONElement e = i.next(); const BSONObj& temp = e.Obj(); // Ignore 'n' operations. const char *opType = temp["op"].valuestrsafe(); if (*opType == 'n') continue; string ns = temp["ns"].String(); // Run operations under a nested lock as a hack to prevent yielding. // // The list of operations is supposed to be applied atomically; yielding // would break atomicity by allowing an interruption or a shutdown to occur // after only some operations are applied. We are already locked globally // at this point, so taking a DBLock on the namespace creates a nested lock, // and yields are disallowed for operations that hold a nested lock. // // We do not have a wrapping WriteUnitOfWork so it is possible for a journal // commit to happen with a subset of ops applied. // TODO figure out what to do about this. Lock::DBLock lk(txn->lockState(), nsToDatabaseSubstring(ns), MODE_X); invariant(txn->lockState()->isRecursive()); Client::Context ctx(txn, ns); bool failed = repl::applyOperation_inlock(txn, ctx.db(), temp, false, alwaysUpsert); ab.append(!failed); if ( failed ) errors++; num++; logOpForDbHash(ns.c_str()); } result.append( "applied" , num ); result.append( "results" , ab.arr() ); if ( ! fromRepl ) { // We want this applied atomically on slaves // so we re-wrap without the pre-condition for speed string tempNS = str::stream() << dbname << ".$cmd"; // TODO: possibly use mutable BSON to remove preCondition field // once it is available BSONObjIterator iter(cmdObj); BSONObjBuilder cmdBuilder; while (iter.more()) { BSONElement elem(iter.next()); if (strcmp(elem.fieldName(), "preCondition") != 0) { cmdBuilder.append(elem); } } // We currently always logOp the command regardless of whether the individial ops // succeeded and rely on any failures to also happen on secondaries. This isn't // perfect, but it's what the command has always done and is part of its "correct" // behavior. WriteUnitOfWork wunit(txn); repl::logOp(txn, "c", tempNS.c_str(), cmdBuilder.done()); wunit.commit(); } if (errors != 0) { return false; } return true; }
// static Status SyncTail::syncApply(OperationContext* txn, const BSONObj& op, bool convertUpdateToUpsert, ApplyOperationInLockFn applyOperationInLock, ApplyCommandInLockFn applyCommandInLock, IncrementOpsAppliedStatsFn incrementOpsAppliedStats) { if (inShutdown()) { return Status::OK(); } // Count each log op application as a separate operation, for reporting purposes CurOp individualOp(txn); const char* ns = op.getStringField("ns"); verify(ns); const char* opType = op["op"].valuestrsafe(); bool isCommand(opType[0] == 'c'); bool isNoOp(opType[0] == 'n'); if ((*ns == '\0') || (*ns == '.')) { // this is ugly // this is often a no-op // but can't be 100% sure if (!isNoOp) { error() << "skipping bad op in oplog: " << op.toString(); } return Status::OK(); } if (isCommand) { MONGO_WRITE_CONFLICT_RETRY_LOOP_BEGIN { // a command may need a global write lock. so we will conservatively go // ahead and grab one here. suboptimal. :-( Lock::GlobalWrite globalWriteLock(txn->lockState()); // special case apply for commands to avoid implicit database creation Status status = applyCommandInLock(txn, op); incrementOpsAppliedStats(); return status; } MONGO_WRITE_CONFLICT_RETRY_LOOP_END(txn, "syncApply_command", ns); } auto applyOp = [&](Database* db) { // For non-initial-sync, we convert updates to upserts // to suppress errors when replaying oplog entries. txn->setReplicatedWrites(false); DisableDocumentValidation validationDisabler(txn); Status status = applyOperationInLock(txn, db, op, convertUpdateToUpsert, incrementOpsAppliedStats); if (!status.isOK() && status.code() == ErrorCodes::WriteConflict) { throw WriteConflictException(); } return status; }; if (isNoOp || (opType[0] == 'i' && nsToCollectionSubstring(ns) == "system.indexes")) { auto opStr = isNoOp ? "syncApply_noop" : "syncApply_indexBuild"; MONGO_WRITE_CONFLICT_RETRY_LOOP_BEGIN { Lock::DBLock dbLock(txn->lockState(), nsToDatabaseSubstring(ns), MODE_X); OldClientContext ctx(txn, ns); return applyOp(ctx.db()); } MONGO_WRITE_CONFLICT_RETRY_LOOP_END(txn, opStr, ns); }
virtual bool run(OperationContext* txn, const string& dbname, BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result, bool fromRepl) { if ( cmdObj.firstElement().type() != Array ) { errmsg = "ops has to be an array"; return false; } BSONObj ops = cmdObj.firstElement().Obj(); { // check input BSONObjIterator i( ops ); while ( i.more() ) { BSONElement e = i.next(); if ( e.type() == Object ) continue; errmsg = "op not an object: "; errmsg += e.fieldName(); return false; } } // SERVER-4328 todo : is global ok or does this take a long time? i believe multiple // ns used so locking individually requires more analysis Lock::GlobalWrite globalWriteLock(txn->lockState()); // Preconditions check reads the database state, so needs to be done locked if ( cmdObj["preCondition"].type() == Array ) { BSONObjIterator i( cmdObj["preCondition"].Obj() ); while ( i.more() ) { BSONObj f = i.next().Obj(); DBDirectClient db( txn ); BSONObj realres = db.findOne( f["ns"].String() , f["q"].Obj() ); // Apply-ops would never have a $where matcher, so use the default callback, // which will throw an error if $where is found. Matcher m(f["res"].Obj()); if ( ! m.matches( realres ) ) { result.append( "got" , realres ); result.append( "whatFailed" , f ); errmsg = "pre-condition failed"; return false; } } } // apply int num = 0; int errors = 0; BSONObjIterator i( ops ); BSONArrayBuilder ab; const bool alwaysUpsert = cmdObj.hasField("alwaysUpsert") ? cmdObj["alwaysUpsert"].trueValue() : true; while ( i.more() ) { BSONElement e = i.next(); const BSONObj& temp = e.Obj(); string ns = temp["ns"].String(); // Run operations under a nested lock as a hack to prevent them from yielding. // // The list of operations is supposed to be applied atomically; yielding would break // atomicity by allowing an interruption or a shutdown to occur after only some // operations are applied. We are already locked globally at this point, so taking // a DBWrite on the namespace creates a nested lock, and yields are disallowed for // operations that hold a nested lock. Lock::DBWrite lk(txn->lockState(), ns); invariant(txn->lockState()->isRecursive()); Client::Context ctx(ns); bool failed = repl::applyOperation_inlock(txn, ctx.db(), temp, false, alwaysUpsert); ab.append(!failed); if ( failed ) errors++; num++; logOpForDbHash(ns.c_str()); } result.append( "applied" , num ); result.append( "results" , ab.arr() ); if ( ! fromRepl ) { // We want this applied atomically on slaves // so we re-wrap without the pre-condition for speed string tempNS = str::stream() << dbname << ".$cmd"; // TODO: possibly use mutable BSON to remove preCondition field // once it is available BSONObjIterator iter(cmdObj); BSONObjBuilder cmdBuilder; while (iter.more()) { BSONElement elem(iter.next()); if (strcmp(elem.fieldName(), "preCondition") != 0) { cmdBuilder.append(elem); } } repl::logOp(txn, "c", tempNS.c_str(), cmdBuilder.done()); } return errors == 0; }
virtual bool run(OperationContext* txn, const string& dbname, BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result, bool fromRepl) { Lock::GlobalWrite globalWriteLock(txn->lockState()); string source = cmdObj.getStringField( name.c_str() ); string target = cmdObj.getStringField( "to" ); // We stay in source context the whole time. This is mostly to set the CurOp namespace. Client::Context ctx(txn, source); if ( !NamespaceString::validCollectionComponent(target.c_str()) ) { errmsg = "invalid collection name: " + target; return false; } if ( source.empty() || target.empty() ) { errmsg = "invalid command syntax"; return false; } if (!fromRepl) { // If it got through on the master, need to allow it here too Status sourceStatus = userAllowedWriteNS(source); if (!sourceStatus.isOK()) { errmsg = "error with source namespace: " + sourceStatus.reason(); return false; } Status targetStatus = userAllowedWriteNS(target); if (!targetStatus.isOK()) { errmsg = "error with target namespace: " + targetStatus.reason(); return false; } } if (NamespaceString(source).coll() == "system.indexes" || NamespaceString(target).coll() == "system.indexes") { errmsg = "renaming system.indexes is not allowed"; return false; } Database* const sourceDB = dbHolder().get(txn, nsToDatabase(source)); Collection* const sourceColl = sourceDB ? sourceDB->getCollection(txn, source) : NULL; if (!sourceColl) { errmsg = "source namespace does not exist"; return false; } { // Ensure that collection name does not exceed maximum length. // Ensure that index names do not push the length over the max. // Iterator includes unfinished indexes. IndexCatalog::IndexIterator sourceIndIt = sourceColl->getIndexCatalog()->getIndexIterator( txn, true ); int longestIndexNameLength = 0; while ( sourceIndIt.more() ) { int thisLength = sourceIndIt.next()->indexName().length(); if ( thisLength > longestIndexNameLength ) longestIndexNameLength = thisLength; } unsigned int longestAllowed = min(int(NamespaceString::MaxNsCollectionLen), int(NamespaceString::MaxNsLen) - 2/*strlen(".$")*/ - longestIndexNameLength); if (target.size() > longestAllowed) { StringBuilder sb; sb << "collection name length of " << target.size() << " exceeds maximum length of " << longestAllowed << ", allowing for index names"; errmsg = sb.str(); return false; } } const std::vector<BSONObj> indexesInProg = stopIndexBuilds(txn, sourceDB, cmdObj); // Dismissed on success ScopeGuard indexBuildRestorer = MakeGuard(IndexBuilder::restoreIndexes, indexesInProg); Database* const targetDB = dbHolder().openDb(txn, nsToDatabase(target)); { WriteUnitOfWork wunit(txn); // Check if the target namespace exists and if dropTarget is true. // If target exists and dropTarget is not true, return false. if (targetDB->getCollection(txn, target)) { if (!cmdObj["dropTarget"].trueValue()) { errmsg = "target namespace exists"; return false; } Status s = targetDB->dropCollection(txn, target); if ( !s.isOK() ) { errmsg = s.toString(); return false; } } // If we are renaming in the same database, just // rename the namespace and we're done. if (sourceDB == targetDB) { Status s = targetDB->renameCollection(txn, source, target, cmdObj["stayTemp"].trueValue() ); if (!s.isOK()) { return appendCommandStatus(result, s); } if (!fromRepl) { repl::logOp(txn, "c", (dbname + ".$cmd").c_str(), cmdObj); } wunit.commit(); indexBuildRestorer.Dismiss(); return true; } wunit.commit(); } // If we get here, we are renaming across databases, so we must copy all the data and // indexes, then remove the source collection. // Create the target collection. It will be removed if we fail to copy the collection. // TODO use a temp collection and unset the temp flag on success. Collection* targetColl = NULL; { CollectionOptions options; options.setNoIdIndex(); if (sourceColl->isCapped()) { const CollectionOptions sourceOpts = sourceColl->getCatalogEntry()->getCollectionOptions(txn); options.capped = true; options.cappedSize = sourceOpts.cappedSize; options.cappedMaxDocs = sourceOpts.cappedMaxDocs; } WriteUnitOfWork wunit(txn); // No logOp necessary because the entire renameCollection command is one logOp. targetColl = targetDB->createCollection(txn, target, options); if (!targetColl) { errmsg = "Failed to create target collection."; return false; } wunit.commit(); } // Dismissed on success ScopeGuard targetCollectionDropper = MakeGuard(dropCollection, txn, targetDB, target); MultiIndexBlock indexer(txn, targetColl); indexer.allowInterruption(); // Copy the index descriptions from the source collection, adjusting the ns field. { std::vector<BSONObj> indexesToCopy; IndexCatalog::IndexIterator sourceIndIt = sourceColl->getIndexCatalog()->getIndexIterator( txn, true ); while (sourceIndIt.more()) { const BSONObj currIndex = sourceIndIt.next()->infoObj(); // Process the source index. BSONObjBuilder newIndex; newIndex.append("ns", target); newIndex.appendElementsUnique(currIndex); indexesToCopy.push_back(newIndex.obj()); } indexer.init(indexesToCopy); } { // Copy over all the data from source collection to target collection. boost::scoped_ptr<RecordIterator> sourceIt(sourceColl->getIterator(txn)); while (!sourceIt->isEOF()) { txn->checkForInterrupt(false); const BSONObj obj = sourceColl->docFor(txn, sourceIt->getNext()); WriteUnitOfWork wunit(txn); // No logOp necessary because the entire renameCollection command is one logOp. Status status = targetColl->insertDocument(txn, obj, &indexer, true).getStatus(); if (!status.isOK()) return appendCommandStatus(result, status); wunit.commit(); } } Status status = indexer.doneInserting(); if (!status.isOK()) return appendCommandStatus(result, status); { // Getting here means we successfully built the target copy. We now remove the // source collection and finalize the rename. WriteUnitOfWork wunit(txn); Status status = sourceDB->dropCollection(txn, source); if (!status.isOK()) return appendCommandStatus(result, status); indexer.commit(); if (!fromRepl) { repl::logOp(txn, "c", (dbname + ".$cmd").c_str(), cmdObj); } wunit.commit(); } indexBuildRestorer.Dismiss(); targetCollectionDropper.Dismiss(); return true; }
Status renameCollection(OperationContext* txn, const NamespaceString& source, const NamespaceString& target, bool dropTarget, bool stayTemp) { DisableDocumentValidation validationDisabler(txn); ScopedTransaction transaction(txn, MODE_X); Lock::GlobalWrite globalWriteLock(txn->lockState()); // We stay in source context the whole time. This is mostly to set the CurOp namespace. OldClientContext ctx(txn, source.ns()); bool userInitiatedWritesAndNotPrimary = txn->writesAreReplicated() && !repl::getGlobalReplicationCoordinator()->canAcceptWritesFor(source); if (userInitiatedWritesAndNotPrimary) { return Status(ErrorCodes::NotMaster, str::stream() << "Not primary while renaming collection " << source.ns() << " to " << target.ns()); } Database* const sourceDB = dbHolder().get(txn, source.db()); Collection* const sourceColl = sourceDB ? sourceDB->getCollection(source.ns()) : nullptr; if (!sourceColl) { return Status(ErrorCodes::NamespaceNotFound, "source namespace does not exist"); } { // Ensure that collection name does not exceed maximum length. // Ensure that index names do not push the length over the max. // Iterator includes unfinished indexes. IndexCatalog::IndexIterator sourceIndIt = sourceColl->getIndexCatalog()->getIndexIterator(txn, true); int longestIndexNameLength = 0; while (sourceIndIt.more()) { int thisLength = sourceIndIt.next()->indexName().length(); if (thisLength > longestIndexNameLength) longestIndexNameLength = thisLength; } unsigned int longestAllowed = std::min(int(NamespaceString::MaxNsCollectionLen), int(NamespaceString::MaxNsLen) - 2 /*strlen(".$")*/ - longestIndexNameLength); if (target.size() > longestAllowed) { StringBuilder sb; sb << "collection name length of " << target.size() << " exceeds maximum length of " << longestAllowed << ", allowing for index names"; return Status(ErrorCodes::InvalidLength, sb.str()); } } BackgroundOperation::assertNoBgOpInProgForNs(source.ns()); Database* const targetDB = dbHolder().openDb(txn, target.db()); { WriteUnitOfWork wunit(txn); // Check if the target namespace exists and if dropTarget is true. // If target exists and dropTarget is not true, return false. if (targetDB->getCollection(target)) { if (!dropTarget) { return Status(ErrorCodes::NamespaceExists, "target namespace exists"); } Status s = targetDB->dropCollection(txn, target.ns()); if (!s.isOK()) { return s; } } // If we are renaming in the same database, just // rename the namespace and we're done. if (sourceDB == targetDB) { Status s = targetDB->renameCollection(txn, source.ns(), target.ns(), stayTemp); if (!s.isOK()) { return s; } getGlobalServiceContext()->getOpObserver()->onRenameCollection( txn, NamespaceString(source), NamespaceString(target), dropTarget, stayTemp); wunit.commit(); return Status::OK(); } wunit.commit(); } // If we get here, we are renaming across databases, so we must copy all the data and // indexes, then remove the source collection. // Create the target collection. It will be removed if we fail to copy the collection. // TODO use a temp collection and unset the temp flag on success. Collection* targetColl = nullptr; { CollectionOptions options = sourceColl->getCatalogEntry()->getCollectionOptions(txn); WriteUnitOfWork wunit(txn); // No logOp necessary because the entire renameCollection command is one logOp. bool shouldReplicateWrites = txn->writesAreReplicated(); txn->setReplicatedWrites(false); targetColl = targetDB->createCollection(txn, target.ns(), options, false); // _id index build with others later. txn->setReplicatedWrites(shouldReplicateWrites); if (!targetColl) { return Status(ErrorCodes::OutOfDiskSpace, "Failed to create target collection."); } wunit.commit(); } // Dismissed on success ScopeGuard targetCollectionDropper = MakeGuard(dropCollection, txn, targetDB, target.ns()); MultiIndexBlock indexer(txn, targetColl); indexer.allowInterruption(); // Copy the index descriptions from the source collection, adjusting the ns field. { std::vector<BSONObj> indexesToCopy; IndexCatalog::IndexIterator sourceIndIt = sourceColl->getIndexCatalog()->getIndexIterator(txn, true); while (sourceIndIt.more()) { const BSONObj currIndex = sourceIndIt.next()->infoObj(); // Process the source index. BSONObjBuilder newIndex; newIndex.append("ns", target.ns()); newIndex.appendElementsUnique(currIndex); indexesToCopy.push_back(newIndex.obj()); } indexer.init(indexesToCopy); } { // Copy over all the data from source collection to target collection. auto cursor = sourceColl->getCursor(txn); while (auto record = cursor->next()) { txn->checkForInterrupt(); const auto obj = record->data.releaseToBson(); WriteUnitOfWork wunit(txn); // No logOp necessary because the entire renameCollection command is one logOp. bool shouldReplicateWrites = txn->writesAreReplicated(); txn->setReplicatedWrites(false); Status status = targetColl->insertDocument(txn, obj, &indexer, true); txn->setReplicatedWrites(shouldReplicateWrites); if (!status.isOK()) return status; wunit.commit(); } } Status status = indexer.doneInserting(); if (!status.isOK()) return status; { // Getting here means we successfully built the target copy. We now remove the // source collection and finalize the rename. WriteUnitOfWork wunit(txn); bool shouldReplicateWrites = txn->writesAreReplicated(); txn->setReplicatedWrites(false); Status status = sourceDB->dropCollection(txn, source.ns()); txn->setReplicatedWrites(shouldReplicateWrites); if (!status.isOK()) return status; indexer.commit(); getGlobalServiceContext()->getOpObserver()->onRenameCollection( txn, NamespaceString(source), NamespaceString(target), dropTarget, stayTemp); wunit.commit(); } targetCollectionDropper.Dismiss(); return Status::OK(); }
virtual bool run(OperationContext* txn, const string& dbname, BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result, bool fromRepl) { if ( cmdObj.firstElement().type() != Array ) { errmsg = "ops has to be an array"; return false; } BSONObj ops = cmdObj.firstElement().Obj(); { // check input BSONObjIterator i( ops ); while ( i.more() ) { BSONElement e = i.next(); if (!_checkOperation(e, errmsg)) { return false; } } } // SERVER-4328 todo : is global ok or does this take a long time? i believe multiple // ns used so locking individually requires more analysis ScopedTransaction scopedXact(txn, MODE_X); Lock::GlobalWrite globalWriteLock(txn->lockState()); if (!fromRepl && !repl::getGlobalReplicationCoordinator()->canAcceptWritesForDatabase(dbname)) { return appendCommandStatus(result, Status(ErrorCodes::NotMaster, str::stream() << "Not primary while applying ops to database " << dbname)); } // Preconditions check reads the database state, so needs to be done locked if ( cmdObj["preCondition"].type() == Array ) { BSONObjIterator i( cmdObj["preCondition"].Obj() ); while ( i.more() ) { BSONObj f = i.next().Obj(); DBDirectClient db( txn ); BSONObj realres = db.findOne( f["ns"].String() , f["q"].Obj() ); // Apply-ops would never have a $where matcher, so use the default callback, // which will throw an error if $where is found. Matcher m(f["res"].Obj()); if ( ! m.matches( realres ) ) { result.append( "got" , realres ); result.append( "whatFailed" , f ); errmsg = "pre-condition failed"; return false; } } } // apply int num = 0; int errors = 0; BSONObjIterator i( ops ); BSONArrayBuilder ab; const bool alwaysUpsert = cmdObj.hasField("alwaysUpsert") ? cmdObj["alwaysUpsert"].trueValue() : true; while ( i.more() ) { BSONElement e = i.next(); const BSONObj& temp = e.Obj(); // Ignore 'n' operations. const char *opType = temp["op"].valuestrsafe(); if (*opType == 'n') continue; const string ns = temp["ns"].String(); // Run operations under a nested lock as a hack to prevent yielding. // // The list of operations is supposed to be applied atomically; yielding // would break atomicity by allowing an interruption or a shutdown to occur // after only some operations are applied. We are already locked globally // at this point, so taking a DBLock on the namespace creates a nested lock, // and yields are disallowed for operations that hold a nested lock. // // We do not have a wrapping WriteUnitOfWork so it is possible for a journal // commit to happen with a subset of ops applied. // TODO figure out what to do about this. Lock::GlobalWrite globalWriteLockDisallowTempRelease(txn->lockState()); // Ensures that yielding will not happen (see the comment above). DEV { Locker::LockSnapshot lockSnapshot; invariant(!txn->lockState()->saveLockStateAndUnlock(&lockSnapshot)); }; OldClientContext ctx(txn, ns); Status status(ErrorCodes::InternalError, ""); while (true) { try { // We assume that in the WriteConflict retry case, either the op rolls back // any changes it makes or is otherwise safe to rerun. status = repl::applyOperation_inlock(txn, ctx.db(), temp, false, alwaysUpsert); break; } catch (const WriteConflictException& wce) { LOG(2) << "WriteConflictException in applyOps command, retrying."; txn->recoveryUnit()->commitAndRestart(); continue; } } ab.append(status.isOK()); if (!status.isOK()) { errors++; } num++; WriteUnitOfWork wuow(txn); logOpForDbHash(txn, ns.c_str()); wuow.commit(); } result.append( "applied" , num ); result.append( "results" , ab.arr() ); if ( ! fromRepl ) { // We want this applied atomically on slaves // so we re-wrap without the pre-condition for speed string tempNS = str::stream() << dbname << ".$cmd"; // TODO: possibly use mutable BSON to remove preCondition field // once it is available BSONObjIterator iter(cmdObj); BSONObjBuilder cmdBuilder; while (iter.more()) { BSONElement elem(iter.next()); if (strcmp(elem.fieldName(), "preCondition") != 0) { cmdBuilder.append(elem); } } const BSONObj cmdRewritten = cmdBuilder.done(); // We currently always logOp the command regardless of whether the individial ops // succeeded and rely on any failures to also happen on secondaries. This isn't // perfect, but it's what the command has always done and is part of its "correct" // behavior. while (true) { try { WriteUnitOfWork wunit(txn); getGlobalEnvironment()->getOpObserver()->onApplyOps(txn, tempNS, cmdRewritten); wunit.commit(); break; } catch (const WriteConflictException& wce) { LOG(2) << "WriteConflictException while logging applyOps command, retrying."; txn->recoveryUnit()->commitAndRestart(); continue; } } } if (errors != 0) { return false; } return true; }