diff options
Diffstat (limited to 'src/mongo')
-rw-r--r-- | src/mongo/db/catalog/rename_collection.cpp | 115 | ||||
-rw-r--r-- | src/mongo/db/catalog/rename_collection.h | 8 | ||||
-rw-r--r-- | src/mongo/db/catalog/rename_collection_test.cpp | 228 |
3 files changed, 320 insertions, 31 deletions
diff --git a/src/mongo/db/catalog/rename_collection.cpp b/src/mongo/db/catalog/rename_collection.cpp index 5ea50be213c..1d93179c373 100644 --- a/src/mongo/db/catalog/rename_collection.cpp +++ b/src/mongo/db/catalog/rename_collection.cpp @@ -68,6 +68,46 @@ NamespaceString getNamespaceFromUUIDElement(OperationContext* opCtx, const BSONE return getNamespaceFromUUID(opCtx, uuid); } +Status renameTargetCollectionToTmp(OperationContext* opCtx, + const NamespaceString& sourceNs, + const UUID& sourceUUID, + Database* const targetDB, + const NamespaceString& targetNs, + const UUID& targetUUID) { + repl::UnreplicatedWritesBlock uwb(opCtx); + + auto tmpNameResult = targetDB->makeUniqueCollectionNamespace(opCtx, "tmp%%%%%.rename"); + if (!tmpNameResult.isOK()) { + return tmpNameResult.getStatus().withContext( + str::stream() << "Cannot generate a temporary collection name for the target " + << targetNs.ns() + << " (" + << targetUUID + << ") so that the source" + << sourceNs.ns() + << " (" + << sourceUUID + << ") could be renamed to " + << targetNs.ns()); + } + const auto& tmpName = tmpNameResult.getValue(); + const bool stayTemp = true; + return writeConflictRetry(opCtx, "renameCollection", targetNs.ns(), [&] { + WriteUnitOfWork wunit(opCtx); + auto status = targetDB->renameCollection(opCtx, targetNs.ns(), tmpName.ns(), stayTemp); + if (!status.isOK()) + return status; + + wunit.commit(); + + log() << "Successfully renamed the target " << targetNs.ns() << " (" << targetUUID + << ") to " << tmpName << " so that the source " << sourceNs.ns() << " (" << sourceUUID + << ") could be renamed to " << targetNs.ns(); + + return Status::OK(); + }); +} + Status renameCollectionCommon(OperationContext* opCtx, const NamespaceString& source, const NamespaceString& target, @@ -75,8 +115,8 @@ Status renameCollectionCommon(OperationContext* opCtx, repl::OpTime renameOpTimeFromApplyOps, const RenameCollectionOptions& options) { auto uuidString = targetUUID ? targetUUID->toString() : "no UUID"; - log() << "renameCollection: renaming collection " << source << " to " << target << " (" - << uuidString << ")"; + log() << "renameCollection: renaming collection " << uuidString << " from " << source << " to " + << target; // A valid 'renameOpTimeFromApplyOps' is not allowed when writes are replicated. if (!renameOpTimeFromApplyOps.isNull() && opCtx->writesAreReplicated()) { @@ -157,11 +197,34 @@ Status renameCollectionCommon(OperationContext* opCtx, return Status(ErrorCodes::NamespaceExists, "target namespace exists"); } + // If UUID doesn't point to the existing target, we should rename the target rather than + // drop it. + if (options.dropTargetUUID && options.dropTargetUUID != targetColl->uuid()) { + auto dropTargetNssFromUUID = getNamespaceFromUUID(opCtx, options.dropTargetUUID.get()); + // We need to rename the targetColl to a temporary name. + auto status = renameTargetCollectionToTmp( + opCtx, source, targetUUID.get(), targetDB, target, targetColl->uuid().get()); + if (!status.isOK()) + return status; + targetColl = nullptr; + } } else if (targetDB->getViewCatalog()->lookup(opCtx, target.ns())) { return Status(ErrorCodes::NamespaceExists, str::stream() << "a view already exists with that name: " << target.ns()); } + // When reapplying oplog entries (such as in the case of initial sync) we need + // to identify the collection to drop by UUID, as otherwise we might end up + // dropping the wrong collection. + if (!targetColl && options.dropTargetUUID) { + invariant(options.dropTarget); + auto dropTargetNssFromUUID = getNamespaceFromUUID(opCtx, options.dropTargetUUID.get()); + if (!dropTargetNssFromUUID.isEmpty() && !dropTargetNssFromUUID.isDropPendingNamespace()) { + invariant(dropTargetNssFromUUID.db() == target.db()); + targetColl = targetDB->getCollection(opCtx, dropTargetNssFromUUID); + } + } + auto sourceUUID = sourceColl->uuid(); // If we are renaming in the same database, just rename the namespace and we're done. if (sourceDB == targetDB) { @@ -206,7 +269,7 @@ Status renameCollectionCommon(OperationContext* opCtx, // No logOp necessary because the entire renameCollection command is one logOp. repl::UnreplicatedWritesBlock uwb(opCtx); - status = targetDB->dropCollection(opCtx, target.ns(), renameOpTime); + status = targetDB->dropCollection(opCtx, targetColl->ns().ns(), renameOpTime); if (!status.isOK()) { return status; } @@ -435,22 +498,48 @@ Status renameCollectionForApplyOps(OperationContext* opCtx, sourceNss = uiNss; } - if (sourceNss.isDropPendingNamespace()) { - return Status(ErrorCodes::NamespaceNotFound, - str::stream() << "renameCollectionForApplyOps() cannot accept a source " - "collection that is in a drop-pending state: " - << sourceNss.toString() - << " (UUID: " - << ui.toString(false) - << ")"); - } - OptionalCollectionUUID targetUUID; if (!ui.eoo()) targetUUID = uassertStatusOK(UUID::parse(ui)); RenameCollectionOptions options; options.dropTarget = cmd["dropTarget"].trueValue(); + if (cmd["dropTarget"].type() == BinData) { + auto uuid = uassertStatusOK(UUID::parse(cmd["dropTarget"])); + options.dropTargetUUID = uuid; + } + + const Collection* const sourceColl = + AutoGetCollectionForRead(opCtx, sourceNss, AutoGetCollection::ViewMode::kViewsPermitted) + .getCollection(); + + if (sourceNss.isDropPendingNamespace() || sourceColl == nullptr) { + NamespaceString dropTargetNss; + + if (options.dropTarget) + dropTargetNss = targetNss; + + if (options.dropTargetUUID) { + dropTargetNss = getNamespaceFromUUID(opCtx, options.dropTargetUUID.get()); + } + + // Downgrade renameCollection to dropCollection. + if (!dropTargetNss.isEmpty()) { + BSONObjBuilder unusedResult; + return dropCollection(opCtx, + dropTargetNss, + unusedResult, + renameOpTime, + DropCollectionSystemCollectionMode::kAllowSystemCollectionDrops); + } + + return Status(ErrorCodes::NamespaceNotFound, + str::stream() + << "renameCollection() cannot accept a source " + "collection that does not exist or is in a drop-pending state: " + << sourceNss.toString()); + } + options.stayTemp = cmd["stayTemp"].trueValue(); return renameCollectionCommon(opCtx, sourceNss, targetNss, targetUUID, renameOpTime, options); } diff --git a/src/mongo/db/catalog/rename_collection.h b/src/mongo/db/catalog/rename_collection.h index a744a339fec..83ccfacbc8c 100644 --- a/src/mongo/db/catalog/rename_collection.h +++ b/src/mongo/db/catalog/rename_collection.h @@ -29,6 +29,7 @@ #include "mongo/base/status.h" #include "mongo/bson/bsonelement.h" #include "mongo/bson/bsonobj.h" +#include "mongo/db/catalog/collection_options.h" #include "mongo/util/uuid.h" namespace mongo { @@ -40,12 +41,13 @@ class OpTime; } // namespace repl /** - * 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. + * Renames the collection from "source" to "target" and drops the existing collection with UUID + * dropTargetUUID iff "dropTarget" is true. "stayTemp" indicates whether a collection should + * maintain its temporariness. */ struct RenameCollectionOptions { bool dropTarget = false; + OptionalCollectionUUID dropTargetUUID; bool stayTemp = false; }; Status renameCollection(OperationContext* opCtx, diff --git a/src/mongo/db/catalog/rename_collection_test.cpp b/src/mongo/db/catalog/rename_collection_test.cpp index 60386382669..b3a919fc8a2 100644 --- a/src/mongo/db/catalog/rename_collection_test.cpp +++ b/src/mongo/db/catalog/rename_collection_test.cpp @@ -290,6 +290,15 @@ CollectionOptions _makeCollectionOptionsWithUuid() { } /** + * Creates a collection with UUID and returns the UUID. + */ +CollectionUUID _createCollectionWithUUID(OperationContext* opCtx, const NamespaceString& nss) { + const auto options = _makeCollectionOptionsWithUuid(); + _createCollection(opCtx, nss, options); + return options.uuid.get(); +} + +/** * Returns true if collection exists. */ bool _collectionExists(OperationContext* opCtx, const NamespaceString& nss) { @@ -318,6 +327,14 @@ CollectionUUID _getCollectionUuid(OperationContext* opCtx, const NamespaceString } /** + * Get collection namespace by UUID. + */ +NamespaceString _getCollectionNssFromUUID(OperationContext* opCtx, const UUID& uuid) { + Collection* source = UUIDCatalog::get(opCtx).lookupCollectionByUUID(uuid); + return source ? source->ns() : NamespaceString(); +} + +/** * Returns true if namespace refers to a temporary collection. */ bool _isTempCollection(OperationContext* opCtx, const NamespaceString& nss) { @@ -499,12 +516,9 @@ TEST_F(RenameCollectionTest, RenameCollectionForApplyOpsAcrossDatabaseWithTarget } TEST_F(RenameCollectionTest, RenameCollectionToItselfByNsForApplyOps) { - _createCollection(_opCtx.get(), _sourceNss); auto dbName = _sourceNss.db().toString(); - AutoGetDb autoDb(_opCtx.get(), dbName, MODE_X); - auto db = autoDb.getDb(); - auto uuid = db->getCollection(_opCtx.get(), _sourceNss)->uuid(); - auto uuidDoc = BSON("ui" << uuid.get()); + auto uuid = _createCollectionWithUUID(_opCtx.get(), _sourceNss); + auto uuidDoc = BSON("ui" << uuid); auto cmd = BSON("renameCollection" << _sourceNss.ns() << "to" << _sourceNss.ns() << "dropTarget" << true); ASSERT_OK(renameCollectionForApplyOps(_opCtx.get(), dbName, uuidDoc["ui"], cmd, {})); @@ -512,12 +526,9 @@ TEST_F(RenameCollectionTest, RenameCollectionToItselfByNsForApplyOps) { } TEST_F(RenameCollectionTest, RenameCollectionToItselfByUUIDForApplyOps) { - _createCollection(_opCtx.get(), _targetNss); auto dbName = _targetNss.db().toString(); - AutoGetDb autoDb(_opCtx.get(), dbName, MODE_X); - auto db = autoDb.getDb(); - auto uuid = db->getCollection(_opCtx.get(), _targetNss)->uuid(); - auto uuidDoc = BSON("ui" << uuid.get()); + auto uuid = _createCollectionWithUUID(_opCtx.get(), _targetNss); + auto uuidDoc = BSON("ui" << uuid); auto cmd = BSON("renameCollection" << _sourceNss.ns() << "to" << _targetNss.ns() << "dropTarget" << true); ASSERT_OK(renameCollectionForApplyOps(_opCtx.get(), dbName, uuidDoc["ui"], cmd, {})); @@ -526,18 +537,119 @@ TEST_F(RenameCollectionTest, RenameCollectionToItselfByUUIDForApplyOps) { TEST_F(RenameCollectionTest, RenameCollectionByUUIDRatherThanNsForApplyOps) { auto realRenameFromNss = NamespaceString("test.bar2"); - _createCollection(_opCtx.get(), realRenameFromNss); auto dbName = realRenameFromNss.db().toString(); - AutoGetDb autoDb(_opCtx.get(), dbName, MODE_X); - auto db = autoDb.getDb(); - auto uuid = db->getCollection(_opCtx.get(), realRenameFromNss)->uuid(); - auto uuidDoc = BSON("ui" << uuid.get()); + auto uuid = _createCollectionWithUUID(_opCtx.get(), realRenameFromNss); + auto uuidDoc = BSON("ui" << uuid); auto cmd = BSON("renameCollection" << _sourceNss.ns() << "to" << _targetNss.ns() << "dropTarget" << true); ASSERT_OK(renameCollectionForApplyOps(_opCtx.get(), dbName, uuidDoc["ui"], cmd, {})); ASSERT_TRUE(_collectionExists(_opCtx.get(), _targetNss)); } +TEST_F(RenameCollectionTest, RenameCollectionForApplyOpsDropTargetByUUIDTargetDoesNotExist) { + const auto& collA = NamespaceString("test.A"); + const auto& collB = NamespaceString("test.B"); + const auto& collC = NamespaceString("test.C"); + auto dbName = collA.db().toString(); + auto collAUUID = _createCollectionWithUUID(_opCtx.get(), collA); + auto collCUUID = _createCollectionWithUUID(_opCtx.get(), collC); + auto uuidDoc = BSON("ui" << collAUUID); + // Rename A to B, drop C, where B is not an existing collection + auto cmd = + BSON("renameCollection" << collA.ns() << "to" << collB.ns() << "dropTarget" << collCUUID); + ASSERT_OK(renameCollectionForApplyOps(_opCtx.get(), dbName, uuidDoc["ui"], cmd, {})); + // A and C should be dropped + ASSERT_FALSE(_collectionExists(_opCtx.get(), collA)); + ASSERT_FALSE(_collectionExists(_opCtx.get(), collC)); + // B (originally A) should exist + ASSERT_TRUE(_collectionExists(_opCtx.get(), collB)); +} + +TEST_F(RenameCollectionTest, RenameCollectionForApplyOpsDropTargetByUUIDTargetExists) { + const auto& collA = NamespaceString("test.A"); + const auto& collB = NamespaceString("test.B"); + const auto& collC = NamespaceString("test.C"); + auto dbName = collA.db().toString(); + auto collAUUID = _createCollectionWithUUID(_opCtx.get(), collA); + auto collBUUID = _createCollectionWithUUID(_opCtx.get(), collB); + auto collCUUID = _createCollectionWithUUID(_opCtx.get(), collC); + auto uuidDoc = BSON("ui" << collAUUID); + // Rename A to B, drop C, where B is an existing collection + // B should be kept but with a temporary name + auto cmd = + BSON("renameCollection" << collA.ns() << "to" << collB.ns() << "dropTarget" << collCUUID); + ASSERT_OK(renameCollectionForApplyOps(_opCtx.get(), dbName, uuidDoc["ui"], cmd, {})); + // A and C should be dropped + ASSERT_FALSE(_collectionExists(_opCtx.get(), collA)); + ASSERT_FALSE(_collectionExists(_opCtx.get(), collC)); + // B (originally A) should exist + ASSERT_TRUE(_collectionExists(_opCtx.get(), collB)); + // The original B should exist too, but with a temporary name + const auto& tmpB = UUIDCatalog::get(_opCtx.get()).lookupNSSByUUID(collBUUID); + ASSERT_FALSE(tmpB.isEmpty()); + ASSERT_TRUE(tmpB.coll().startsWith("tmp")); + ASSERT_TRUE(tmpB != collB); +} + +TEST_F(RenameCollectionTest, + RenameCollectionForApplyOpsDropTargetByUUIDTargetExistsButTemporarily) { + + const auto& collA = NamespaceString("test.A"); + const auto& collB = NamespaceString("test.B"); + const auto& collC = NamespaceString("test.C"); + + CollectionOptions collectionOptions = _makeCollectionOptionsWithUuid(); + collectionOptions.temp = true; + _createCollection(_opCtx.get(), collB, collectionOptions); + auto collBUUID = _getCollectionUuid(_opCtx.get(), collB); + + auto dbName = collA.db().toString(); + auto collAUUID = _createCollectionWithUUID(_opCtx.get(), collA); + auto collCUUID = _createCollectionWithUUID(_opCtx.get(), collC); + auto uuidDoc = BSON("ui" << collAUUID); + // Rename A to B, drop C, where B is an existing collection + // B should be kept but with a temporary name + auto cmd = + BSON("renameCollection" << collA.ns() << "to" << collB.ns() << "dropTarget" << collCUUID); + ASSERT_OK(renameCollectionForApplyOps(_opCtx.get(), dbName, uuidDoc["ui"], cmd, {})); + // A and C should be dropped + ASSERT_FALSE(_collectionExists(_opCtx.get(), collA)); + ASSERT_FALSE(_collectionExists(_opCtx.get(), collC)); + // B (originally A) should exist + ASSERT_TRUE(_collectionExists(_opCtx.get(), collB)); + // The original B should exist too, but with a temporary name + const auto& tmpB = UUIDCatalog::get(_opCtx.get()).lookupNSSByUUID(collBUUID); + ASSERT_FALSE(tmpB.isEmpty()); + ASSERT_TRUE(tmpB != collB); + ASSERT_TRUE(tmpB.coll().startsWith("tmp")); + ASSERT_TRUE(_isTempCollection(_opCtx.get(), tmpB)); +} + +TEST_F(RenameCollectionTest, + RenameCollectionForApplyOpsDropTargetByUUIDTargetExistsButRealDropTargetDoesNotExist) { + const auto& collA = NamespaceString("test.A"); + const auto& collB = NamespaceString("test.B"); + auto dbName = collA.db().toString(); + auto collAUUID = _createCollectionWithUUID(_opCtx.get(), collA); + auto collBUUID = _createCollectionWithUUID(_opCtx.get(), collB); + auto collCUUID = UUID::gen(); + auto uuidDoc = BSON("ui" << collAUUID); + // Rename A to B, drop C, where B is an existing collection + // B should be kept but with a temporary name + auto cmd = + BSON("renameCollection" << collA.ns() << "to" << collB.ns() << "dropTarget" << collCUUID); + ASSERT_OK(renameCollectionForApplyOps(_opCtx.get(), dbName, uuidDoc["ui"], cmd, {})); + // A and C should be dropped + ASSERT_FALSE(_collectionExists(_opCtx.get(), collA)); + // B (originally A) should exist + ASSERT_TRUE(_collectionExists(_opCtx.get(), collB)); + // The original B should exist too, but with a temporary name + const auto& tmpB = UUIDCatalog::get(_opCtx.get()).lookupNSSByUUID(collBUUID); + ASSERT_FALSE(tmpB.isEmpty()); + ASSERT_TRUE(tmpB != collB); + ASSERT_TRUE(tmpB.coll().startsWith("tmp")); +} + TEST_F(RenameCollectionTest, RenameCollectionReturnsNamespaceExitsIfTargetExistsAndDropTargetIsFalse) { _createCollection(_opCtx.get(), _sourceNss); @@ -622,6 +734,92 @@ DEATH_TEST_F(RenameCollectionTest, ASSERT_OK(renameCollectionForApplyOps(_opCtx.get(), dbName, {}, cmd, renameOpTime)); } +TEST_F(RenameCollectionTest, RenameCollectionForApplyOpsSourceAndTargetDoNotExist) { + auto uuidDoc = BSON("ui" << UUID::gen()); + auto cmd = BSON("renameCollection" << _sourceNss.ns() << "to" << _targetNss.ns() << "dropTarget" + << "true"); + ASSERT_EQUALS(ErrorCodes::NamespaceNotFound, + renameCollectionForApplyOps( + _opCtx.get(), _sourceNss.db().toString(), uuidDoc["ui"], cmd, {})); + ASSERT_FALSE(_collectionExists(_opCtx.get(), _sourceNss)); + ASSERT_FALSE(_collectionExists(_opCtx.get(), _targetNss)); +} + +TEST_F(RenameCollectionTest, RenameCollectionForApplyOpsDropTargetEvenIfSourceDoesNotExist) { + _createCollectionWithUUID(_opCtx.get(), _targetNss); + auto missingSourceNss = NamespaceString("test.bar2"); + auto uuidDoc = BSON("ui" << UUID::gen()); + auto cmd = + BSON("renameCollection" << missingSourceNss.ns() << "to" << _targetNss.ns() << "dropTarget" + << "true"); + ASSERT_OK(renameCollectionForApplyOps( + _opCtx.get(), missingSourceNss.db().toString(), uuidDoc["ui"], cmd, {})); + ASSERT_FALSE(_collectionExists(_opCtx.get(), _targetNss)); +} + +TEST_F(RenameCollectionTest, RenameCollectionForApplyOpsDropTargetByUUIDEvenIfSourceDoesNotExist) { + auto missingSourceNss = NamespaceString("test.bar2"); + auto dropTargetNss = NamespaceString("test.bar3"); + _createCollectionWithUUID(_opCtx.get(), _targetNss); + auto dropTargetUUID = _createCollectionWithUUID(_opCtx.get(), dropTargetNss); + auto uuidDoc = BSON("ui" << UUID::gen()); + auto cmd = + BSON("renameCollection" << missingSourceNss.ns() << "to" << _targetNss.ns() << "dropTarget" + << dropTargetUUID); + ASSERT_OK(renameCollectionForApplyOps( + _opCtx.get(), missingSourceNss.db().toString(), uuidDoc["ui"], cmd, {})); + ASSERT_TRUE(_collectionExists(_opCtx.get(), _targetNss)); + ASSERT_FALSE(_collectionExists(_opCtx.get(), dropTargetNss)); +} + +TEST_F(RenameCollectionTest, RenameCollectionForApplyOpsDropTargetEvenIfSourceIsDropPending) { + repl::OpTime dropOpTime(Timestamp(Seconds(100), 0), 1LL); + auto dropPendingNss = _sourceNss.makeDropPendingNamespace(dropOpTime); + + auto dropTargetUUID = _createCollectionWithUUID(_opCtx.get(), _targetNss); + auto uuidDoc = BSON("ui" << _createCollectionWithUUID(_opCtx.get(), dropPendingNss)); + auto cmd = + BSON("renameCollection" << dropPendingNss.ns() << "to" << _targetNss.ns() << "dropTarget" + << "true"); + + repl::UnreplicatedWritesBlock uwb(_opCtx.get()); + repl::OpTime renameOpTime = {Timestamp(Seconds(200), 1U), 1LL}; + ASSERT_OK(renameCollectionForApplyOps( + _opCtx.get(), dropPendingNss.db().toString(), uuidDoc["ui"], cmd, renameOpTime)); + + // Source collections stays in drop-pending state. + ASSERT_TRUE(_collectionExists(_opCtx.get(), dropPendingNss)); + ASSERT_FALSE(_collectionExists(_opCtx.get(), _targetNss)); + ASSERT_EQUALS(_targetNss.makeDropPendingNamespace(renameOpTime), + _getCollectionNssFromUUID(_opCtx.get(), dropTargetUUID)); +} + +TEST_F(RenameCollectionTest, RenameCollectionForApplyOpsDropTargetByUUIDEvenIfSourceIsDropPending) { + repl::OpTime dropOpTime(Timestamp(Seconds(100), 0), 1LL); + auto dropPendingNss = _sourceNss.makeDropPendingNamespace(dropOpTime); + auto dropTargetNss = NamespaceString("test.bar2"); + + _createCollectionWithUUID(_opCtx.get(), _targetNss); + + auto dropTargetUUID = _createCollectionWithUUID(_opCtx.get(), dropTargetNss); + auto uuidDoc = BSON("ui" << _createCollectionWithUUID(_opCtx.get(), dropPendingNss)); + auto cmd = + BSON("renameCollection" << dropPendingNss.ns() << "to" << _targetNss.ns() << "dropTarget" + << dropTargetUUID); + + repl::UnreplicatedWritesBlock uwb(_opCtx.get()); + repl::OpTime renameOpTime = {Timestamp(Seconds(200), 1U), 1LL}; + ASSERT_OK(renameCollectionForApplyOps( + _opCtx.get(), dropPendingNss.db().toString(), uuidDoc["ui"], cmd, renameOpTime)); + + // Source collections stays in drop-pending state. + ASSERT_TRUE(_collectionExists(_opCtx.get(), dropPendingNss)); + ASSERT_FALSE(_collectionExists(_opCtx.get(), dropTargetNss)); + ASSERT_EQUALS(dropTargetNss.makeDropPendingNamespace(renameOpTime), + _getCollectionNssFromUUID(_opCtx.get(), dropTargetUUID)); + ASSERT_TRUE(_collectionExists(_opCtx.get(), _targetNss)); +} + void _testRenameCollectionStayTemp(OperationContext* opCtx, const NamespaceString& sourceNss, const NamespaceString& targetNss, |