Skip to content

Commit 6176c2d

Browse files
jordistEvergreen Agent
authored andcommitted
SERVER-79661 Make the 'internalRenameIfOptionsAndIndexesMatch' command sharding-aware
1 parent f505e82 commit 6176c2d

8 files changed

+288
-253
lines changed
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/*
2+
* Tests the internalRenameIfOptionsAndIndexesMatch on shard servers.
3+
* Note: Because the mongos doesn't expose this command, we need a directed noPassthrough test to be
4+
* able to test it by running it directly on shards.
5+
*/
6+
7+
const st = new ShardingTest({shards: 2});
8+
9+
const dbName = "test";
10+
const testDB = st.getDB(dbName);
11+
const sourceColl = testDB['source'];
12+
const destColl = testDB['dest'];
13+
const inexistentColl = testDB['inexistentColl'];
14+
15+
function setupCollections() {
16+
sourceColl.drop();
17+
destColl.drop();
18+
inexistentColl.drop();
19+
20+
sourceColl.insert({x: 1});
21+
destColl.insert({});
22+
}
23+
24+
setupCollections();
25+
26+
const dbPrimaryShardConn = st.getPrimaryShard(dbName);
27+
28+
function makeCorrectCommand(sourceColl, destColl) {
29+
const dbVersion = st.s.getDB('config')['databases'].findOne({_id: dbName}).version;
30+
31+
let collectionInfos = testDB.getCollectionInfos({name: destColl.getName()});
32+
let collectionOptions = collectionInfos.length === 1 ? collectionInfos[0].options : {};
33+
34+
return {
35+
internalRenameIfOptionsAndIndexesMatch: 1,
36+
from: sourceColl.getFullName(),
37+
to: destColl.getFullName(),
38+
indexes: destColl.getIndexes(),
39+
collectionOptions: collectionOptions,
40+
databaseVersion: dbVersion
41+
};
42+
}
43+
44+
// If no dbVersion attached, command fails.
45+
{
46+
let cmd = makeCorrectCommand(sourceColl, destColl);
47+
delete cmd.databaseVersion;
48+
assert.commandFailedWithCode(dbPrimaryShardConn.adminCommand(cmd), ErrorCodes.IllegalOperation);
49+
}
50+
51+
// If wrong dbVersion attached, command fails
52+
{
53+
let cmd = makeCorrectCommand(sourceColl, destColl);
54+
cmd.databaseVersion.timestamp = Timestamp(12345, 6789);
55+
assert.commandFailedWithCode(dbPrimaryShardConn.adminCommand(cmd), ErrorCodes.StaleDbVersion);
56+
}
57+
58+
// If source collection does not exist, command fails.
59+
{
60+
let cmd = makeCorrectCommand(inexistentColl, destColl);
61+
assert.commandFailedWithCode(dbPrimaryShardConn.adminCommand(cmd),
62+
ErrorCodes.NamespaceNotFound);
63+
}
64+
65+
// If expected indexes don't match, command fails.
66+
{
67+
destColl.createIndex({x: 1});
68+
69+
// Collection does not exist, but indexes expected.
70+
let cmd = makeCorrectCommand(sourceColl, inexistentColl);
71+
cmd.indexes = destColl.getIndexes();
72+
assert.commandFailedWithCode(dbPrimaryShardConn.adminCommand(cmd), ErrorCodes.CommandFailed);
73+
74+
// Collection exists and has indexes, but wrong indexes expected.
75+
cmd = makeCorrectCommand(sourceColl, destColl);
76+
destColl.dropIndex("x_1");
77+
assert.commandFailedWithCode(dbPrimaryShardConn.adminCommand(cmd), ErrorCodes.CommandFailed);
78+
}
79+
setupCollections();
80+
81+
// If expected options don't match, command fails.
82+
{
83+
assert.commandWorked(testDB.runCommand({collMod: destColl.getName(), validator: {a: 1}}));
84+
const options = testDB.getCollectionInfos({name: destColl.getName()})[0];
85+
86+
// Collection does not exist, but some options expected.
87+
let cmd = makeCorrectCommand(sourceColl, inexistentColl);
88+
cmd.collectionOptions = options;
89+
assert.commandFailedWithCode(dbPrimaryShardConn.adminCommand(cmd), ErrorCodes.CommandFailed);
90+
91+
// Collection exists and has options set, but wrong options expected.
92+
cmd = makeCorrectCommand(sourceColl, destColl);
93+
assert.commandWorked(testDB.runCommand({collMod: destColl.getName(), validator: {}}));
94+
assert.commandFailedWithCode(dbPrimaryShardConn.adminCommand(cmd), ErrorCodes.CommandFailed);
95+
}
96+
setupCollections();
97+
98+
// If target collection is sharded, fails
99+
{
100+
let shardedColl = testDB['sharded'];
101+
assert.commandWorked(
102+
st.s.adminCommand({shardCollection: shardedColl.getFullName(), key: {x: 1}}));
103+
let cmd = makeCorrectCommand(sourceColl, shardedColl);
104+
assert.commandFailedWithCode(dbPrimaryShardConn.adminCommand(cmd), ErrorCodes.IllegalOperation);
105+
}
106+
setupCollections();
107+
108+
// Expectations are met. Command works.
109+
{
110+
let cmd = makeCorrectCommand(sourceColl, destColl);
111+
assert.commandWorked(dbPrimaryShardConn.adminCommand(cmd));
112+
assert.eq(1, destColl.find({x: 1}).itcount());
113+
114+
setupCollections();
115+
cmd = makeCorrectCommand(sourceColl, inexistentColl);
116+
assert.commandWorked(dbPrimaryShardConn.adminCommand(cmd));
117+
assert.eq(1, inexistentColl.find({x: 1}).itcount());
118+
}
119+
120+
st.stop();

