diff options
author | matt dannenberg <matt.dannenberg@10gen.com> | 2015-03-13 06:35:51 -0400 |
---|---|---|
committer | matt dannenberg <matt.dannenberg@10gen.com> | 2015-04-09 10:57:16 -0400 |
commit | 1725d76f448323a2bbaa11ffd37fd7b10cd6a64b (patch) | |
tree | be35489fd99d5b0040f27d86b731cfcae1e4479a /src/mongo/db/catalog | |
parent | acc7a72194990f35ff706bdcab7ec443c39fb0d5 (diff) | |
download | mongo-1725d76f448323a2bbaa11ffd37fd7b10cd6a64b.tar.gz |
SERVER-17573 move OpObserver calls into the datalayer as much as possible and eliminate repl bools
Diffstat (limited to 'src/mongo/db/catalog')
-rw-r--r-- | src/mongo/db/catalog/apply_ops.cpp | 215 | ||||
-rw-r--r-- | src/mongo/db/catalog/apply_ops.h | 45 | ||||
-rw-r--r-- | src/mongo/db/catalog/capped_utils.cpp | 261 | ||||
-rw-r--r-- | src/mongo/db/catalog/capped_utils.h | 58 | ||||
-rw-r--r-- | src/mongo/db/catalog/coll_mod.cpp | 182 | ||||
-rw-r--r-- | src/mongo/db/catalog/coll_mod.h | 45 | ||||
-rw-r--r-- | src/mongo/db/catalog/collection.cpp | 101 | ||||
-rw-r--r-- | src/mongo/db/catalog/collection.h | 34 | ||||
-rw-r--r-- | src/mongo/db/catalog/database.cpp | 28 | ||||
-rw-r--r-- | src/mongo/db/catalog/database.h | 4 | ||||
-rw-r--r-- | src/mongo/db/catalog/drop_collection.cpp | 109 | ||||
-rw-r--r-- | src/mongo/db/catalog/drop_collection.h | 43 | ||||
-rw-r--r-- | src/mongo/db/catalog/drop_database.cpp | 111 | ||||
-rw-r--r-- | src/mongo/db/catalog/drop_database.h | 38 | ||||
-rw-r--r-- | src/mongo/db/catalog/drop_indexes.cpp | 194 | ||||
-rw-r--r-- | src/mongo/db/catalog/drop_indexes.h | 46 | ||||
-rw-r--r-- | src/mongo/db/catalog/rename_collection.cpp | 292 | ||||
-rw-r--r-- | src/mongo/db/catalog/rename_collection.h | 46 |
18 files changed, 1786 insertions, 66 deletions
diff --git a/src/mongo/db/catalog/apply_ops.cpp b/src/mongo/db/catalog/apply_ops.cpp new file mode 100644 index 00000000000..2ef6d46c9d0 --- /dev/null +++ b/src/mongo/db/catalog/apply_ops.cpp @@ -0,0 +1,215 @@ +/** + * Copyright (C) 2015 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 <http://www.gnu.org/licenses/>. + * + * 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. + */ + +#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kCommand + +#include "mongo/platform/basic.h" + +#include "mongo/db/catalog/apply_ops.h" + +#include "mongo/db/client.h" +#include "mongo/db/commands/dbhash.h" +#include "mongo/db/concurrency/write_conflict_exception.h" +#include "mongo/db/db_raii.h" +#include "mongo/db/dbdirectclient.h" +#include "mongo/db/matcher/matcher.h" +#include "mongo/db/op_observer.h" +#include "mongo/db/operation_context_impl.h" +#include "mongo/db/repl/oplog.h" +#include "mongo/db/repl/replication_coordinator_global.h" +#include "mongo/db/service_context.h" +#include "mongo/util/log.h" + +namespace mongo { + Status applyOps(OperationContext* txn, + const std::string& dbName, + const BSONObj& applyOpCmd, + BSONObjBuilder* result) { + // 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()); + + bool userInitiatedWritesAndNotPrimary = txn->writesAreReplicated() && + !repl::getGlobalReplicationCoordinator()->canAcceptWritesForDatabase(dbName); + + if (userInitiatedWritesAndNotPrimary) { + return Status(ErrorCodes::NotMaster, + str::stream() << "Not primary while applying ops to database " << dbName); + } + + bool shouldReplicateWrites = txn->writesAreReplicated(); + txn->setReplicatedWrites(false); + BSONObj ops = applyOpCmd.firstElement().Obj(); + // Preconditions check reads the database state, so needs to be done locked + if (applyOpCmd["preCondition"].type() == Array) { + BSONObjIterator i(applyOpCmd["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); + txn->setReplicatedWrites(shouldReplicateWrites); + return Status(ErrorCodes::BadValue, "pre-condition failed"); + } + } + } + + // apply + int num = 0; + int errors = 0; + + BSONObjIterator i(ops); + BSONArrayBuilder ab; + const bool alwaysUpsert = applyOpCmd.hasField("alwaysUpsert") ? + applyOpCmd["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 std::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, + !txn->writesAreReplicated(), + 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()); + txn->setReplicatedWrites(shouldReplicateWrites); + + if (txn->writesAreReplicated()) { + // We want this applied atomically on slaves + // so we re-wrap without the pre-condition for speed + + std::string tempNS = str::stream() << dbName << ".$cmd"; + + // TODO: possibly use mutable BSON to remove preCondition field + // once it is available + BSONObjIterator iter(applyOpCmd); + 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); + getGlobalServiceContext()->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 Status(ErrorCodes::UnknownError, ""); + } + + return Status::OK(); + } + +} // namespace mongo diff --git a/src/mongo/db/catalog/apply_ops.h b/src/mongo/db/catalog/apply_ops.h new file mode 100644 index 00000000000..13639deb586 --- /dev/null +++ b/src/mongo/db/catalog/apply_ops.h @@ -0,0 +1,45 @@ +/** + * Copyright (C) 2015 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 <http://www.gnu.org/licenses/>. + * + * 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/base/status.h" + +namespace mongo { + class BSONObj; + class BSONObjBuilder; + class OperationContext; + + /** + * Applies ops contained in "applyOpCmd" and populates fields in "result" to be returned to the + * user. + */ + Status applyOps(OperationContext* txn, + const std::string& dbName, + const BSONObj& applyOpCmd, + BSONObjBuilder* result); + +} // namespace mongo diff --git a/src/mongo/db/catalog/capped_utils.cpp b/src/mongo/db/catalog/capped_utils.cpp new file mode 100644 index 00000000000..76be6e8d040 --- /dev/null +++ b/src/mongo/db/catalog/capped_utils.cpp @@ -0,0 +1,261 @@ +/** + * Copyright (C) 2015 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 <http://www.gnu.org/licenses/>. + * + * 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 "mongo/db/catalog/capped_utils.h" + +#include "mongo/db/background.h" +#include "mongo/db/catalog/collection.h" +#include "mongo/db/catalog/database.h" +#include "mongo/db/catalog/index_catalog.h" +#include "mongo/db/client.h" +#include "mongo/db/db_raii.h" +#include "mongo/db/index_builder.h" +#include "mongo/db/operation_context_impl.h" +#include "mongo/db/query/internal_plans.h" +#include "mongo/db/repl/replication_coordinator_global.h" +#include "mongo/db/service_context.h" + +namespace mongo { +namespace { + std::vector<BSONObj> stopIndexBuildsEmptyCapped(OperationContext* opCtx, + Database* db, + const NamespaceString& ns) { + IndexCatalog::IndexKillCriteria criteria; + criteria.ns = ns; + return IndexBuilder::killMatchingIndexBuilds(db->getCollection(ns), criteria); + } + + std::vector<BSONObj> stopIndexBuildsConvertToCapped(OperationContext* opCtx, + Database* db, + const NamespaceString& ns) { + IndexCatalog::IndexKillCriteria criteria; + criteria.ns = ns; + Collection* coll = db->getCollection(ns); + if (coll) { + return IndexBuilder::killMatchingIndexBuilds(coll, criteria); + } + return std::vector<BSONObj>(); + } + +} // namespace + + Status emptyCapped(OperationContext* txn, + const NamespaceString& collectionName) { + ScopedTransaction scopedXact(txn, MODE_IX); + AutoGetDb autoDb(txn, collectionName.db(), MODE_X); + + bool userInitiatedWritesAndNotPrimary = txn->writesAreReplicated() && + !repl::getGlobalReplicationCoordinator()->canAcceptWritesForDatabase( + collectionName.db()); + + if (userInitiatedWritesAndNotPrimary) { + return Status(ErrorCodes::NotMaster, + str::stream() << "Not primary while truncating collection " + << collectionName.ns()); + } + + Database* db = autoDb.getDb(); + massert(13429, "no such database", db); + + Collection* collection = db->getCollection(collectionName); + massert(28584, "no such collection", collection); + + std::vector<BSONObj> indexes = stopIndexBuildsEmptyCapped(txn, db, collectionName); + + WriteUnitOfWork wuow(txn); + + Status status = collection->truncate(txn); + if (!status.isOK()) { + return status; + } + + IndexBuilder::restoreIndexes(txn, indexes); + + getGlobalServiceContext()->getOpObserver()->onEmptyCapped(txn, collection->ns()); + + wuow.commit(); + + return Status::OK(); + } + + Status cloneCollectionAsCapped(OperationContext* txn, + Database* db, + const std::string& shortFrom, + const std::string& shortTo, + double size, + bool temp) { + + std::string fromNs = db->name() + "." + shortFrom; + std::string toNs = db->name() + "." + shortTo; + + Collection* fromCollection = db->getCollection(fromNs); + if (!fromCollection) + return Status(ErrorCodes::NamespaceNotFound, + str::stream() << "source collection " << fromNs << " does not exist"); + + if (db->getCollection(toNs)) + return Status(ErrorCodes::NamespaceExists, "to collection already exists"); + + // create new collection + { + OldClientContext ctx(txn, toNs); + BSONObjBuilder spec; + spec.appendBool("capped", true); + spec.append("size", size); + if (temp) + spec.appendBool("temp", true); + + WriteUnitOfWork wunit(txn); + Status status = userCreateNS(txn, ctx.db(), toNs, spec.done()); + if (!status.isOK()) + return status; + wunit.commit(); + } + + Collection* toCollection = db->getCollection(toNs); + invariant(toCollection); // we created above + + // how much data to ignore because it won't fit anyway + // datasize and extentSize can't be compared exactly, so add some padding to 'size' + + long long allocatedSpaceGuess = + std::max(static_cast<long long>(size * 2), + static_cast<long long>(toCollection->getRecordStore()->storageSize(txn) * 2)); + + long long excessSize = fromCollection->dataSize(txn) - allocatedSpaceGuess; + + boost::scoped_ptr<PlanExecutor> exec(InternalPlanner::collectionScan( + txn, + fromNs, + fromCollection, + InternalPlanner::FORWARD)); + + + while (true) { + BSONObj obj; + PlanExecutor::ExecState state = exec->getNext(&obj, NULL); + + switch(state) { + case PlanExecutor::IS_EOF: + return Status::OK(); + case PlanExecutor::DEAD: + db->dropCollection(txn, toNs); + return Status(ErrorCodes::InternalError, "executor turned dead while iterating"); + case PlanExecutor::FAILURE: + return Status(ErrorCodes::InternalError, "executor error while iterating"); + case PlanExecutor::ADVANCED: + if (excessSize > 0) { + excessSize -= (4 * obj.objsize()); // 4x is for padding, power of 2, etc... + continue; + } + + WriteUnitOfWork wunit(txn); + toCollection->insertDocument(txn, obj, true, txn->writesAreReplicated()); + wunit.commit(); + } + } + + invariant(false); // unreachable + } + + Status convertToCapped(OperationContext* txn, + const NamespaceString& collectionName, + double size) { + + StringData dbname = collectionName.db(); + StringData shortSource = collectionName.coll(); + + ScopedTransaction transaction(txn, MODE_IX); + AutoGetDb autoDb(txn, collectionName.db(), MODE_X); + + bool userInitiatedWritesAndNotPrimary = txn->writesAreReplicated() && + !repl::getGlobalReplicationCoordinator()->canAcceptWritesForDatabase(dbname); + + if (userInitiatedWritesAndNotPrimary) { + return Status(ErrorCodes::NotMaster, + str::stream() << "Not primary while converting " + << collectionName.ns() << " to a capped collection"); + } + + Database* const db = autoDb.getDb(); + if (!db) { + return Status(ErrorCodes::DatabaseNotFound, + str::stream() << "database " << dbname << " not found"); + } + + stopIndexBuildsConvertToCapped(txn, db, collectionName); + BackgroundOperation::assertNoBgOpInProgForDb(dbname); + + std::string shortTmpName = str::stream() << "tmp.convertToCapped." << shortSource; + std::string longTmpName = str::stream() << dbname << "." << shortTmpName; + + WriteUnitOfWork wunit(txn); + if (db->getCollection(longTmpName)) { + Status status = db->dropCollection(txn, longTmpName); + if (!status.isOK()) + return status; + } + + + bool shouldReplicateWrites = txn->writesAreReplicated(); + txn->setReplicatedWrites(false); + Status status = cloneCollectionAsCapped(txn, + db, + shortSource.toString(), + shortTmpName, + size, + true); + + if (!status.isOK()) { + txn->setReplicatedWrites(shouldReplicateWrites); + return status; + } + + verify(db->getCollection(longTmpName)); + + status = db->dropCollection(txn, collectionName.ns()); + txn->setReplicatedWrites(shouldReplicateWrites); + if (!status.isOK()) + return status; + + status = db->renameCollection(txn, longTmpName, collectionName.ns(), false); + if (!status.isOK()) + return status; + + getGlobalServiceContext()->getOpObserver()->onConvertToCapped( + txn, + NamespaceString(collectionName), + size); + + wunit.commit(); + return Status::OK(); + } + +} // namespace mongo diff --git a/src/mongo/db/catalog/capped_utils.h b/src/mongo/db/catalog/capped_utils.h new file mode 100644 index 00000000000..05104230fcf --- /dev/null +++ b/src/mongo/db/catalog/capped_utils.h @@ -0,0 +1,58 @@ +/** + * Copyright (C) 2015 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 <http://www.gnu.org/licenses/>. + * + * 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/base/status.h" + +namespace mongo { + class Database; + class NamespaceString; + class OperationContext; + + /** + * Drops all documents contained in the capped collection, "collectionName". + */ + Status emptyCapped(OperationContext* txn, + const NamespaceString& collectionName); + + /** + * Clones the collection "shortFrom" to the capped collection "shortTo" with a size of "size". + */ + Status cloneCollectionAsCapped(OperationContext* txn, + Database* db, + const std::string& shortFrom, + const std::string& shortTo, + double size, + bool temp); + + /** + * Converts the collection "collectionName" to a capped collection with a size of "size". + */ + Status convertToCapped(OperationContext* txn, + const NamespaceString& collectionName, + double size); +} // namespace mongo diff --git a/src/mongo/db/catalog/coll_mod.cpp b/src/mongo/db/catalog/coll_mod.cpp new file mode 100644 index 00000000000..765f2285792 --- /dev/null +++ b/src/mongo/db/catalog/coll_mod.cpp @@ -0,0 +1,182 @@ +/** + * Copyright (C) 2015 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 <http://www.gnu.org/licenses/>. + * + * 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 "mongo/db/catalog/coll_mod.h" + +#include "mongo/db/catalog/collection.h" +#include "mongo/db/catalog/collection_catalog_entry.h" +#include "mongo/db/catalog/database.h" +#include "mongo/db/client.h" +#include "mongo/db/db_raii.h" +#include "mongo/db/index/index_descriptor.h" +#include "mongo/db/repl/replication_coordinator_global.h" +#include "mongo/db/service_context.h" + +namespace mongo { + Status collMod(OperationContext* txn, + const NamespaceString& ns, + const BSONObj& cmdObj, + BSONObjBuilder* result) { + StringData dbName = ns.db(); + ScopedTransaction transaction(txn, MODE_IX); + AutoGetDb autoDb(txn, dbName, MODE_X); + Database* const db = autoDb.getDb(); + Collection* coll = db ? db->getCollection(ns) : NULL; + + // If db/collection does not exist, short circuit and return. + if (!db || !coll) { + return Status(ErrorCodes::NamespaceNotFound, "ns does not exist"); + } + + OldClientContext ctx(txn, ns); + + bool userInitiatedWritesAndNotPrimary = txn->writesAreReplicated() && + !repl::getGlobalReplicationCoordinator()->canAcceptWritesForDatabase(dbName); + + if (userInitiatedWritesAndNotPrimary) { + return Status(ErrorCodes::NotMaster, + str::stream() << "Not primary while setting collection options on " + << ns.toString()); + } + + WriteUnitOfWork wunit(txn); + + Status errorStatus = Status::OK(); + + BSONForEach(e, cmdObj) { + if (str::equals("collMod", e.fieldName())) { + // no-op + } + else if (str::startsWith(e.fieldName(), "$")) { + // no-op ignore top-level fields prefixed with $. They are for the command processor + } + else if (LiteParsedQuery::cmdOptionMaxTimeMS == e.fieldNameStringData()) { + // no-op + } + else if (str::equals("index", e.fieldName())) { + BSONObj indexObj = e.Obj(); + BSONObj keyPattern = indexObj.getObjectField("keyPattern"); + + if (keyPattern.isEmpty()){ + errorStatus = Status(ErrorCodes::InvalidOptions, "no keyPattern specified"); + continue; + } + + BSONElement newExpireSecs = indexObj["expireAfterSeconds"]; + if (newExpireSecs.eoo()) { + errorStatus = Status(ErrorCodes::InvalidOptions, "no expireAfterSeconds field"); + continue; + } + if (! newExpireSecs.isNumber()) { + errorStatus = Status(ErrorCodes::InvalidOptions, + "expireAfterSeconds field must be a number"); + continue; + } + + const IndexDescriptor* idx = coll->getIndexCatalog() + ->findIndexByKeyPattern(txn, keyPattern); + if (idx == NULL) { + errorStatus = Status(ErrorCodes::InvalidOptions, + str::stream() << "cannot find index " << keyPattern + << " for ns " << ns.toString()); + continue; + } + BSONElement oldExpireSecs = idx->infoObj().getField("expireAfterSeconds"); + if (oldExpireSecs.eoo()){ + errorStatus = Status(ErrorCodes::InvalidOptions, + "no expireAfterSeconds field to update"); + continue; + } + if (! oldExpireSecs.isNumber()) { + errorStatus = Status(ErrorCodes::InvalidOptions, + "existing expireAfterSeconds field is not a number"); + continue; + } + + if (oldExpireSecs != newExpireSecs) { + result->appendAs(oldExpireSecs, "expireAfterSeconds_old"); + // Change the value of "expireAfterSeconds" on disk. + coll->getCatalogEntry()->updateTTLSetting(txn, + idx->indexName(), + newExpireSecs.numberLong()); + // Notify the index catalog that the definition of this index changed. + idx = coll->getIndexCatalog()->refreshEntry(txn, idx); + result->appendAs(newExpireSecs , "expireAfterSeconds_new"); + } + } + else { + // As of SERVER-17312 we only support these two options. When SERVER-17320 is + // resolved this will need to be enhanced to handle other options. + typedef CollectionOptions CO; + const StringData name = e.fieldNameStringData(); + const int flag = (name == "usePowerOf2Sizes") ? CO::Flag_UsePowerOf2Sizes : + (name == "noPadding") ? CO::Flag_NoPadding : + 0; + if (!flag) { + errorStatus = Status(ErrorCodes::InvalidOptions, + str::stream() << "unknown option to collMod: " << name); + continue; + } + + CollectionCatalogEntry* cce = coll->getCatalogEntry(); + + const int oldFlags = cce->getCollectionOptions(txn).flags; + const bool oldSetting = oldFlags & flag; + const bool newSetting = e.trueValue(); + + result->appendBool(name.toString() + "_old", oldSetting); + result->appendBool(name.toString() + "_new", newSetting); + + const int newFlags = newSetting + ? (oldFlags | flag) // set flag + : (oldFlags & ~flag); // clear flag + + // NOTE we do this unconditionally to ensure that we note that the user has + // explicitly set flags, even if they are just setting the default. + cce->updateFlags(txn, newFlags); + + const CollectionOptions newOptions = cce->getCollectionOptions(txn); + invariant(newOptions.flags == newFlags); + invariant(newOptions.flagsSet); + } + } + + if (!errorStatus.isOK()) { + return errorStatus; + } + + getGlobalServiceContext()->getOpObserver()->onCollMod(txn, + (dbName.toString() + ".$cmd").c_str(), + cmdObj); + + wunit.commit(); + return Status::OK(); + } +} // namespace mongo diff --git a/src/mongo/db/catalog/coll_mod.h b/src/mongo/db/catalog/coll_mod.h new file mode 100644 index 00000000000..1f511f45145 --- /dev/null +++ b/src/mongo/db/catalog/coll_mod.h @@ -0,0 +1,45 @@ +/** + * Copyright (C) 2015 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 <http://www.gnu.org/licenses/>. + * + * 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/base/status.h" + +namespace mongo { + class BSONObj; + class BSONObjBuilder; + class NamespaceString; + class OperationContext; + + /** + * Performs the collection modification described in "cmdObj" on the collection "ns". + */ + Status collMod(OperationContext* txn, + const NamespaceString& ns, + const BSONObj& cmdObj, + BSONObjBuilder* result); +} // namespace mongo + diff --git a/src/mongo/db/catalog/collection.cpp b/src/mongo/db/catalog/collection.cpp index 2940e9c7c9b..88f8cde49b3 100644 --- a/src/mongo/db/catalog/collection.cpp +++ b/src/mongo/db/catalog/collection.cpp @@ -38,15 +38,19 @@ #include "mongo/base/counter.h" #include "mongo/base/owned_pointer_map.h" -#include "mongo/db/clientcursor.h" -#include "mongo/db/commands/server_status_metric.h" -#include "mongo/db/curop.h" #include "mongo/db/catalog/collection_catalog_entry.h" #include "mongo/db/catalog/database_catalog_entry.h" #include "mongo/db/catalog/index_create.h" +#include "mongo/db/clientcursor.h" +#include "mongo/db/commands/server_status_metric.h" +#include "mongo/db/curop.h" #include "mongo/db/index/index_access_method.h" +#include "mongo/db/op_observer.h" #include "mongo/db/operation_context.h" +#include "mongo/db/ops/update_driver.h" +#include "mongo/db/ops/update_request.h" #include "mongo/db/repl/replication_coordinator_global.h" +#include "mongo/db/service_context.h" #include "mongo/db/storage/mmap_v1/mmap_v1_options.h" #include "mongo/db/storage/record_fetcher.h" @@ -169,9 +173,9 @@ namespace mongo { return true; } - StatusWith<RecordId> Collection::insertDocument( OperationContext* txn, + StatusWith<RecordId> Collection::insertDocument(OperationContext* txn, const DocWriter* doc, - bool enforceQuota ) { + bool enforceQuota) { dassert(txn->lockState()->isCollectionLockedForMode(ns().toString(), MODE_IX)); invariant( !_indexCatalog.haveAnyIndexes() ); // eventually can implement, just not done @@ -181,12 +185,16 @@ namespace mongo { if ( !loc.isOK() ) return loc; + // we cannot call into the OpObserver here because the document being written is not present + // fortunately, this is currently only used for adding entries to the oplog. + return StatusWith<RecordId>( loc ); } - StatusWith<RecordId> Collection::insertDocument( OperationContext* txn, + StatusWith<RecordId> Collection::insertDocument(OperationContext* txn, const BSONObj& docToInsert, - bool enforceQuota ) { + bool enforceQuota, + bool fromMigrate) { const SnapshotId sid = txn->recoveryUnit()->getSnapshotId(); @@ -200,13 +208,19 @@ namespace mongo { StatusWith<RecordId> res = _insertDocument( txn, docToInsert, enforceQuota ); invariant( sid == txn->recoveryUnit()->getSnapshotId() ); + if (res.isOK()) { + getGlobalServiceContext()->getOpObserver()->onInsert(txn, + ns(), + docToInsert, + fromMigrate); + } return res; } - StatusWith<RecordId> Collection::insertDocument( OperationContext* txn, + StatusWith<RecordId> Collection::insertDocument(OperationContext* txn, const BSONObj& doc, MultiIndexBlock* indexBlock, - bool enforceQuota ) { + bool enforceQuota) { dassert(txn->lockState()->isCollectionLockedForMode(ns().toString(), MODE_IX)); StatusWith<RecordId> loc = _recordStore->insertRecord( txn, @@ -221,6 +235,8 @@ namespace mongo { if ( !status.isOK() ) return StatusWith<RecordId>( status ); + getGlobalServiceContext()->getOpObserver()->onInsert(txn, ns(), doc); + return loc; } @@ -271,11 +287,11 @@ namespace mongo { return Status::OK(); } - void Collection::deleteDocument( OperationContext* txn, - const RecordId& loc, - bool cappedOK, - bool noWarn, - BSONObj* deletedId ) { + void Collection::deleteDocument(OperationContext* txn, + const RecordId& loc, + bool cappedOK, + bool noWarn, + BSONObj* deletedId) { if ( isCapped() && !cappedOK ) { log() << "failing remove on a capped ns " << _ns << endl; uasserted( 10089, "cannot remove from a capped collection" ); @@ -284,9 +300,11 @@ namespace mongo { Snapshotted<BSONObj> doc = docFor(txn, loc); - if (deletedId) { - BSONElement e = doc.value()["_id"]; - if (e.type()) { + BSONElement e = doc.value()["_id"]; + BSONObj id; + if (e.type()) { + id = e.wrap(); + if (deletedId) { *deletedId = e.wrap(); } } @@ -299,6 +317,10 @@ namespace mongo { _recordStore->deleteRecord(txn, loc); _infoCache.notifyOfWriteOp(); + + if (!id.isEmpty()) { + getGlobalServiceContext()->getOpObserver()->onDelete(txn, ns().ns(), id); + } } Counter64 moveCounter; @@ -306,25 +328,26 @@ namespace mongo { StatusWith<RecordId> Collection::updateDocument( OperationContext* txn, const RecordId& oldLocation, - const Snapshotted<BSONObj>& objOld, - const BSONObj& objNew, + const Snapshotted<BSONObj>& oldDoc, + const BSONObj& newDoc, bool enforceQuota, bool indexesAffected, - OpDebug* debug ) { + OpDebug* debug, + oplogUpdateEntryArgs& args) { dassert(txn->lockState()->isCollectionLockedForMode(ns().toString(), MODE_IX)); - invariant(objOld.snapshotId() == txn->recoveryUnit()->getSnapshotId()); + invariant(oldDoc.snapshotId() == txn->recoveryUnit()->getSnapshotId()); SnapshotId sid = txn->recoveryUnit()->getSnapshotId(); - BSONElement oldId = objOld.value()["_id"]; - if ( !oldId.eoo() && ( oldId != objNew["_id"] ) ) + BSONElement oldId = oldDoc.value()["_id"]; + if ( !oldId.eoo() && ( oldId != newDoc["_id"] ) ) return StatusWith<RecordId>( ErrorCodes::InternalError, "in Collection::updateDocument _id mismatch", 13596 ); // At the end of this step, we will have a map of UpdateTickets, one per index, which - // represent the index updates needed to be done, based on the changes between objOld and - // objNew. + // represent the index updates needed to be done, based on the changes between oldDoc and + // newDoc. OwnedPointerMap<IndexDescriptor*,UpdateTicket> updateTickets; if ( indexesAffected ) { IndexCatalog::IndexIterator ii = _indexCatalog.getIndexIterator( txn, true ); @@ -341,8 +364,8 @@ namespace mongo { UpdateTicket* updateTicket = new UpdateTicket(); updateTickets.mutableMap()[descriptor] = updateTicket; Status ret = iam->validateUpdate(txn, - objOld.value(), - objNew, + oldDoc.value(), + newDoc, oldLocation, options, updateTicket, @@ -357,8 +380,8 @@ namespace mongo { // object is removed from all indexes. StatusWith<RecordId> newLocation = _recordStore->updateRecord( txn, oldLocation, - objNew.objdata(), - objNew.objsize(), + newDoc.objdata(), + newDoc.objsize(), _enforceQuota( enforceQuota ), this ); @@ -381,10 +404,13 @@ namespace mongo { debug->nmoved += 1; } - Status s = _indexCatalog.indexRecord(txn, objNew, newLocation.getValue()); + Status s = _indexCatalog.indexRecord(txn, newDoc, newLocation.getValue()); if (!s.isOK()) return StatusWith<RecordId>(s); invariant( sid == txn->recoveryUnit()->getSnapshotId() ); + args.ns = ns().ns(); + getGlobalServiceContext()->getOpObserver()->onUpdate(txn, args); + return newLocation; } @@ -410,6 +436,9 @@ namespace mongo { } invariant( sid == txn->recoveryUnit()->getSnapshotId() ); + args.ns = ns().ns(); + getGlobalServiceContext()->getOpObserver()->onUpdate(txn, args); + return newLocation; } @@ -435,7 +464,8 @@ namespace mongo { const RecordId& loc, const Snapshotted<RecordData>& oldRec, const char* damageSource, - const mutablebson::DamageVector& damages ) { + const mutablebson::DamageVector& damages, + oplogUpdateEntryArgs& args) { dassert(txn->lockState()->isCollectionLockedForMode(ns().toString(), MODE_IX)); invariant(oldRec.snapshotId() == txn->recoveryUnit()->getSnapshotId()); @@ -443,7 +473,14 @@ namespace mongo { // Broadcast the mutation so that query results stay correct. _cursorManager.invalidateDocument(txn, loc, INVALIDATION_MUTATION); - return _recordStore->updateWithDamages(txn, loc, oldRec.value(), damageSource, damages); + Status status = + _recordStore->updateWithDamages(txn, loc, oldRec.value(), damageSource, damages); + + if (status.isOK()) { + args.ns = ns().ns(); + getGlobalServiceContext()->getOpObserver()->onUpdate(txn, args); + } + return status; } bool Collection::_enforceQuota( bool userEnforeQuota ) const { diff --git a/src/mongo/db/catalog/collection.h b/src/mongo/db/catalog/collection.h index f1518065aaf..9f537cb5fdb 100644 --- a/src/mongo/db/catalog/collection.h +++ b/src/mongo/db/catalog/collection.h @@ -39,6 +39,7 @@ #include "mongo/db/catalog/index_catalog.h" #include "mongo/db/exec/collection_scan_common.h" #include "mongo/db/namespace_string.h" +#include "mongo/db/op_observer.h" #include "mongo/db/record_id.h" #include "mongo/db/storage/capped_callback.h" #include "mongo/db/storage/record_store.h" @@ -54,6 +55,9 @@ namespace mongo { class MultiIndexBlock; class OperationContext; + class UpdateDriver; + class UpdateRequest; + class RecordIterator; class RecordFetcher; @@ -167,7 +171,8 @@ namespace mongo { */ StatusWith<RecordId> insertDocument( OperationContext* txn, const BSONObj& doc, - bool enforceQuota ); + bool enforceQuota, + bool fromMigrate = false); StatusWith<RecordId> insertDocument( OperationContext* txn, const DocWriter* doc, @@ -196,22 +201,23 @@ namespace mongo { * if not, it is moved * @return the post update location of the doc (may or may not be the same as oldLocation) */ - StatusWith<RecordId> updateDocument( OperationContext* txn, - const RecordId& oldLocation, - const Snapshotted<BSONObj>& oldDoc, - const BSONObj& newDoc, - bool enforceQuota, - bool indexesAffected, - OpDebug* debug ); - + StatusWith<RecordId> updateDocument(OperationContext* txn, + const RecordId& oldLocation, + const Snapshotted<BSONObj>& oldDoc, + const BSONObj& newDoc, + bool enforceQuota, + bool indexesAffected, + OpDebug* debug, + oplogUpdateEntryArgs& args); /** * right now not allowed to modify indexes */ - Status updateDocumentWithDamages( OperationContext* txn, - const RecordId& loc, - const Snapshotted<RecordData>& oldRec, - const char* damageSource, - const mutablebson::DamageVector& damages ); + Status updateDocumentWithDamages(OperationContext* txn, + const RecordId& loc, + const Snapshotted<RecordData>& oldRec, + const char* damageSource, + const mutablebson::DamageVector& damages, + oplogUpdateEntryArgs& args); // ----------- diff --git a/src/mongo/db/catalog/database.cpp b/src/mongo/db/catalog/database.cpp index 814184ef694..46b58a16618 100644 --- a/src/mongo/db/catalog/database.cpp +++ b/src/mongo/db/catalog/database.cpp @@ -55,6 +55,7 @@ #include "mongo/db/introspect.h" #include "mongo/db/op_observer.h" #include "mongo/db/repl/oplog.h" +#include "mongo/db/repl/replication_coordinator_global.h" #include "mongo/db/server_parameters.h" #include "mongo/db/stats/top.h" #include "mongo/db/storage_options.h" @@ -272,8 +273,6 @@ namespace mongo { continue; } - getGlobalServiceContext()->getOpObserver()->onDropCollection( - txn, NamespaceString(ns)); wunit.commit(); } catch (const WriteConflictException& exp) { @@ -351,7 +350,7 @@ namespace mongo { _dbEntry->appendExtraStats( opCtx, output, scale ); } - Status Database::dropCollection( OperationContext* txn, StringData fullns ) { + Status Database::dropCollection(OperationContext* txn, StringData fullns) { invariant(txn->lockState()->isDbLockedForMode(name(), MODE_X)); LOG(1) << "dropCollection: " << fullns << endl; @@ -363,12 +362,12 @@ namespace mongo { return Status::OK(); } + NamespaceString nss(fullns); { - NamespaceString s( fullns ); - verify( s.db() == _name ); + verify(nss.db() == _name); - if( s.isSystem() ) { - if( s.coll() == "system.profile" ) { + if (nss.isSystem()) { + if (nss.isSystemDotProfile()) { if ( _profile != 0 ) return Status( ErrorCodes::IllegalOperation, "turn off profiling before dropping system.profile collection" ); @@ -417,6 +416,7 @@ namespace mongo { } } + getGlobalServiceContext()->getOpObserver()->onDropCollection(txn, nss); return Status::OK(); } @@ -486,7 +486,6 @@ namespace mongo { Collection* Database::createCollection( OperationContext* txn, StringData ns, const CollectionOptions& options, - bool allocateDefaultSpace, bool createIdIndex ) { massert( 17399, "collection already exists", getCollection( ns ) == NULL ); massertNamespaceNotIndex( ns, "createCollection" ); @@ -513,7 +512,7 @@ namespace mongo { txn->recoveryUnit()->registerChange( new AddCollectionChange(this, ns) ); - Status status = _dbEntry->createCollection(txn, ns, options, allocateDefaultSpace); + Status status = _dbEntry->createCollection(txn, ns, options, true /*allocateDefaultSpace*/); massertNoTraceStatusOK(status); @@ -537,6 +536,8 @@ namespace mongo { } + getGlobalServiceContext()->getOpObserver()->onCreateCollection(txn, nss, options); + return collection; } @@ -592,7 +593,6 @@ namespace mongo { Database* db, StringData ns, BSONObj options, - bool logForReplication, bool createDefaultIndexes ) { invariant( db ); @@ -619,13 +619,7 @@ namespace mongo { if ( !status.isOK() ) return status; - invariant( db->createCollection( txn, ns, collectionOptions, true, createDefaultIndexes ) ); - - if ( logForReplication ) { - getGlobalServiceContext()->getOpObserver()->onCreateCollection(txn, - NamespaceString(ns), - collectionOptions); - } + invariant(db->createCollection(txn, ns, collectionOptions, createDefaultIndexes)); return Status::OK(); } diff --git a/src/mongo/db/catalog/database.h b/src/mongo/db/catalog/database.h index 57fbece74d8..251946310fb 100644 --- a/src/mongo/db/catalog/database.h +++ b/src/mongo/db/catalog/database.h @@ -82,12 +82,11 @@ namespace mongo { const DatabaseCatalogEntry* getDatabaseCatalogEntry() const; - Status dropCollection( OperationContext* txn, StringData fullns ); + Status dropCollection(OperationContext* txn, StringData fullns); Collection* createCollection( OperationContext* txn, StringData ns, const CollectionOptions& options = CollectionOptions(), - bool allocateSpace = true, bool createDefaultIndexes = true ); /** @@ -162,7 +161,6 @@ namespace mongo { Database* db, StringData ns, BSONObj options, - bool logForReplication, bool createDefaultIndexes = true ); } // namespace mongo diff --git a/src/mongo/db/catalog/drop_collection.cpp b/src/mongo/db/catalog/drop_collection.cpp new file mode 100644 index 00000000000..bf58462449f --- /dev/null +++ b/src/mongo/db/catalog/drop_collection.cpp @@ -0,0 +1,109 @@ +/** + * Copyright (C) 2015 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 <http://www.gnu.org/licenses/>. + * + * 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. + */ + +#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kCommand + +#include "mongo/platform/basic.h" + +#include "mongo/db/catalog/drop_collection.h" + +#include "mongo/db/catalog/collection.h" +#include "mongo/db/catalog/database.h" +#include "mongo/db/catalog/index_catalog.h" +#include "mongo/db/client.h" +#include "mongo/db/concurrency/write_conflict_exception.h" +#include "mongo/db/curop.h" +#include "mongo/db/db_raii.h" +#include "mongo/db/index_builder.h" +#include "mongo/db/operation_context_impl.h" +#include "mongo/db/repl/replication_coordinator_global.h" +#include "mongo/db/server_options.h" +#include "mongo/db/service_context.h" +#include "mongo/util/log.h" + +namespace mongo { +namespace { + std::vector<BSONObj> stopIndexBuilds(OperationContext* opCtx, + Database* db, + const NamespaceString& collectionName) { + IndexCatalog::IndexKillCriteria criteria; + criteria.ns = collectionName; + return IndexBuilder::killMatchingIndexBuilds(db->getCollection(collectionName), + criteria); + } + +} // namespace + Status dropCollection(OperationContext* txn, + const NamespaceString& collectionName, + BSONObjBuilder& result) { + if (!serverGlobalParams.quiet) { + log() << "CMD: drop " << collectionName; + } + + std::string dbname = collectionName.db().toString(); + + MONGO_WRITE_CONFLICT_RETRY_LOOP_BEGIN { + ScopedTransaction transaction(txn, MODE_IX); + + AutoGetDb autoDb(txn, dbname, MODE_X); + Database* const db = autoDb.getDb(); + Collection* coll = db ? db->getCollection(collectionName) : nullptr; + + // If db/collection does not exist, short circuit and return. + if ( !db || !coll ) { + return Status(ErrorCodes::NamespaceNotFound, "ns not found"); + } + OldClientContext context(txn, collectionName); + + bool userInitiatedWritesAndNotPrimary = txn->writesAreReplicated() && + !repl::getGlobalReplicationCoordinator()->canAcceptWritesForDatabase(dbname); + + if (userInitiatedWritesAndNotPrimary) { + return Status(ErrorCodes::NotMaster, + str::stream() << "Not primary while dropping collection " + << collectionName.ns()); + } + + int numIndexes = coll->getIndexCatalog()->numIndexesTotal(txn); + + stopIndexBuilds(txn, db, collectionName); + + result.append("ns", collectionName); + result.append("nIndexesWas", numIndexes); + WriteUnitOfWork wunit(txn); + Status s = db->dropCollection(txn, collectionName.ns()); + + if ( !s.isOK() ) { + return s; + } + + wunit.commit(); + } MONGO_WRITE_CONFLICT_RETRY_LOOP_END(txn, "drop", collectionName.ns()); + return Status::OK(); + } +} // namespace mongo diff --git a/src/mongo/db/catalog/drop_collection.h b/src/mongo/db/catalog/drop_collection.h new file mode 100644 index 00000000000..a12f5e8419c --- /dev/null +++ b/src/mongo/db/catalog/drop_collection.h @@ -0,0 +1,43 @@ +/** + * Copyright (C) 2015 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 <http://www.gnu.org/licenses/>. + * + * 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/base/status.h" + +namespace mongo { + class BSONObjBuilder; + class NamespaceString; + class OperationContext; + + /** + * Drops the collection "collectionName" and populates "result" with statistics about what + * was removed. + */ + Status dropCollection(OperationContext* txn, + const NamespaceString& collectionName, + BSONObjBuilder& result); +} // namespace mongo diff --git a/src/mongo/db/catalog/drop_database.cpp b/src/mongo/db/catalog/drop_database.cpp new file mode 100644 index 00000000000..655305d216e --- /dev/null +++ b/src/mongo/db/catalog/drop_database.cpp @@ -0,0 +1,111 @@ +/** + * Copyright (C) 2015 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 <http://www.gnu.org/licenses/>. + * + * 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. + */ + +#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kCommand + +#include "mongo/platform/basic.h" + +#include "mongo/db/catalog/drop_database.h" + +#include "mongo/db/catalog/database.h" +#include "mongo/db/catalog/database_catalog_entry.h" +#include "mongo/db/catalog/index_catalog.h" +#include "mongo/db/client.h" +#include "mongo/db/concurrency/write_conflict_exception.h" +#include "mongo/db/curop.h" +#include "mongo/db/db_raii.h" +#include "mongo/db/index_builder.h" +#include "mongo/db/op_observer.h" +#include "mongo/db/operation_context_impl.h" +#include "mongo/db/repl/replication_coordinator_global.h" +#include "mongo/db/service_context.h" +#include "mongo/util/log.h" + +namespace mongo { +namespace { + std::vector<BSONObj> stopIndexBuilds(OperationContext* opCtx, Database* db) { + invariant(db); + std::list<std::string> collections; + db->getDatabaseCatalogEntry()->getCollectionNamespaces(&collections); + + std::vector<BSONObj> allKilledIndexes; + for (std::list<std::string>::iterator it = collections.begin(); + it != collections.end(); + ++it) { + std::string ns = *it; + + IndexCatalog::IndexKillCriteria criteria; + criteria.ns = ns; + std::vector<BSONObj> killedIndexes = + IndexBuilder::killMatchingIndexBuilds(db->getCollection(ns), criteria); + allKilledIndexes.insert(allKilledIndexes.end(), + killedIndexes.begin(), + killedIndexes.end()); + } + return allKilledIndexes; + } +} // namespace + + Status dropDatabase(OperationContext* txn, const std::string& dbName) { + MONGO_WRITE_CONFLICT_RETRY_LOOP_BEGIN { + ScopedTransaction transaction(txn, MODE_X); + Lock::GlobalWrite lk(txn->lockState()); + AutoGetDb autoDB(txn, dbName, MODE_X); + Database* const db = autoDB.getDb(); + if (!db) { + // DB doesn't exist, so deem it a success. + return Status::OK(); + } + OldClientContext context(txn, dbName); + + bool userInitiatedWritesAndNotPrimary = txn->writesAreReplicated() && + !repl::getGlobalReplicationCoordinator()->canAcceptWritesForDatabase(dbName); + + if (userInitiatedWritesAndNotPrimary) { + return Status(ErrorCodes::NotMaster, + str::stream() << "Not primary while dropping database " << dbName); + } + + log() << "dropDatabase " << dbName << " starting"; + + stopIndexBuilds(txn, db); + mongo::dropDatabase(txn, db); + + log() << "dropDatabase " << dbName << " finished"; + + WriteUnitOfWork wunit(txn); + + getGlobalServiceContext()->getOpObserver()->onDropDatabase(txn, dbName + ".$cmd"); + + wunit.commit(); + } MONGO_WRITE_CONFLICT_RETRY_LOOP_END(txn, "dropDatabase", dbName); + + return Status::OK(); + } + +} // namespace mongo diff --git a/src/mongo/db/catalog/drop_database.h b/src/mongo/db/catalog/drop_database.h new file mode 100644 index 00000000000..184d66d5bf2 --- /dev/null +++ b/src/mongo/db/catalog/drop_database.h @@ -0,0 +1,38 @@ +/** + * Copyright (C) 2015 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 <http://www.gnu.org/licenses/>. + * + * 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/base/status.h" + +namespace mongo { + class OperationContext; + + /** + * Drops the database "dbName". + */ + Status dropDatabase(OperationContext* txn, const std::string& dbName); +} // namespace mongo diff --git a/src/mongo/db/catalog/drop_indexes.cpp b/src/mongo/db/catalog/drop_indexes.cpp new file mode 100644 index 00000000000..0ac1c2dae13 --- /dev/null +++ b/src/mongo/db/catalog/drop_indexes.cpp @@ -0,0 +1,194 @@ +/** + * Copyright (C) 2015 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 <http://www.gnu.org/licenses/>. + * + * 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. + */ + +#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kCommand + +#include "mongo/platform/basic.h" + +#include "mongo/db/catalog/drop_indexes.h" + +#include "mongo/db/catalog/collection.h" +#include "mongo/db/catalog/database.h" +#include "mongo/db/catalog/index_catalog.h" +#include "mongo/db/client.h" +#include "mongo/db/concurrency/write_conflict_exception.h" +#include "mongo/db/curop.h" +#include "mongo/db/db_raii.h" +#include "mongo/db/index/index_descriptor.h" +#include "mongo/db/index_builder.h" +#include "mongo/db/repl/replication_coordinator_global.h" +#include "mongo/db/service_context.h" +#include "mongo/util/log.h" + +namespace mongo { +namespace { + std::vector<BSONObj> stopIndexBuilds(OperationContext* opCtx, + Database* db, + const std::string& toDeleteNs, + const BSONObj& cmdObj) { + Collection* collection = db->getCollection(toDeleteNs); + IndexCatalog::IndexKillCriteria criteria; + + // Get index name to drop + BSONElement toDrop = cmdObj.getField("index"); + + if (toDrop.type() == String) { + // Kill all in-progress indexes + if (strcmp("*", toDrop.valuestr()) == 0) { + criteria.ns = toDeleteNs; + return IndexBuilder::killMatchingIndexBuilds(collection, criteria); + } + // Kill an in-progress index by name + else { + criteria.name = toDrop.valuestr(); + return IndexBuilder::killMatchingIndexBuilds(collection, criteria); + } + } + // Kill an in-progress index build by index key + else if (toDrop.type() == Object) { + criteria.key = toDrop.Obj(); + return IndexBuilder::killMatchingIndexBuilds(collection, criteria); + } + + return std::vector<BSONObj>(); + } + + Status wrappedRun(OperationContext* txn, + const StringData& dbname, + const std::string& toDeleteNs, + Database* const db, + const BSONObj& jsobj, + BSONObjBuilder* anObjBuilder) { + if (!serverGlobalParams.quiet) { + LOG(0) << "CMD: dropIndexes " << toDeleteNs; + } + Collection* collection = db ? db->getCollection(toDeleteNs) : nullptr; + + // If db/collection does not exist, short circuit and return. + if (!db || !collection) { + return Status(ErrorCodes::NamespaceNotFound, "ns not found"); + } + + OldClientContext ctx(txn, toDeleteNs); + stopIndexBuilds(txn, db, toDeleteNs, jsobj); + + IndexCatalog* indexCatalog = collection->getIndexCatalog(); + anObjBuilder->appendNumber("nIndexesWas", indexCatalog->numIndexesTotal(txn)); + + + BSONElement f = jsobj.getField("index"); + if (f.type() == String) { + + std::string indexToDelete = f.valuestr(); + + if (indexToDelete == "*") { + Status s = indexCatalog->dropAllIndexes(txn, false); + if (!s.isOK()) { + return s; + } + anObjBuilder->append("msg", "non-_id indexes dropped for collection"); + return Status::OK(); + } + + IndexDescriptor* desc = collection->getIndexCatalog()->findIndexByName(txn, + indexToDelete); + if (desc == NULL) { + return Status(ErrorCodes::IndexNotFound, + str::stream() << "index not found with name [" + << indexToDelete << "]"); + } + + if (desc->isIdIndex()) { + return Status(ErrorCodes::InvalidOptions, "cannot drop _id index"); + } + + Status s = indexCatalog->dropIndex(txn, desc); + if (!s.isOK()) { + return s; + } + + return Status::OK(); + } + + if (f.type() == Object) { + IndexDescriptor* desc = + collection->getIndexCatalog()->findIndexByKeyPattern(txn, f.embeddedObject()); + if (desc == NULL) { + return Status(ErrorCodes::InvalidOptions, + str::stream() << "can't find index with key: " + << f.embeddedObject().toString()); + } + + if (desc->isIdIndex()) { + return Status(ErrorCodes::InvalidOptions, "cannot drop _id index"); + } + + Status s = indexCatalog->dropIndex(txn, desc); + if (!s.isOK()) { + return s; + } + + return Status::OK(); + } + + return Status(ErrorCodes::IndexNotFound, "invalid index name spec"); + } +} // namespace + + Status dropIndexes(OperationContext* txn, + const NamespaceString& ns, + const BSONObj& idxDescriptor, + BSONObjBuilder* result) { + StringData dbName = ns.db(); + MONGO_WRITE_CONFLICT_RETRY_LOOP_BEGIN { + ScopedTransaction transaction(txn, MODE_IX); + AutoGetDb autoDb(txn, dbName, MODE_X); + + bool userInitiatedWritesAndNotPrimary = txn->writesAreReplicated() && + !repl::getGlobalReplicationCoordinator()->canAcceptWritesForDatabase(dbName); + + if (userInitiatedWritesAndNotPrimary) { + return Status(ErrorCodes::NotMaster, + str::stream() << "Not primary while dropping indexes in " + << ns.toString()); + } + + WriteUnitOfWork wunit(txn); + Status status = wrappedRun(txn, dbName, ns, autoDb.getDb(), idxDescriptor, result); + if (!status.isOK()) { + return status; + } + getGlobalServiceContext()->getOpObserver()->onDropIndex(txn, + dbName.toString() + ".$cmd", + idxDescriptor); + wunit.commit(); + } MONGO_WRITE_CONFLICT_RETRY_LOOP_END(txn, "dropIndexes", dbName); + return Status::OK(); + } + +} // namespace mongo diff --git a/src/mongo/db/catalog/drop_indexes.h b/src/mongo/db/catalog/drop_indexes.h new file mode 100644 index 00000000000..ba07687098e --- /dev/null +++ b/src/mongo/db/catalog/drop_indexes.h @@ -0,0 +1,46 @@ +/** + * Copyright (C) 2015 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 <http://www.gnu.org/licenses/>. + * + * 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/base/status.h" + +namespace mongo { + class BSONObj; + class BSONObjBuilder; + class NamespaceString; + class OperationContext; + + /** + * Drops the index from collection "ns" that matches the "idxDescriptor" and populates + * "result" with some statistics about the dropped index. + */ + Status dropIndexes(OperationContext* txn, + const NamespaceString& ns, + const BSONObj& idxDescriptor, + BSONObjBuilder* result); + +} // namespace mongo diff --git a/src/mongo/db/catalog/rename_collection.cpp b/src/mongo/db/catalog/rename_collection.cpp new file mode 100644 index 00000000000..8d21fea5d8c --- /dev/null +++ b/src/mongo/db/catalog/rename_collection.cpp @@ -0,0 +1,292 @@ +/** + * Copyright (C) 2015 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 <http://www.gnu.org/licenses/>. + * + * 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. + */ + +#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kCommand + +#include "mongo/platform/basic.h" + +#include "mongo/db/catalog/rename_collection.h" + +#include "mongo/db/catalog/collection.h" +#include "mongo/db/catalog/collection_catalog_entry.h" +#include "mongo/db/catalog/database.h" +#include "mongo/db/catalog/database_holder.h" +#include "mongo/db/catalog/index_catalog.h" +#include "mongo/db/catalog/index_create.h" +#include "mongo/db/client.h" +#include "mongo/db/db_raii.h" +#include "mongo/db/index/index_descriptor.h" +#include "mongo/db/index_builder.h" +#include "mongo/db/jsobj.h" +#include "mongo/db/repl/replication_coordinator_global.h" +#include "mongo/db/service_context.h" +#include "mongo/util/scopeguard.h" + +namespace mongo { +namespace { + static void dropCollection(OperationContext* txn, Database* db, StringData collName) { + WriteUnitOfWork wunit(txn); + if (db->dropCollection(txn, collName).isOK()) { + // ignoring failure case + wunit.commit(); + } + } + + // renameCollection's + std::vector<BSONObj> stopIndexBuilds(OperationContext* opCtx, + Database* db, + const NamespaceString& source, + const NamespaceString& target) { + + IndexCatalog::IndexKillCriteria criteria; + criteria.ns = source; + std::vector<BSONObj> prelim = + IndexBuilder::killMatchingIndexBuilds(db->getCollection(source), criteria); + + std::vector<BSONObj> indexes; + + for (int i = 0; i < static_cast<int>(prelim.size()); i++) { + // Change the ns + BSONObj stripped = prelim[i].removeField("ns"); + BSONObjBuilder builder; + builder.appendElements(stripped); + builder.append("ns", target); + indexes.push_back(builder.obj()); + } + + return indexes; + } +} // namespace + + Status renameCollection(OperationContext* txn, + const NamespaceString& source, + const NamespaceString& target, + bool dropTarget, + bool stayTemp) { + 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); + + bool userInitiatedWritesAndNotPrimary = txn->writesAreReplicated() && + !repl::getGlobalReplicationCoordinator()->canAcceptWritesForDatabase(source.db()); + + 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()); + } + } + + const std::vector<BSONObj> indexesInProg = stopIndexBuilds(txn, sourceDB, source, target); + // Dismissed on success + ScopeGuard indexBuildRestorer = MakeGuard(IndexBuilder::restoreIndexes, txn, indexesInProg); + + 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) { + printStackTrace(); + 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(); + indexBuildRestorer.Dismiss(); + 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; + 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. + bool shouldReplicateWrites = txn->writesAreReplicated(); + txn->setReplicatedWrites(false); + targetColl = targetDB->createCollection(txn, target.ns(), options); + 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); + 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(); + + const Snapshotted<BSONObj> obj = sourceColl->docFor(txn, sourceIt->getNext()); + + 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.value(), &indexer, true).getStatus(); + 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(); + } + + indexBuildRestorer.Dismiss(); + targetCollectionDropper.Dismiss(); + return Status::OK(); + } + +} // namespace mongo diff --git a/src/mongo/db/catalog/rename_collection.h b/src/mongo/db/catalog/rename_collection.h new file mode 100644 index 00000000000..1ec0b754779 --- /dev/null +++ b/src/mongo/db/catalog/rename_collection.h @@ -0,0 +1,46 @@ +/** + * Copyright (C) 2015 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 <http://www.gnu.org/licenses/>. + * + * 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/base/status.h" + +namespace mongo { + class NamespaceString; + class OperationContext; + + /** + * Renames the collection "source" to "target" and drops the existing collection named "target" + * iff "dropTarget" is true. "stayTemp" indicates whether a collection should maintain its + * temporariness. + */ + Status renameCollection(OperationContext* txn, + const NamespaceString& source, + const NamespaceString& target, + bool dropTarget, + bool stayTemp); + +} // namespace mongo |