From e2675683edd92cd4105df52eff3f0eb64055181c Mon Sep 17 00:00:00 2001 From: Yuhong Zhang Date: Fri, 4 Mar 2022 00:06:12 +0000 Subject: SERVER-63578 Convert a unique index to a non-unique index via the collMod command --- src/mongo/db/catalog/coll_mod.cpp | 27 ++++++++++++++-- src/mongo/db/catalog/coll_mod_index.cpp | 36 +++++++++++++++++++--- src/mongo/db/catalog/coll_mod_index.h | 1 + src/mongo/db/catalog/collection.h | 5 ++- src/mongo/db/catalog/collection_impl.cpp | 4 +-- src/mongo/db/catalog/collection_impl.h | 2 +- src/mongo/db/catalog/collection_mock.h | 2 +- src/mongo/db/coll_mod.idl | 8 +++++ src/mongo/db/op_observer.h | 1 + src/mongo/db/op_observer_util.cpp | 3 ++ .../db/storage/bson_collection_catalog_entry.cpp | 16 ++++++++-- .../db/storage/bson_collection_catalog_entry.h | 2 +- 12 files changed, 89 insertions(+), 18 deletions(-) (limited to 'src/mongo') diff --git a/src/mongo/db/catalog/coll_mod.cpp b/src/mongo/db/catalog/coll_mod.cpp index 0ffc15e0e60..747de3e044a 100644 --- a/src/mongo/db/catalog/coll_mod.cpp +++ b/src/mongo/db/catalog/coll_mod.cpp @@ -185,7 +185,8 @@ StatusWith parseCollModRequest(OperationContext* opCtx, } if (!cmdIndex.getExpireAfterSeconds() && !cmdIndex.getHidden() && - !cmdIndex.getUnique() && !cmdIndex.getPrepareUnique()) { + !cmdIndex.getUnique() && !cmdIndex.getPrepareUnique() && + !cmdIndex.getForceNonUnique()) { return Status(ErrorCodes::InvalidOptions, "no expireAfterSeconds, hidden, unique, or prepareUnique field"); } @@ -201,6 +202,13 @@ StatusWith parseCollModRequest(OperationContext* opCtx, serverGlobalParams.featureCompatibility)); } + if (cmdIndex.getUnique() && cmdIndex.getForceNonUnique()) { + return Status( + ErrorCodes::InvalidOptions, + "collMod does not support 'unique' and 'forceNonUnique' options at the " + "same time"); + } + if (cmdIndex.getExpireAfterSeconds()) { if (isTimeseries) { return Status(ErrorCodes::InvalidOptions, @@ -305,7 +313,7 @@ StatusWith parseCollModRequest(OperationContext* opCtx, cmr.numModifications++; if (bool unique = *cmdIndex.getUnique(); !unique) { - return Status(ErrorCodes::BadValue, "Cannot make index non-unique"); + return Status(ErrorCodes::BadValue, "'Unique: false' option is not supported"); } // Attempting to converting a unique index should be treated as a no-op. @@ -350,6 +358,21 @@ StatusWith parseCollModRequest(OperationContext* opCtx, } } + if (cmdIndex.getForceNonUnique()) { + cmr.numModifications++; + if (bool unique = *cmdIndex.getForceNonUnique(); !unique) { + return Status(ErrorCodes::BadValue, "'forceNonUnique: false' is not supported"); + } + + // Attempting to convert a non-unique index should be treated as a no-op. + if (!cmrIndex->idx->unique()) { + indexObjForOplog = + indexObjForOplog.removeField(CollModIndex::kForceNonUniqueFieldName); + } else { + cmrIndex->indexForceNonUnique = true; + } + } + // The index options doc must contain either the name or key pattern, but not both. // If we have just one field, the index modifications requested matches the current // state in catalog and there is nothing further to do. diff --git a/src/mongo/db/catalog/coll_mod_index.cpp b/src/mongo/db/catalog/coll_mod_index.cpp index 7be401d110b..4a6a26237ea 100644 --- a/src/mongo/db/catalog/coll_mod_index.cpp +++ b/src/mongo/db/catalog/coll_mod_index.cpp @@ -128,8 +128,7 @@ void getKeysForIndex(OperationContext* opCtx, } /** - * Adjusts unique setting on an index. - * An index can be converted to unique but removing the uniqueness property is not allowed. + * Adjusts unique setting on an index to true. */ void _processCollModIndexRequestUnique(OperationContext* opCtx, AutoGetCollection* autoColl, @@ -150,7 +149,7 @@ void _processCollModIndexRequestUnique(OperationContext* opCtx, } *newUnique = true; - autoColl->getWritableCollection(opCtx)->updateUniqueSetting(opCtx, idx->indexName()); + autoColl->getWritableCollection(opCtx)->updateUniqueSetting(opCtx, idx->indexName(), true); // Resets 'prepareUnique' to false after converting to unique index; autoColl->getWritableCollection(opCtx)->updatePrepareUniqueSetting( opCtx, idx->indexName(), false); @@ -173,6 +172,19 @@ void _processCollModIndexRequestPrepareUnique(OperationContext* opCtx, } } +/** + * Adjusts unique setting on an index to false. + */ +void _processCollModIndexRequestForceNonUnique(OperationContext* opCtx, + AutoGetCollection* autoColl, + const IndexDescriptor* idx, + boost::optional* newForceNonUnique) { + invariant(idx->unique(), str::stream() << "Index is already non-unique: " << idx->infoObj()); + + *newForceNonUnique = true; + autoColl->getWritableCollection(opCtx)->updateUniqueSetting(opCtx, idx->indexName(), false); +} + } // namespace void processCollModIndexRequest(OperationContext* opCtx, @@ -186,9 +198,11 @@ void processCollModIndexRequest(OperationContext* opCtx, auto indexHidden = collModIndexRequest.indexHidden; auto indexUnique = collModIndexRequest.indexUnique; auto indexPrepareUnique = collModIndexRequest.indexPrepareUnique; + auto indexForceNonUnique = collModIndexRequest.indexForceNonUnique; // Return early if there are no index modifications requested. - if (!indexExpireAfterSeconds && !indexHidden && !indexUnique && !indexPrepareUnique) { + if (!indexExpireAfterSeconds && !indexHidden && !indexUnique && !indexPrepareUnique && + !indexForceNonUnique) { return; } @@ -199,6 +213,7 @@ void processCollModIndexRequest(OperationContext* opCtx, boost::optional newUnique; boost::optional newPrepareUnique; boost::optional oldPrepareUnique; + boost::optional newForceNonUnique; // TTL Index if (indexExpireAfterSeconds) { @@ -224,6 +239,11 @@ void processCollModIndexRequest(OperationContext* opCtx, opCtx, autoColl, idx, *indexPrepareUnique, &newPrepareUnique, &oldPrepareUnique); } + // User wants to convert an index back to be non-unique. + if (indexForceNonUnique) { + _processCollModIndexRequestForceNonUnique(opCtx, autoColl, idx, &newForceNonUnique); + } + *indexCollModInfo = IndexCollModInfo{!newExpireSecs ? boost::optional() : Seconds(*newExpireSecs), !oldExpireSecs ? boost::optional() : Seconds(*oldExpireSecs), @@ -232,13 +252,14 @@ void processCollModIndexRequest(OperationContext* opCtx, newUnique, newPrepareUnique, oldPrepareUnique, + newForceNonUnique, idx->indexName()}; // This matches the default for IndexCatalog::refreshEntry(). auto flags = CreateIndexEntryFlags::kIsReady; // Update data format version in storage engine metadata for index. - if (indexUnique) { + if (indexUnique || indexForceNonUnique) { flags = CreateIndexEntryFlags::kIsReady | CreateIndexEntryFlags::kUpdateMetadata; } @@ -254,6 +275,7 @@ void processCollModIndexRequest(OperationContext* opCtx, newUnique, oldPrepareUnique, newPrepareUnique, + newForceNonUnique, result](boost::optional) { // add the fields to BSONObjBuilder result if (oldExpireSecs) { @@ -276,6 +298,10 @@ void processCollModIndexRequest(OperationContext* opCtx, result->append("prepareUnique_old", *oldPrepareUnique); result->append("prepareUnique_new", *newPrepareUnique); } + if (newForceNonUnique) { + invariant(*newForceNonUnique); + result->appendBool("forceNonUnique_new", true); + } }); if (MONGO_unlikely(assertAfterIndexUpdate.shouldFail())) { diff --git a/src/mongo/db/catalog/coll_mod_index.h b/src/mongo/db/catalog/coll_mod_index.h index acd7849d70e..ed6f24f4dd5 100644 --- a/src/mongo/db/catalog/coll_mod_index.h +++ b/src/mongo/db/catalog/coll_mod_index.h @@ -49,6 +49,7 @@ struct ParsedCollModIndexRequest { boost::optional indexHidden; boost::optional indexUnique; boost::optional indexPrepareUnique; + boost::optional indexForceNonUnique; }; /** diff --git a/src/mongo/db/catalog/collection.h b/src/mongo/db/catalog/collection.h index aa5bc579c4e..d2e76c12993 100644 --- a/src/mongo/db/catalog/collection.h +++ b/src/mongo/db/catalog/collection.h @@ -604,10 +604,9 @@ public: virtual void updateHiddenSetting(OperationContext* opCtx, StringData idxName, bool hidden) = 0; /* - * Converts the the given index to be unique. - * This is a one-way transformation - the uniqueness constraint cannot be removed. + * Converts the the given index to be unique or non-unique. */ - virtual void updateUniqueSetting(OperationContext* opCtx, StringData idxName) = 0; + virtual void updateUniqueSetting(OperationContext* opCtx, StringData idxName, bool unique) = 0; /* * Disallows or allows new duplicates in the given index. diff --git a/src/mongo/db/catalog/collection_impl.cpp b/src/mongo/db/catalog/collection_impl.cpp index fa9a7d0dff4..92aec2083c5 100644 --- a/src/mongo/db/catalog/collection_impl.cpp +++ b/src/mongo/db/catalog/collection_impl.cpp @@ -1979,12 +1979,12 @@ void CollectionImpl::updateHiddenSetting(OperationContext* opCtx, StringData idx }); } -void CollectionImpl::updateUniqueSetting(OperationContext* opCtx, StringData idxName) { +void CollectionImpl::updateUniqueSetting(OperationContext* opCtx, StringData idxName, bool unique) { int offset = _metadata->findIndexOffset(idxName); invariant(offset >= 0); _writeMetadata(opCtx, [&](BSONCollectionCatalogEntry::MetaData& md) { - md.indexes[offset].updateUniqueSetting(); + md.indexes[offset].updateUniqueSetting(unique); }); } diff --git a/src/mongo/db/catalog/collection_impl.h b/src/mongo/db/catalog/collection_impl.h index 5c21e188dc1..f89cdf96ede 100644 --- a/src/mongo/db/catalog/collection_impl.h +++ b/src/mongo/db/catalog/collection_impl.h @@ -443,7 +443,7 @@ public: void updateHiddenSetting(OperationContext* opCtx, StringData idxName, bool hidden) final; - void updateUniqueSetting(OperationContext* opCtx, StringData idxName) final; + void updateUniqueSetting(OperationContext* opCtx, StringData idxName, bool unique) final; void updatePrepareUniqueSetting(OperationContext* opCtx, StringData idxName, diff --git a/src/mongo/db/catalog/collection_mock.h b/src/mongo/db/catalog/collection_mock.h index 65529dfd820..6f9f4736af4 100644 --- a/src/mongo/db/catalog/collection_mock.h +++ b/src/mongo/db/catalog/collection_mock.h @@ -411,7 +411,7 @@ public: std::abort(); } - void updateUniqueSetting(OperationContext* opCtx, StringData idxName) { + void updateUniqueSetting(OperationContext* opCtx, StringData idxName, bool unique) { std::abort(); } diff --git a/src/mongo/db/coll_mod.idl b/src/mongo/db/coll_mod.idl index a79677d9589..1b72151b12f 100644 --- a/src/mongo/db/coll_mod.idl +++ b/src/mongo/db/coll_mod.idl @@ -67,6 +67,10 @@ structs: optional: true type: safeBool unstable: true + forceNonUnique: + optional: true + type: safeBool + unstable: true CollModReply: description: "The collMod command's reply." @@ -100,6 +104,10 @@ structs: optional: true type: safeBool unstable: true + forceNonUnique_new: + optional: true + type: safeBool + unstable: true CollModRequest: description: "The collMod command's request." diff --git a/src/mongo/db/op_observer.h b/src/mongo/db/op_observer.h index 030521b9f4d..f1e8031c440 100644 --- a/src/mongo/db/op_observer.h +++ b/src/mongo/db/op_observer.h @@ -101,6 +101,7 @@ struct IndexCollModInfo { boost::optional unique; boost::optional prepareUnique; boost::optional oldPrepareUnique; + boost::optional forceNonUnique; std::string indexName; }; diff --git a/src/mongo/db/op_observer_util.cpp b/src/mongo/db/op_observer_util.cpp index cd79324a2aa..60a5902cd82 100644 --- a/src/mongo/db/op_observer_util.cpp +++ b/src/mongo/db/op_observer_util.cpp @@ -67,6 +67,9 @@ BSONObj makeCollModCmdObj(const BSONObj& collModCmd, if (indexInfo->prepareUnique) indexObjBuilder.append("prepareUnique", indexInfo->prepareUnique.get()); + if (indexInfo->forceNonUnique) + indexObjBuilder.append("forceNonUnique", indexInfo->forceNonUnique.get()); + cmdObjBuilder.append(indexFieldName, indexObjBuilder.obj()); } else { cmdObjBuilder.append(elem); diff --git a/src/mongo/db/storage/bson_collection_catalog_entry.cpp b/src/mongo/db/storage/bson_collection_catalog_entry.cpp index 58cdc01fe39..2e45db1a989 100644 --- a/src/mongo/db/storage/bson_collection_catalog_entry.cpp +++ b/src/mongo/db/storage/bson_collection_catalog_entry.cpp @@ -140,9 +140,19 @@ void BSONCollectionCatalogEntry::IndexMetaData::updateHiddenSetting(bool hidden) } -void BSONCollectionCatalogEntry::IndexMetaData::updateUniqueSetting() { - BSONObjBuilder b(spec); - b.appendBool("unique", true); +void BSONCollectionCatalogEntry::IndexMetaData::updateUniqueSetting(bool unique) { + // If unique == false, we remove this field from catalog rather than add a field with false. + BSONObjBuilder b; + for (BSONObjIterator bi(spec); bi.more();) { + BSONElement e = bi.next(); + if (e.fieldNameStringData() != "unique") { + b.append(e); + } + } + + if (unique) { + b.append("unique", unique); + } spec = b.obj(); } diff --git a/src/mongo/db/storage/bson_collection_catalog_entry.h b/src/mongo/db/storage/bson_collection_catalog_entry.h index 18bca894a71..b2afd733205 100644 --- a/src/mongo/db/storage/bson_collection_catalog_entry.h +++ b/src/mongo/db/storage/bson_collection_catalog_entry.h @@ -98,7 +98,7 @@ public: void updateHiddenSetting(bool hidden); - void updateUniqueSetting(); + void updateUniqueSetting(bool unique); void updatePrepareUniqueSetting(bool prepareUnique); -- cgit v1.2.1