SERVER-109929 shardCollection/reshardCollection must attempt to create a shardkey when a multikey/wildcard index exists on the shard key field (#42869)

GitOrigin-RevId: 4e10f9972c7443e1d3a3583107da052c451d6c40
This commit is contained in:
Silvia Surroca 2025-10-22 16:17:37 +02:00 committed by MongoDB Bot
parent d4ce0777a9
commit c43b6e0ba8
3 changed files with 84 additions and 52 deletions

View File

@ -25,7 +25,17 @@ const incompatibleShardKeyIndexes = [
{
key: {skey: 1, multiKeyField: 1},
options: {},
isMultiKey: true,
makeItMultiKey: function (coll) {
coll.insert({skey: 10, multiKeyField: [1, 2, 3]});
},
},
{
key: {skey: 1},
options: {},
makeItMultiKey: function (coll) {
coll.insert({skey: [1, 2, 3]});
},
skeyFieldIsMultikey: true,
},
{
key: {skey: 1, "a.$**": 1},
@ -87,8 +97,10 @@ describe("testing incompatible shard key indexes", function () {
beforeEach(() => {
this.coll.drop();
// Insert a document that will make the index on 'multiKeyField' multikey.
this.coll.insert({skey: 22, multiKeyField: [1, 2, 3]});
// Make the index multikey if necessary.
if (incompatibleShardKeyIndex.makeItMultiKey) {
incompatibleShardKeyIndex.makeItMultiKey(this.coll);
}
// Create the incompatible index.
assert.commandWorked(
@ -97,51 +109,50 @@ describe("testing incompatible shard key indexes", function () {
});
it("can't shard a non-empty collection with only an incompatible shard key index", () => {
const res = this.st.s.adminCommand({shardCollection: this.coll.getFullName(), key: shardKeyPattern});
this.coll.insert({skey: 33});
assert.commandFailedWithCode(
this.st.s.adminCommand({shardCollection: this.coll.getFullName(), key: shardKeyPattern}),
ErrorCodes.InvalidOptions,
[ErrorCodes.InvalidOptions],
);
});
it("shardColleciont on an empty collection will attempt to create a shard key index", () => {
it("shardCollecion on an empty collection will attempt to create a shard key index", () => {
this.coll.deleteMany({});
if (incompatibleShardKeyIndex.isMultiKey) {
// We can't test a multiKey index on an empty collection, since it would not
// be multiKey.
return;
if (incompatibleShardKeyIndex.makeItMultiKey) {
this.coll.remove({});
}
const res = this.st.s.adminCommand({shardCollection: this.coll.getFullName(), key: shardKeyPattern});
assert.commandWorkedOrFailedWithCode(res, [
ErrorCodes.InvalidOptions,
ErrorCodes.IndexKeySpecsConflict,
]);
if (res.ok) {
// The shardCollection command succeeded, check that the shard key index was
// created and it's not the incompatible index.
assert(
incompatibleShardKeyIndex.canCoexistWithShardKeyIndex,
"reshardCollection succeeded when it should have failed",
);
const shardKeyIndex = this.coll.getIndexByKey(shardKeyPattern);
const incompatibleIndex = this.coll.getIndexByKey(incompatibleShardKeyIndex.key);
assert.neq(shardKeyIndex, null, "shard key index was not created");
assert.neq(incompatibleIndex, null, "incompatible index is missing");
assert.neq(incompatibleIndex, shardKeyIndex, "shard key index mustn't be the incompatible index");
if (!incompatibleShardKeyIndex.canCoexistWithShardKeyIndex) {
assert.commandFailedWithCode(res, [
ErrorCodes.IndexOptionsConflict,
ErrorCodes.IndexKeySpecsConflict,
ErrorCodes.InvalidOptions,
]);
return;
}
assert.commandWorked(res);
// A valid shard key index should be implicitly created during the reshardCollection
// operation.
const shardKeyIndex = this.coll.getIndexByKey(shardKeyPattern);
const incompatibleIndex = this.coll.getIndexByKey(incompatibleShardKeyIndex.key);
assert.neq(shardKeyIndex, null, "shard key index was not created");
assert.neq(incompatibleIndex, null, "incompatible index is missing");
assert.neq(incompatibleIndex, shardKeyIndex, "shard key index mustn't be the incompatible index");
});
it("can't drop or hide last compatible shard key index", () => {
if (incompatibleShardKeyIndex.skeyFieldIsMultikey) {
// Can't create a compatible shard key index if the shard key field is multikey.
return;
}
// Shard the collection with a compatible shard key index.
let compatibleShardKeyIndex = shardKeyPattern;
if (!incompatibleShardKeyIndex.canCoexistWithShardKeyIndex) {
@ -173,27 +184,30 @@ describe("testing incompatible shard key indexes", function () {
numInitialChunks: 1,
});
assert.commandWorkedOrFailedWithCode(res, [
ErrorCodes.InvalidOptions,
ErrorCodes.IndexKeySpecsConflict,
]);
if (res.ok) {
// The reshardCollection command succeeded, check that the shard key index was
// created and it's not the incompatible index.
assert(
incompatibleShardKeyIndex.canCoexistWithShardKeyIndex,
"reshardCollection succeeded when it should have failed",
);
const shardKeyIndex = this.coll.getIndexByKey(shardKeyPattern);
const incompatibleIndex = this.coll.getIndexByKey(incompatibleShardKeyIndex.key);
assert.neq(shardKeyIndex, null, "shard key index was not created");
assert.neq(incompatibleIndex, null, "incompatible index is missing");
assert.neq(incompatibleIndex, shardKeyIndex, "shard key index mustn't be the incompatible index");
if (!incompatibleShardKeyIndex.canCoexistWithShardKeyIndex) {
assert.commandFailedWithCode(res, [
ErrorCodes.IndexOptionsConflict,
ErrorCodes.IndexKeySpecsConflict,
ErrorCodes.InvalidOptions,
]);
return;
}
assert.commandWorked(res);
// A valid shard key index should be implicitly created during the reshardCollection
// operation.
assert(
incompatibleShardKeyIndex.canCoexistWithShardKeyIndex,
"reshardCollection succeeded when it should have failed",
);
const shardKeyIndex = this.coll.getIndexByKey(shardKeyPattern);
const incompatibleIndex = this.coll.getIndexByKey(incompatibleShardKeyIndex.key);
assert.neq(shardKeyIndex, null, "shard key index was not created");
assert.neq(incompatibleIndex, null, "incompatible index is missing");
assert.neq(incompatibleIndex, shardKeyIndex, "shard key index mustn't be the incompatible index");
});
it("can't call refineCollectionShardKey with only an incompatible shard key index", () => {

View File

@ -189,10 +189,16 @@ bool validShardKeyIndexExists(OperationContext* opCtx,
// 2. Check for a useful index
bool hasUsefulIndexForKey = false;
bool indexPerfectlyMatchingShardKeyAlreadyExists = false;
std::string allReasons;
for (const auto& idx : indexes) {
std::string reasons;
BSONObj currentKey = idx["key"].embeddedObject();
if (shardKeyPattern.toBSON().woCompare(currentKey) == 0) {
indexPerfectlyMatchingShardKeyAlreadyExists = true;
}
// Check 2.i. and 2.ii.
if (!idx["sparse"].trueValue() && idx["filter"].eoo() && idx["collation"].eoo() &&
shardKeyPattern.toBSON().isPrefixOf(currentKey,
@ -261,7 +267,17 @@ bool validShardKeyIndexExists(OperationContext* opCtx,
if (hasUsefulIndexForKey) {
// Check 2.iii Make sure that there is a useful, non-multikey index available.
behaviors.verifyUsefulNonMultiKeyIndex(nss, shardKeyPattern.toBSON());
try {
behaviors.verifyUsefulNonMultiKeyIndex(nss, shardKeyPattern.toBSON());
} catch (ExceptionFor<ErrorCodes::InvalidOptions>& e) {
if (indexPerfectlyMatchingShardKeyAlreadyExists) {
e.addContext(str::stream()
<< "can't shard collection because an index already exists on the "
"shard key and it's a non shard key valid index.");
throw;
}
return false;
}
}
return hasUsefulIndexForKey;

View File

@ -219,6 +219,8 @@ bool validateShardKeyIndexExistsOrCreateIfPossible(OperationContext* opCtx,
* Returns true if the shard key is valid and already exists. Steps 1, 2 and 3 of the previous
* function.
*
* It throws an exception if a valid shard key index doesn't exist and it's not possible to create
* one.
*/
bool validShardKeyIndexExists(OperationContext* opCtx,
const NamespaceString& nss,