summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--buildscripts/resmokeconfig/suites/clustered_collection_passthrough.yml2
-rw-r--r--jstests/core/collmod_convert_to_unique_violations_size_limit.js119
-rw-r--r--src/mongo/db/catalog/coll_mod.cpp6
-rw-r--r--src/mongo/db/catalog/coll_mod_index.cpp36
-rw-r--r--src/mongo/db/catalog/coll_mod_index.h14
5 files changed, 153 insertions, 24 deletions
diff --git a/buildscripts/resmokeconfig/suites/clustered_collection_passthrough.yml b/buildscripts/resmokeconfig/suites/clustered_collection_passthrough.yml
index 8cf08476490..77d160b3fb4 100644
--- a/buildscripts/resmokeconfig/suites/clustered_collection_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/clustered_collection_passthrough.yml
@@ -63,6 +63,8 @@ selector:
# TODO (SERVER-61259): $text not supported: "No query solutions"
- jstests/core/fts6.js
- jstests/core/fts_projection.js
+ # TODO (SERVER-64081): index keystring comparison not compatible
+ - jstests/core/collmod_convert_to_unique_violations_size_limit.js
exclude_with_any_tags:
- assumes_standalone_mongod
diff --git a/jstests/core/collmod_convert_to_unique_violations_size_limit.js b/jstests/core/collmod_convert_to_unique_violations_size_limit.js
new file mode 100644
index 00000000000..06b71afa354
--- /dev/null
+++ b/jstests/core/collmod_convert_to_unique_violations_size_limit.js
@@ -0,0 +1,119 @@
+/**
+ * Tests that the CannotConvertIndexToUnique error returned contains correct information about
+ * violations found when collMod fails to convert an index to unique and the size of the violations
+ * is large.
+ *
+ * @tags: [
+ * # Cannot implicitly shard accessed collections because of collection existing when none
+ * # expected.
+ * assumes_no_implicit_collection_creation_after_drop, # common tag in collMod tests.
+ * requires_fcv_60,
+ * requires_non_retryable_commands, # common tag in collMod tests.
+ * # TODO(SERVER-61181): Fix validation errors under ephemeralForTest.
+ * incompatible_with_eft,
+ * # TODO(SERVER-61182): Fix WiredTigerKVEngine::alterIdentMetadata() under inMemory.
+ * requires_persistence,
+ * assumes_unsharded_collection,
+ * tenant_migration_incompatible,
+ * ]
+ */
+(function() {
+'use strict';
+
+const collModIndexUniqueEnabled = assert
+ .commandWorked(db.getMongo().adminCommand(
+ {getParameter: 1, featureFlagCollModIndexUnique: 1}))
+ .featureFlagCollModIndexUnique.value;
+
+if (!collModIndexUniqueEnabled) {
+ jsTestLog('Skipping test because the collMod unique index feature flag is disabled.');
+ return;
+}
+
+function sortViolationsArray(arr) {
+ // Sorting unsorted arrays of unsorted arrays -- Sort subarrays, then sort main array by first
+ // key of subarray.
+ for (let i = 0; i < arr.length; i++) {
+ arr[i].ids = arr[i].ids.sort();
+ }
+ return arr.sort(function(a, b) {
+ if (a.ids[0] < b.ids[0]) {
+ return -1;
+ }
+ if (a.ids[0] > b.ids[0]) {
+ return 1;
+ }
+ return 0;
+ });
+}
+
+// Checks that the violations match what we expect.
+function assertFailedWithViolations(result, expectedViolations, sizeLimitViolation) {
+ assert.commandFailedWithCode(result, ErrorCodes.CannotConvertIndexToUnique);
+ if (sizeLimitViolation) {
+ assert.eq(
+ result.errmsg,
+ "Cannot convert the index to unique. Too many conflicting documents were detected. " +
+ "Please resolve them and rerun collMod.");
+ } else {
+ assert.eq(
+ result.errmsg,
+ "Cannot convert the index to unique. Please resolve conflicting documents before " +
+ "running collMod again.");
+ }
+ assert.eq(bsonWoCompare(sortViolationsArray(result.violations),
+ sortViolationsArray(expectedViolations)),
+ 0,
+ "result violations " + result.violations + " does not match expected violations " +
+ expectedViolations);
+}
+
+const collName = 'collmod_convert_to_unique_violations_size_limit';
+const coll = db.getCollection(collName);
+coll.drop();
+assert.commandWorked(db.createCollection(collName));
+
+// Creates regular indexes and try to use collMod to convert them to unique indexes.
+assert.commandWorked(coll.createIndex({a: 1}));
+
+// Inserts 8 duplicate documents with 1MB large _id to exceed the 8MB size limit.
+let ids = [];
+let id;
+const nDocs = 8;
+for (let i = 0; i < nDocs; ++i) {
+ id = "x".repeat(1024 * 1024) + i;
+ assert.commandWorked(coll.insert({_id: id, a: 1}));
+
+ // Only first 7 violations will be reported because 8th violation is over 8MB limit.
+ if (i < nDocs - 1) {
+ ids[i] = id;
+ }
+}
+
+// Sets 'prepareUnique' before converting the index to unique.
+assert.commandWorked(
+ db.runCommand({collMod: collName, index: {keyPattern: {a: 1}, prepareUnique: true}}));
+
+// Expects dryRun: true and unique: true conversion to fail with size exceeding violation.
+assertFailedWithViolations(
+ db.runCommand({collMod: collName, index: {keyPattern: {a: 1}, unique: true}, dryRun: true}),
+ [{ids}],
+ true);
+
+// Expects unique: true conversion to fail with size exceeding violation.
+assertFailedWithViolations(
+ db.runCommand({collMod: collName, index: {keyPattern: {a: 1}, unique: true}}), [{ids}], true);
+
+// Removes last violation.
+assert.commandWorked(coll.deleteOne({_id: id}));
+
+// Expects dryRun: true and unique: true conversion to fail without size exceeding violation.
+assertFailedWithViolations(
+ db.runCommand({collMod: collName, index: {keyPattern: {a: 1}, unique: true}, dryRun: true}),
+ [{ids}],
+ false);
+
+// Expects unique: true conversion to fail without size exceeding violation.
+assertFailedWithViolations(
+ db.runCommand({collMod: collName, index: {keyPattern: {a: 1}, unique: true}}), [{ids}], false);
+})();
diff --git a/src/mongo/db/catalog/coll_mod.cpp b/src/mongo/db/catalog/coll_mod.cpp
index 12a7715d33d..52bc185ff29 100644
--- a/src/mongo/db/catalog/coll_mod.cpp
+++ b/src/mongo/db/catalog/coll_mod.cpp
@@ -564,8 +564,7 @@ Status _processCollModDryRunMode(OperationContext* opCtx,
// Throws exception if index contains duplicates.
auto violatingRecordsList = scanIndexForDuplicates(opCtx, collection, cmr.indexRequest.idx);
if (!violatingRecordsList.empty()) {
- uassertStatusOK(buildConvertUniqueErrorStatus(
- buildDuplicateViolations(opCtx, collection, violatingRecordsList)));
+ uassertStatusOK(buildConvertUniqueErrorStatus(opCtx, collection, violatingRecordsList));
}
return Status::OK();
@@ -603,8 +602,7 @@ StatusWith<const IndexDescriptor*> _setUpCollModIndexUnique(OperationContext* op
nss);
if (!violatingRecordsList.empty()) {
- uassertStatusOK(buildConvertUniqueErrorStatus(
- buildDuplicateViolations(opCtx, collection, violatingRecordsList)));
+ uassertStatusOK(buildConvertUniqueErrorStatus(opCtx, collection, violatingRecordsList));
}
return idx;
diff --git a/src/mongo/db/catalog/coll_mod_index.cpp b/src/mongo/db/catalog/coll_mod_index.cpp
index 6bcb123e3aa..74d5e096979 100644
--- a/src/mongo/db/catalog/coll_mod_index.cpp
+++ b/src/mongo/db/catalog/coll_mod_index.cpp
@@ -145,8 +145,7 @@ void _processCollModIndexRequestUnique(OperationContext* opCtx,
if (mode && *mode == repl::OplogApplication::Mode::kApplyOpsCmd) {
auto duplicateRecordsList = scanIndexForDuplicates(opCtx, collection, idx);
if (!duplicateRecordsList.empty()) {
- uassertStatusOK(buildConvertUniqueErrorStatus(
- buildDuplicateViolations(opCtx, collection, duplicateRecordsList)));
+ uassertStatusOK(buildConvertUniqueErrorStatus(opCtx, collection, duplicateRecordsList));
}
}
@@ -340,23 +339,38 @@ std::list<std::set<RecordId>> scanIndexForDuplicates(
return duplicateRecordsList;
}
-BSONArray buildDuplicateViolations(OperationContext* opCtx,
- const CollectionPtr& collection,
- const std::list<std::set<RecordId>>& duplicateRecordsList) {
+Status buildConvertUniqueErrorStatus(OperationContext* opCtx,
+ const CollectionPtr& collection,
+ const std::list<std::set<RecordId>>& duplicateRecordsList) {
BSONArrayBuilder duplicateViolations;
+ size_t violationsSize = 0;
+
for (const auto& duplicateRecords : duplicateRecordsList) {
BSONArrayBuilder currViolatingIds;
for (const auto& recordId : duplicateRecords) {
auto doc = collection->docFor(opCtx, recordId).value();
- currViolatingIds.append(doc["_id"]);
+ auto id = doc["_id"];
+ violationsSize += id.size();
+
+ // Returns duplicate violations up to 8MB.
+ if (violationsSize > BSONObjMaxUserSize / 2) {
+ // Returns at least one violation.
+ if (duplicateViolations.arrSize() == 0 && currViolatingIds.arrSize() == 0) {
+ currViolatingIds.append(id);
+ }
+ if (currViolatingIds.arrSize() > 0) {
+ duplicateViolations.append(BSON("ids" << currViolatingIds.arr()));
+ }
+ return Status(
+ CannotConvertIndexToUniqueInfo(duplicateViolations.arr()),
+ "Cannot convert the index to unique. Too many conflicting documents were "
+ "detected. Please resolve them and rerun collMod.");
+ }
+ currViolatingIds.append(id);
}
duplicateViolations.append(BSON("ids" << currViolatingIds.arr()));
}
- return duplicateViolations.arr();
-}
-
-Status buildConvertUniqueErrorStatus(const BSONArray& violations) {
- return Status(CannotConvertIndexToUniqueInfo(violations),
+ return Status(CannotConvertIndexToUniqueInfo(duplicateViolations.arr()),
"Cannot convert the index to unique. Please resolve conflicting documents "
"before running collMod again.");
}
diff --git a/src/mongo/db/catalog/coll_mod_index.h b/src/mongo/db/catalog/coll_mod_index.h
index e5f78be7f55..acd7849d70e 100644
--- a/src/mongo/db/catalog/coll_mod_index.h
+++ b/src/mongo/db/catalog/coll_mod_index.h
@@ -79,15 +79,11 @@ std::list<std::set<RecordId>> scanIndexForDuplicates(
boost::optional<KeyString::Value> firstKeyString = {});
/**
- * Builds the BSONArray of the violations with duplicate index keys.
+ * Builds a BSONArray of the violations with duplicate index keys and returns the formatted error
+ * status for not being able to convert the index to unique.
*/
-BSONArray buildDuplicateViolations(OperationContext* opCtx,
- const CollectionPtr& collection,
- const std::list<std::set<RecordId>>& duplicateRecordsList);
-
-/**
- * Returns the formatted error status for not being able to convert the index to unique.
- */
-Status buildConvertUniqueErrorStatus(const BSONArray& violations);
+Status buildConvertUniqueErrorStatus(OperationContext* opCtx,
+ const CollectionPtr& collection,
+ const std::list<std::set<RecordId>>& duplicateRecordsList);
} // namespace mongo