diff options
author | Yuhong Zhang <danielzhangyh@gmail.com> | 2022-01-26 21:26:16 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2022-01-28 16:50:38 +0000 |
commit | f02247dfac78ed35cbd4bc8b0a510209463c6080 (patch) | |
tree | 2aff8e3c3eddbd7cd4ec524847776b11e4f596ae /src/mongo/db | |
parent | db799be5aebf432380cb5f7acb0f204fbc120a13 (diff) | |
download | mongo-f02247dfac78ed35cbd4bc8b0a510209463c6080.tar.gz |
SERVER-62886 Add the option `disallowNewDuplicateKeys` to collMod command
Diffstat (limited to 'src/mongo/db')
-rw-r--r-- | src/mongo/db/catalog/coll_mod.cpp | 33 | ||||
-rw-r--r-- | src/mongo/db/catalog/coll_mod_index.cpp | 87 | ||||
-rw-r--r-- | src/mongo/db/catalog/coll_mod_index.h | 1 | ||||
-rw-r--r-- | src/mongo/db/catalog/index_catalog_impl.cpp | 2 | ||||
-rw-r--r-- | src/mongo/db/coll_mod.idl | 12 | ||||
-rw-r--r-- | src/mongo/db/coll_mod_reply_validation.cpp | 12 | ||||
-rw-r--r-- | src/mongo/db/coll_mod_reply_validation.h | 2 | ||||
-rw-r--r-- | src/mongo/db/op_observer.h | 2 | ||||
-rw-r--r-- | src/mongo/db/op_observer_impl.cpp | 4 |
9 files changed, 125 insertions, 30 deletions
diff --git a/src/mongo/db/catalog/coll_mod.cpp b/src/mongo/db/catalog/coll_mod.cpp index 2b4bc8ee5f2..ac2b11d73b1 100644 --- a/src/mongo/db/catalog/coll_mod.cpp +++ b/src/mongo/db/catalog/coll_mod.cpp @@ -164,15 +164,16 @@ StatusWith<ParsedCollModRequest> parseCollModRequest(OperationContext* opCtx, } if (!cmdIndex.getExpireAfterSeconds() && !cmdIndex.getHidden() && - !cmdIndex.getUnique()) { - return Status(ErrorCodes::InvalidOptions, - "no expireAfterSeconds, hidden, or unique field"); + !cmdIndex.getUnique() && !cmdIndex.getDisallowNewDuplicateKeys()) { + return Status( + ErrorCodes::InvalidOptions, + "no expireAfterSeconds, hidden, unique, or disallowNewDuplicateKeys field"); } auto cmrIndex = &cmr.indexRequest; auto indexObj = e.Obj(); - if (cmdIndex.getUnique()) { + if (cmdIndex.getUnique() || cmdIndex.getDisallowNewDuplicateKeys()) { uassert(ErrorCodes::InvalidOptions, "collMod does not support converting an index to unique", feature_flags::gCollModIndexUnique.isEnabled( @@ -309,6 +310,15 @@ StatusWith<ParsedCollModRequest> parseCollModRequest(OperationContext* opCtx, } } + // The 'disallowNewDuplicateKeys' option is an ephemeral setting. It is replicated but + // still susceptible to process restarts. We do not compare the requested change with + // the existing state, so there is no need for the no-op conversion logic that we have + // for 'hidden' or 'unique'. + if (cmdIndex.getDisallowNewDuplicateKeys()) { + cmr.numModifications++; + cmrIndex->indexDisallowNewDuplicateKeys = cmdIndex.getDisallowNewDuplicateKeys(); + } + // 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. @@ -450,6 +460,12 @@ StatusWith<ParsedCollModRequest> parseCollModRequest(OperationContext* opCtx, oplogEntryBuilder->append(e); } + // Currently disallows the use of 'indexDisallowNewDuplicateKeys' with other collMod options. + if (cmr.indexRequest.indexDisallowNewDuplicateKeys && cmr.numModifications > 1) { + return {ErrorCodes::InvalidOptions, + "disallowNewDuplicateKeys cannot be combined with any other modification."}; + } + return {std::move(cmr)}; } @@ -572,10 +588,6 @@ StatusWith<std::unique_ptr<CollModWriteOpsTracker::Token>> _setUpCollModIndexUni const auto& cmr = statusW.getValue(); auto idx = cmr.indexRequest.idx; auto violatingRecordsList = scanIndexForDuplicates(opCtx, collection, idx); - if (!violatingRecordsList.empty()) { - uassertStatusOK(buildConvertUniqueErrorStatus( - buildDuplicateViolations(opCtx, collection, violatingRecordsList))); - } CurOpFailpointHelpers::waitWhileFailPointEnabled(&hangAfterCollModIndexUniqueSideWriteTracker, opCtx, @@ -583,6 +595,11 @@ StatusWith<std::unique_ptr<CollModWriteOpsTracker::Token>> _setUpCollModIndexUni []() {}, nss); + if (!violatingRecordsList.empty()) { + uassertStatusOK(buildConvertUniqueErrorStatus( + buildDuplicateViolations(opCtx, collection, violatingRecordsList))); + } + return std::move(writeOpsToken); } diff --git a/src/mongo/db/catalog/coll_mod_index.cpp b/src/mongo/db/catalog/coll_mod_index.cpp index d9d94e0933f..480afa91916 100644 --- a/src/mongo/db/catalog/coll_mod_index.cpp +++ b/src/mongo/db/catalog/coll_mod_index.cpp @@ -183,6 +183,25 @@ void _processCollModIndexRequestUnique(OperationContext* opCtx, *newUnique = true; autoColl->getWritableCollection(opCtx)->updateUniqueSetting(opCtx, idx->indexName()); + idx->getEntry()->accessMethod()->setEnforceDuplicateConstraints(false); +} + +/** + * Adjusts enforceDuplicateConstraints setting on an index. + */ +void _processCollModIndexRequestDisallowNewDuplicateKeys( + OperationContext* opCtx, + AutoGetCollection* autoColl, + const IndexDescriptor* idx, + bool indexDisallowNewDuplicateKeys, + boost::optional<bool>* newDisallowNewDuplicateKeys, + boost::optional<bool>* oldDisallowNewDuplicateKeys) { + *newDisallowNewDuplicateKeys = indexDisallowNewDuplicateKeys; + auto accessMethod = idx->getEntry()->accessMethod(); + *oldDisallowNewDuplicateKeys = accessMethod->isEnforcingDuplicateConstraints(); + if (*oldDisallowNewDuplicateKeys != *newDisallowNewDuplicateKeys) { + accessMethod->setEnforceDuplicateConstraints(indexDisallowNewDuplicateKeys); + } } } // namespace @@ -198,9 +217,11 @@ void processCollModIndexRequest(OperationContext* opCtx, auto indexExpireAfterSeconds = collModIndexRequest.indexExpireAfterSeconds; auto indexHidden = collModIndexRequest.indexHidden; auto indexUnique = collModIndexRequest.indexUnique; + auto indexDisallowNewDuplicateKeys = collModIndexRequest.indexDisallowNewDuplicateKeys; // Return early if there are no index modifications requested. - if (!indexExpireAfterSeconds && !indexHidden && !indexUnique) { + if (!indexExpireAfterSeconds && !indexHidden && !indexUnique && + !indexDisallowNewDuplicateKeys) { return; } @@ -209,6 +230,8 @@ void processCollModIndexRequest(OperationContext* opCtx, boost::optional<bool> newHidden; boost::optional<bool> oldHidden; boost::optional<bool> newUnique; + boost::optional<bool> newDisallowNewDuplicateKeys; + boost::optional<bool> oldDisallowNewDuplicateKeys; // TTL Index if (indexExpireAfterSeconds) { @@ -230,12 +253,23 @@ void processCollModIndexRequest(OperationContext* opCtx, opCtx, autoColl, idx, mode, docsForUniqueIndex, &newUnique); } + if (indexDisallowNewDuplicateKeys) { + _processCollModIndexRequestDisallowNewDuplicateKeys(opCtx, + autoColl, + idx, + *indexDisallowNewDuplicateKeys, + &newDisallowNewDuplicateKeys, + &oldDisallowNewDuplicateKeys); + } + *indexCollModInfo = IndexCollModInfo{!newExpireSecs ? boost::optional<Seconds>() : Seconds(*newExpireSecs), !oldExpireSecs ? boost::optional<Seconds>() : Seconds(*oldExpireSecs), newHidden, oldHidden, newUnique, + oldDisallowNewDuplicateKeys, + newDisallowNewDuplicateKeys, idx->indexName()}; // This matches the default for IndexCatalog::refreshEntry(). @@ -251,26 +285,37 @@ void processCollModIndexRequest(OperationContext* opCtx, autoColl->getWritableCollection(opCtx)->getIndexCatalog()->refreshEntry( opCtx, autoColl->getWritableCollection(opCtx), idx, flags); - opCtx->recoveryUnit()->onCommit( - [oldExpireSecs, newExpireSecs, oldHidden, newHidden, newUnique, result]( - boost::optional<Timestamp>) { - // add the fields to BSONObjBuilder result - if (oldExpireSecs) { - result->append("expireAfterSeconds_old", *oldExpireSecs); - } - if (newExpireSecs) { - result->append("expireAfterSeconds_new", *newExpireSecs); - } - if (newHidden) { - invariant(oldHidden); - result->append("hidden_old", *oldHidden); - result->append("hidden_new", *newHidden); - } - if (newUnique) { - invariant(*newUnique); - result->appendBool("unique_new", true); - } - }); + opCtx->recoveryUnit()->onCommit([oldExpireSecs, + newExpireSecs, + oldHidden, + newHidden, + newUnique, + oldDisallowNewDuplicateKeys, + newDisallowNewDuplicateKeys, + result](boost::optional<Timestamp>) { + // add the fields to BSONObjBuilder result + if (oldExpireSecs) { + result->append("expireAfterSeconds_old", *oldExpireSecs); + } + if (newExpireSecs) { + result->append("expireAfterSeconds_new", *newExpireSecs); + } + if (newHidden) { + invariant(oldHidden); + result->append("hidden_old", *oldHidden); + result->append("hidden_new", *newHidden); + } + if (newUnique) { + invariant(*newUnique); + result->appendBool("unique_new", true); + } + if (newDisallowNewDuplicateKeys) { + // Unlike other fields, 'disallowNewDuplicateKeys' can have the same old and new values. + invariant(oldDisallowNewDuplicateKeys); + result->append("disallowNewDuplicateKeys_old", *oldDisallowNewDuplicateKeys); + result->append("disallowNewDuplicateKeys_new", *newDisallowNewDuplicateKeys); + } + }); if (MONGO_unlikely(assertAfterIndexUpdate.shouldFail())) { LOGV2(20307, "collMod - assertAfterIndexUpdate fail point enabled"); diff --git a/src/mongo/db/catalog/coll_mod_index.h b/src/mongo/db/catalog/coll_mod_index.h index ba4e9ba65de..f7b43353ec2 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<long long> indexExpireAfterSeconds; boost::optional<bool> indexHidden; boost::optional<bool> indexUnique; + boost::optional<bool> indexDisallowNewDuplicateKeys; }; /** diff --git a/src/mongo/db/catalog/index_catalog_impl.cpp b/src/mongo/db/catalog/index_catalog_impl.cpp index fa180b98be7..e3463a5b033 100644 --- a/src/mongo/db/catalog/index_catalog_impl.cpp +++ b/src/mongo/db/catalog/index_catalog_impl.cpp @@ -1434,6 +1434,7 @@ const IndexDescriptor* IndexCatalogImpl::refreshEntry(OperationContext* opCtx, // CollectionIndexUsageTrackerDecoration (shared state among Collection instances). auto oldEntry = _readyIndexes.release(oldDesc); invariant(oldEntry); + auto enforceDuplicateConstraints = oldEntry->accessMethod()->isEnforcingDuplicateConstraints(); opCtx->recoveryUnit()->registerChange(std::make_unique<IndexRemoveChange>( std::move(oldEntry), collection->getSharedDecorations())); CollectionIndexUsageTrackerDecoration::get(collection->getSharedDecorations()) @@ -1448,6 +1449,7 @@ const IndexDescriptor* IndexCatalogImpl::refreshEntry(OperationContext* opCtx, auto newDesc = std::make_unique<IndexDescriptor>(_getAccessMethodName(keyPattern), spec); auto newEntry = createIndexEntry(opCtx, collection, std::move(newDesc), flags); invariant(newEntry->isReady(opCtx, collection)); + newEntry->accessMethod()->setEnforceDuplicateConstraints(enforceDuplicateConstraints); auto desc = newEntry->descriptor(); CollectionIndexUsageTrackerDecoration::get(collection->getSharedDecorations()) .registerIndex(desc->indexName(), desc->keyPattern()); diff --git a/src/mongo/db/coll_mod.idl b/src/mongo/db/coll_mod.idl index 68eee65ffdb..821aed598d6 100644 --- a/src/mongo/db/coll_mod.idl +++ b/src/mongo/db/coll_mod.idl @@ -63,6 +63,10 @@ structs: optional: true type: safeBool unstable: true + disallowNewDuplicateKeys: + optional: true + type: safeBool + unstable: true CollModReply: description: "The collMod command's reply." @@ -88,6 +92,14 @@ structs: optional: true type: safeBool unstable: true + disallowNewDuplicateKeys_old: + optional: true + type: safeBool + unstable: true + disallowNewDuplicateKeys_new: + optional: true + type: safeBool + unstable: true CollModRequest: description: "The collMod command's request." diff --git a/src/mongo/db/coll_mod_reply_validation.cpp b/src/mongo/db/coll_mod_reply_validation.cpp index 46ac5b0b64d..3ebe4a58675 100644 --- a/src/mongo/db/coll_mod_reply_validation.cpp +++ b/src/mongo/db/coll_mod_reply_validation.cpp @@ -40,5 +40,17 @@ void validateReply(const CollModReply& reply) { << "(hidden_new and hidden_old) or none of them.", false); } + + auto disallowNewDuplicateKeys_new = reply.getDisallowNewDuplicateKeys_new().is_initialized(); + auto disallowNewDuplicateKeys_old = reply.getDisallowNewDuplicateKeys_old().is_initialized(); + + if ((!disallowNewDuplicateKeys_new && disallowNewDuplicateKeys_old) || + (disallowNewDuplicateKeys_new && !disallowNewDuplicateKeys_old)) { + uassert(ErrorCodes::CommandResultSchemaViolation, + str::stream() << "Invalid CollModReply: Reply should define either both fields " + << "(disallowNewDuplicateKeys_new and disallowNewDuplicateKeys_old) " + "or none of them.", + false); + } } } // namespace mongo::coll_mod_reply_validation diff --git a/src/mongo/db/coll_mod_reply_validation.h b/src/mongo/db/coll_mod_reply_validation.h index cf0fca8359f..e4a9ac9e49a 100644 --- a/src/mongo/db/coll_mod_reply_validation.h +++ b/src/mongo/db/coll_mod_reply_validation.h @@ -36,8 +36,8 @@ namespace mongo::coll_mod_reply_validation { * CollMod reply object requires extra validation, as the current IDL validation capabilities * are not sufficient in this case. * It is used to check that reply includes: - * - (expireAfterSeconds_new and expireAfterSeconds_old) together or none of them. * - (hidden_new and hidden_old) together or none of them. + * - (disallowNewDuplicateKeys_new and disallowNewDuplicateKeys_old) together or none of them." */ void validateReply(const CollModReply& reply); } // namespace mongo::coll_mod_reply_validation diff --git a/src/mongo/db/op_observer.h b/src/mongo/db/op_observer.h index 9c2a30501d6..e42f9533939 100644 --- a/src/mongo/db/op_observer.h +++ b/src/mongo/db/op_observer.h @@ -99,6 +99,8 @@ struct IndexCollModInfo { boost::optional<bool> hidden; boost::optional<bool> oldHidden; boost::optional<bool> unique; + boost::optional<bool> disallowNewDuplicateKeys; + boost::optional<bool> oldDisallowNewDuplicateKeys; std::string indexName; }; diff --git a/src/mongo/db/op_observer_impl.cpp b/src/mongo/db/op_observer_impl.cpp index 40f7d2569bf..a47a6055603 100644 --- a/src/mongo/db/op_observer_impl.cpp +++ b/src/mongo/db/op_observer_impl.cpp @@ -992,6 +992,10 @@ void OpObserverImpl::onCollMod(OperationContext* opCtx, auto oldHidden = indexInfo->oldHidden.get(); o2Builder.append("hidden_old", oldHidden); } + if (indexInfo->oldDisallowNewDuplicateKeys) { + auto oldDisallowNewDuplicateKeys = indexInfo->oldDisallowNewDuplicateKeys.get(); + o2Builder.append("disallowNewDuplicates_old", oldDisallowNewDuplicateKeys); + } } MutableOplogEntry oplogEntry; |