jstests/noPassthrough/timeseries/timeseries_out_concurrent_sharding.js

Lines changed: 0 additions & 182 deletions
This file was deleted.

src/mongo/db/catalog/rename_collection.cpp

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -800,41 +800,52 @@ void doLocalRenameIfOptionsAndIndexesHaveNotChanged(OperationContext* opCtx,
800800
std::list<BSONObj> originalIndexes,
801801
BSONObj originalCollectionOptions) {
802802
AutoGetDb dbLock(opCtx, targetNs.dbName(), MODE_X);
803+
804+
// Check target collection options match expected.
803805
auto collection = dbLock.getDb()
804806
? CollectionCatalog::get(opCtx)->lookupCollectionByNamespace(opCtx, targetNs)
805807
: nullptr;
806-
BSONObj collectionOptions = {};
807-
if (collection) {
808-
// We do not include the UUID field in the options comparison. It is ok if the target
809-
// collection was dropped and recreated, as long as the new target collection has the same
810-
// options and indexes as the original one did. This is mainly to support concurrent $out
811-
// to the same collection.
812-
collectionOptions = collection->getCollectionOptions().toBSON().removeField("uuid");
813-
}
808+
const BSONObj collectionOptions =
809+
collection ? collection->getCollectionOptions().toBSON() : BSONObj();
810+
checkTargetCollectionOptionsMatch(targetNs, originalCollectionOptions, collectionOptions);
811+
812+
// Check target collection indexes match expected.
813+
const auto currentIndexes =
814+
listIndexesEmptyListIfMissing(opCtx, targetNs, ListIndexesInclude::Nothing);
815+
checkTargetCollectionIndexesMatch(targetNs, originalIndexes, currentIndexes);
816+
817+
validateAndRunRenameCollection(opCtx, sourceNs, targetNs, options);
818+
}
814819

820+
void checkTargetCollectionOptionsMatch(const NamespaceString& targetNss,
821+
const BSONObj& expectedOptions,
822+
const BSONObj& currentOptions) {
823+
// We do not include the UUID field in the options comparison. It is ok if the target collection
824+
// was dropped and recreated, as long as the new target collection has the same options and
825+
// indexes as the original one did. This is mainly to support concurrent $out to the same
826+
// collection.
815827
uassert(ErrorCodes::CommandFailed,
816828
str::stream() << "collection options of target collection "
817-
<< targetNs.toStringForErrorMsg()
818-
<< " changed during processing. Original options: "
819-
<< originalCollectionOptions << ", new options: " << collectionOptions,
820-
SimpleBSONObjComparator::kInstance.evaluate(
821-
originalCollectionOptions.removeField("uuid") == collectionOptions));
822-
823-
auto currentIndexes =
824-
listIndexesEmptyListIfMissing(opCtx, targetNs, ListIndexesInclude::Nothing);
829+
<< targetNss.toStringForErrorMsg()
830+
<< " changed during processing. Original options: " << expectedOptions
831+
<< ", new options: " << currentOptions,
832+
SimpleBSONObjComparator::kInstance.evaluate(expectedOptions.removeField("uuid") ==
833+
currentOptions.removeField("uuid")));
834+
}
825835

836+
void checkTargetCollectionIndexesMatch(const NamespaceString& targetNss,
837+
const std::list<BSONObj>& expectedIndexes,
838+
const std::list<BSONObj>& currentIndexes) {
826839
UnorderedFieldsBSONObjComparator comparator;
827840
uassert(
828841
ErrorCodes::CommandFailed,
829-
str::stream() << "indexes of target collection " << targetNs.toStringForErrorMsg()
842+
str::stream() << "indexes of target collection " << targetNss.toStringForErrorMsg()
830843
<< " changed during processing.",
831-
originalIndexes.size() == currentIndexes.size() &&
832-
std::equal(originalIndexes.begin(),
833-
originalIndexes.end(),
844+
expectedIndexes.size() == currentIndexes.size() &&
845+
std::equal(expectedIndexes.begin(),
846+
expectedIndexes.end(),
834847
currentIndexes.begin(),
835848
[&](auto& lhs, auto& rhs) { return comparator.compare(lhs, rhs) == 0; }));
836-
837-
validateAndRunRenameCollection(opCtx, sourceNs, targetNs, options);
838849
}
839850

840851
void validateNamespacesForRenameCollection(OperationContext* opCtx,

0 commit comments

Comments
 (0)