summaryrefslogtreecommitdiff
path: root/src/mongo/db
diff options
context:
space:
mode:
authorYuhong Zhang <danielzhangyh@gmail.com>2022-01-26 21:26:16 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2022-01-28 16:50:38 +0000
commitf02247dfac78ed35cbd4bc8b0a510209463c6080 (patch)
tree2aff8e3c3eddbd7cd4ec524847776b11e4f596ae /src/mongo/db
parentdb799be5aebf432380cb5f7acb0f204fbc120a13 (diff)
downloadmongo-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.cpp33
-rw-r--r--src/mongo/db/catalog/coll_mod_index.cpp87
-rw-r--r--src/mongo/db/catalog/coll_mod_index.h1
-rw-r--r--src/mongo/db/catalog/index_catalog_impl.cpp2
-rw-r--r--src/mongo/db/coll_mod.idl12
-rw-r--r--src/mongo/db/coll_mod_reply_validation.cpp12
-rw-r--r--src/mongo/db/coll_mod_reply_validation.h2
-rw-r--r--src/mongo/db/op_observer.h2
-rw-r--r--src/mongo/db/op_observer_impl.cpp4
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;