SERVER-119642 Expose recordIdsReplicated as part of listCollection info. (#48615)
Co-authored-by: Yuhong Zhang <yuhong.zhang@mongodb.com> GitOrigin-RevId: 278dc49f572c0302f5f32631efa4b5df79b498cf
This commit is contained in:
committed by
MongoDB Bot
parent
20e138bf7c
commit
f08740250e
@@ -26,7 +26,7 @@ jsTestLog("Cloning as capped should work with replicatedRecordIds and preserve t
|
||||
assert.commandWorked(db.runCommand({cloneCollectionAsCapped: collName, toCollection: cappedCollName, size: 2000}));
|
||||
let collectionOptions = db[cappedCollName].exists();
|
||||
assert(collectionOptions.options.capped, collectionOptions);
|
||||
assert(collectionOptions.options.recordIdsReplicated, collectionOptions);
|
||||
assert(collectionOptions.info.recordIdsReplicated, collectionOptions);
|
||||
let indexes = db[cappedCollName].getIndexes();
|
||||
assert.eq(indexes.length, 1, indexes);
|
||||
indexes = db[collName].getIndexes();
|
||||
@@ -36,7 +36,7 @@ jsTestLog("Converting to capped should work with recordIdsReplicated.");
|
||||
assert.commandWorked(db.runCommand({convertToCapped: collName, size: 2000}));
|
||||
collectionOptions = db[collName].exists();
|
||||
assert(collectionOptions.options.capped, collectionOptions);
|
||||
assert(collectionOptions.options.recordIdsReplicated, collectionOptions);
|
||||
assert(collectionOptions.info.recordIdsReplicated, collectionOptions);
|
||||
indexes = db[collName].getIndexes();
|
||||
assert.eq(indexes.length, 1, indexes);
|
||||
|
||||
@@ -44,4 +44,4 @@ jsTestLog("Creating collection with capped:true and recordIdsReplicated:true sho
|
||||
assert.commandWorked(db.runCommand({create: createdAsCappedCollName, capped: true, size: 2000}));
|
||||
collectionOptions = db[createdAsCappedCollName].exists();
|
||||
assert(collectionOptions.options.capped, collectionOptions);
|
||||
assert(collectionOptions.options.recordIdsReplicated, collectionOptions);
|
||||
assert(collectionOptions.info.recordIdsReplicated, collectionOptions);
|
||||
|
||||
@@ -35,7 +35,7 @@ assert(
|
||||
);
|
||||
jsTestLog("Collection options after creation: " + tojson(collInfo));
|
||||
assert(
|
||||
collInfo.options.hasOwnProperty("recordIdsReplicated"),
|
||||
collInfo.info.hasOwnProperty("recordIdsReplicated"),
|
||||
"collection options does not contain recordIdsReplicated flag after collection creation",
|
||||
);
|
||||
|
||||
@@ -58,7 +58,7 @@ assert(
|
||||
);
|
||||
jsTestLog("Collection options after collMod: " + tojson(collInfo));
|
||||
assert(
|
||||
!collInfo.options.hasOwnProperty("recordIdsReplicated"),
|
||||
!collInfo.info.hasOwnProperty("recordIdsReplicated"),
|
||||
"collMod failed to remove recordIdsReplicated flag from collection options",
|
||||
);
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ const clusteredCollName = collName + "_clustered";
|
||||
assert.commandWorked(db.createCollection(clusteredCollName, {clusteredIndex: {key: {_id: 1}, unique: true}}));
|
||||
const clusteredCollInfo = db[clusteredCollName].exists();
|
||||
assert(
|
||||
!clusteredCollInfo.options.hasOwnProperty("recordIdsReplicated"),
|
||||
!clusteredCollInfo.info.hasOwnProperty("recordIdsReplicated"),
|
||||
"clustered collection created with recordIdsReplicated collection option: " + tojson(clusteredCollInfo),
|
||||
);
|
||||
|
||||
@@ -46,6 +46,6 @@ assert(
|
||||
);
|
||||
jsTestLog("Collection options after creation: " + tojson(collInfo));
|
||||
assert(
|
||||
collInfo.options.hasOwnProperty("recordIdsReplicated"),
|
||||
collInfo.info.hasOwnProperty("recordIdsReplicated"),
|
||||
"collection options does not contain recordIdsReplicated flag after collection creation",
|
||||
);
|
||||
|
||||
@@ -77,7 +77,7 @@ function mapListCatalogToListCollectionsEntry(listCatalogEntry, listCatalogMap,
|
||||
// Destructure the nested `md` field and validate that we recognize all fields.
|
||||
const {
|
||||
options: mdOptions,
|
||||
recordIdsReplicated: recordIdsReplicated,
|
||||
recordIdsReplicated: mdRecordIdsReplicated,
|
||||
// Indexes are carefully checked when validating `listIndexes` later.
|
||||
indexes: mdIndexes,
|
||||
// Namespace information can be safely thrown away.
|
||||
@@ -121,6 +121,7 @@ function mapListCatalogToListCollectionsEntry(listCatalogEntry, listCatalogMap,
|
||||
readOnly: isDbReadOnly,
|
||||
uuid: mdOptionsUuid,
|
||||
...(nsConfigDebugDump !== undefined && {configDebugDump: nsConfigDebugDump}),
|
||||
...(mdRecordIdsReplicated && {recordIdsReplicated: mdRecordIdsReplicated}),
|
||||
},
|
||||
...(idIndex !== undefined && {idIndex: idIndex.spec}),
|
||||
};
|
||||
@@ -379,15 +380,16 @@ function validateListCatalogToListCollectionsConsistency(
|
||||
if (e.type == "view" || e.type == "timeseries") delete e.info.configDebugDump;
|
||||
});
|
||||
}
|
||||
// TODO (SERVER-91702): Remove the exclusion once the race with downgrade is fixed.
|
||||
if (ignoreRecordIdsReplicatedOption) {
|
||||
listCatalogMap.forEach((e) => delete e.options.recordIdsReplicated);
|
||||
listCollections.forEach((e) => delete e.options.recordIdsReplicated);
|
||||
}
|
||||
|
||||
const listCollectionsFromListCatalog = removeDuplicateDocuments(sortCollectionsInPlace(listCatalogMap));
|
||||
const sortedListCollections = sortCollectionsInPlace([...listCollections]);
|
||||
|
||||
// TODO (SERVER-91702): Remove the exclusion once the race with downgrade is fixed.
|
||||
if (ignoreRecordIdsReplicatedOption) {
|
||||
listCollectionsFromListCatalog.forEach((e) => delete e.info.recordIdsReplicated);
|
||||
sortedListCollections.forEach((e) => delete e.info.recordIdsReplicated);
|
||||
}
|
||||
|
||||
const equals = bsonUnorderedFieldArrayEquals(listCollectionsFromListCatalog, sortedListCollections);
|
||||
if (!equals) {
|
||||
const message =
|
||||
@@ -737,11 +739,11 @@ export function assertCatalogListOperationsConsistencyForDb(db, tenantId) {
|
||||
if (TestData.isRunningFCVUpgradeDowngradeSuite) {
|
||||
catalogInfo.forEach((doc) => {
|
||||
if (doc.md) {
|
||||
delete doc.md.options.recordIdsReplicated;
|
||||
delete doc.md.recordIdsReplicated;
|
||||
}
|
||||
});
|
||||
collInfo.forEach((doc) => {
|
||||
delete doc.options.recordIdsReplicated;
|
||||
delete doc.info.recordIdsReplicated;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ assert.commandWorked(coll.remove({_id: 4}));
|
||||
// a) if recordIdsReplicated:true, replaying the prepare oplog entry will give the previously
|
||||
// inserted document the same RID it had then - RID: 2 - as this info is present in the oplog entry.
|
||||
// b) if recordIdsReplicated:false, determine that RID 4 is not visible and insert at RID 5.
|
||||
let preparedRecordId = primary.getDB("test").getCollectionInfos({name: "foo"})[0].options.recordIdsReplicated
|
||||
let preparedRecordId = primary.getDB("test").getCollectionInfos({name: "foo"})[0].info.recordIdsReplicated
|
||||
? NumberLong(2)
|
||||
: NumberLong(5);
|
||||
replTest.restart(primary);
|
||||
@@ -129,7 +129,7 @@ assert.commandWorked(s2.commitTransaction_forTesting());
|
||||
|
||||
coll = primary.getDB("test")["foo"];
|
||||
assert.commandWorked(coll.insert({_id: 6})); // Should not re-use any RecordIds
|
||||
const newestRecordId = primary.getDB("test").getCollectionInfos({name: "foo"})[0].options.recordIdsReplicated
|
||||
const newestRecordId = primary.getDB("test").getCollectionInfos({name: "foo"})[0].info.recordIdsReplicated
|
||||
? NumberLong(5)
|
||||
: NumberLong(6);
|
||||
docs = sessionDb["foo"].find().showRecordId().toArray();
|
||||
|
||||
@@ -132,7 +132,7 @@ assertDocsInColl(node, []);
|
||||
// an existing recordId. In this case, because the standalone can't see existing non-majority
|
||||
// committed documents, the test must take care to make sure the document inserted as a standalone
|
||||
// doesn't use a recordId that collides with the non-majority committed documents' recordIds.
|
||||
if (node.getDB(dbName).getCollectionInfos({name: collName})[0].options.recordIdsReplicated) {
|
||||
if (node.getDB(dbName).getCollectionInfos({name: collName})[0].info.recordIdsReplicated) {
|
||||
assert.commandWorked(
|
||||
node.getDB(dbName).runCommand({
|
||||
applyOps: [
|
||||
|
||||
@@ -38,10 +38,9 @@ function makeSrcAndDstNames() {
|
||||
}
|
||||
|
||||
function assertRecordIdsReplicated(coll) {
|
||||
const collOptions = assert.commandWorked(
|
||||
coll.getDB().runCommand({listCollections: 1, filter: {name: coll.getName()}}),
|
||||
).cursor.firstBatch[0].options;
|
||||
assert(collOptions.recordIdsReplicated);
|
||||
const collInfo = assert.commandWorked(coll.getDB().runCommand({listCollections: 1, filter: {name: coll.getName()}}))
|
||||
.cursor.firstBatch[0].info;
|
||||
assert(collInfo.recordIdsReplicated);
|
||||
}
|
||||
|
||||
function validateRidsAcrossNodes(coll) {
|
||||
|
||||
@@ -55,7 +55,7 @@ assert(
|
||||
);
|
||||
jsTestLog("Collection options: " + tojson(collInfo));
|
||||
assert(
|
||||
!collInfo.options.hasOwnProperty("recordIdsReplicated"),
|
||||
!collInfo.info.hasOwnProperty("recordIdsReplicated"),
|
||||
"collMod failed to remove recordIdsReplicated flag from collection options",
|
||||
);
|
||||
|
||||
@@ -73,7 +73,7 @@ assert(
|
||||
tojson(secondaryDB.getCollectionInfos()),
|
||||
);
|
||||
assert(
|
||||
!secondaryCollInfo.options.hasOwnProperty("recordIdsReplicated"),
|
||||
!secondaryCollInfo.info.hasOwnProperty("recordIdsReplicated"),
|
||||
"collMod failed to remove recordIdsReplicated flag from collection options on secondary",
|
||||
);
|
||||
|
||||
|
||||
@@ -166,6 +166,9 @@ filters:
|
||||
- "swallow_unnecessary_uuid_mismatch_error.js":
|
||||
approvers:
|
||||
- 10gen/query-execution-router
|
||||
- "replicate_record_ids_collection_migration.js":
|
||||
approvers:
|
||||
- 10gen/server-collection-write-path
|
||||
- "heal_config_shards_on_fcv_upgrade.js":
|
||||
approvers:
|
||||
- 10gen/server-catalog-and-routing
|
||||
|
||||
@@ -147,7 +147,7 @@ function runMoveChunkReplicaRecordIDsTest(collName, keyDoc, useBounds, splitChun
|
||||
|
||||
// Ensure collection option 'recordIdsReplicated' is preserved on destination shard.
|
||||
const collInfo = shard1.getCollection(ns).exists();
|
||||
assert(collInfo.options.recordIdsReplicated, tojson(collInfo));
|
||||
assert(collInfo.info.recordIdsReplicated, tojson(collInfo));
|
||||
}
|
||||
|
||||
const st = new ShardingTest({mongos: 1, shards: 2});
|
||||
|
||||
@@ -31,7 +31,7 @@ assert.commandWorked(testDB.adminCommand({enableSharding: dbName, primaryShard:
|
||||
assert.commandWorked(testDB.createCollection(collName));
|
||||
|
||||
let collInfo = coll.exists();
|
||||
assert(collInfo.options.recordIdsReplicated, tojson(collInfo));
|
||||
assert(collInfo.info.recordIdsReplicated, tojson(collInfo));
|
||||
|
||||
// Remove some of the initial documents on the collection with replicated record
|
||||
// IDs to create gaps in the record IDs.
|
||||
@@ -107,10 +107,10 @@ assert.eq(
|
||||
// Ensure that 'recordIdsReplicated` collection option is still present and set to true.
|
||||
// Check collection options on shard.
|
||||
collInfo = shard1.getCollection(collNS).exists();
|
||||
assert(collInfo.options.recordIdsReplicated, tojson(collInfo));
|
||||
assert(collInfo.info.recordIdsReplicated, tojson(collInfo));
|
||||
|
||||
// Check collection options through mongos.
|
||||
collInfo = mongos.getCollection(collNS).exists();
|
||||
assert(collInfo.options.recordIdsReplicated, tojson(collInfo));
|
||||
assert(collInfo.info.recordIdsReplicated, tojson(collInfo));
|
||||
|
||||
st.stop();
|
||||
|
||||
@@ -113,7 +113,7 @@ function runMoveRangeReplicaRecordIDsTest(collName, keyDoc) {
|
||||
|
||||
// Ensure collection option 'recordIdsReplicated' is preserved on destination shard.
|
||||
const collInfoShard1 = shard1.getCollection(ns).exists();
|
||||
assert(collInfoShard1.options.recordIdsReplicated, tojson(collInfoShard1));
|
||||
assert(collInfoShard1.info.recordIdsReplicated, tojson(collInfoShard1));
|
||||
|
||||
// Second move: shard1 to shard0.
|
||||
|
||||
@@ -131,7 +131,7 @@ function runMoveRangeReplicaRecordIDsTest(collName, keyDoc) {
|
||||
|
||||
// Ensure collection option 'recordIdsReplicated' is still present.
|
||||
const collInfoShard0 = shard0.getCollection(ns).exists();
|
||||
assert(collInfoShard0.options.recordIdsReplicated, tojson(collInfoShard0));
|
||||
assert(collInfoShard0.info.recordIdsReplicated, tojson(collInfoShard0));
|
||||
}
|
||||
|
||||
const st = new ShardingTest({mongos: 1, shards: 2});
|
||||
|
||||
@@ -135,9 +135,8 @@ BaseCloner::AfterStageBehavior DatabaseCloner::listCollectionsStage() {
|
||||
// collectionUUID there as part of the options, but instead places it in the 'info' field.
|
||||
// We need to move it back to CollectionOptions to create the collection properly.
|
||||
result.getOptions().uuid = result.getInfo().getUuid();
|
||||
// TODO SERVER-119642 Use the recordIdsReplicated from listCollection info.
|
||||
_collections.emplace_back(
|
||||
collectionNamespace, result.getOptions(), result.getOptions().recordIdsReplicated);
|
||||
collectionNamespace, result.getOptions(), result.getInfo().getRecordIdsReplicated());
|
||||
}
|
||||
return kContinueNormally;
|
||||
}
|
||||
|
||||
@@ -54,6 +54,9 @@ structs:
|
||||
optional: true
|
||||
uuid:
|
||||
type: uuid
|
||||
recordIdsReplicated:
|
||||
type: bool
|
||||
optional: true
|
||||
mod_visibility: pub
|
||||
|
||||
ListCollectionResult:
|
||||
|
||||
@@ -187,8 +187,9 @@ TEST_F(DatabaseClonerTest, ListCollectionsCollectionsWithRecordIds) {
|
||||
BSON("name" << "b"
|
||||
<< "type"
|
||||
<< "collection"
|
||||
<< "options" << BSON("recordIdsReplicated" << true) << "info"
|
||||
<< BSON("readOnly" << false << "uuid" << uuid2))};
|
||||
<< "options" << BSONObj() << "info"
|
||||
<< BSON("readOnly" << false << "uuid" << uuid2 << "recordIdsReplicated"
|
||||
<< true))};
|
||||
_mockServer->setCommandReply("listCollections",
|
||||
createListCollectionsResponse({sourceInfos[0], sourceInfos[1]}));
|
||||
ASSERT_OK(cloner->run());
|
||||
@@ -202,8 +203,8 @@ TEST_F(DatabaseClonerTest, ListCollectionsCollectionsWithRecordIds) {
|
||||
ASSERT_EQ(false, std::get<2>(collections[0]));
|
||||
ASSERT_EQ(NamespaceString::createNamespaceString_forTest(_dbName, "b"),
|
||||
std::get<0>(collections[1]));
|
||||
ASSERT_BSONOBJ_EQ(BSON("uuid" << uuid2 << "recordIdsReplicated" << true),
|
||||
std::get<1>(collections[1]).toBSON());
|
||||
ASSERT_BSONOBJ_EQ(BSON("uuid" << uuid2), std::get<1>(collections[1]).toBSON());
|
||||
// The recordIdsReplicated
|
||||
ASSERT_EQ(true, std::get<2>(collections[1]));
|
||||
}
|
||||
|
||||
|
||||
@@ -189,13 +189,18 @@ void _addWorkingSetMember(OperationContext* opCtx,
|
||||
BSONObj buildInfoField(OperationContext* opCtx,
|
||||
bool readOnly,
|
||||
const NamespaceString& nss,
|
||||
boost::optional<UUID> uuid) {
|
||||
boost::optional<UUID> uuid,
|
||||
boost::optional<bool> recordIdsReplicated) {
|
||||
BSONObjBuilder infoBuilder;
|
||||
infoBuilder.append("readOnly", readOnly);
|
||||
if (uuid) {
|
||||
infoBuilder.appendElements(uuid->toBSON());
|
||||
}
|
||||
|
||||
if (recordIdsReplicated.get_value_or(false)) {
|
||||
infoBuilder.appendBool("recordIdsReplicated", true);
|
||||
}
|
||||
|
||||
if (const auto configDebugDump =
|
||||
catalog::getConfigDebugDump(VersionContext::getDecoration(opCtx), nss);
|
||||
configDebugDump.has_value()) {
|
||||
@@ -223,7 +228,12 @@ BSONObj buildViewBson(OperationContext* opCtx, const ViewDefinition& view, bool
|
||||
}
|
||||
optionsBuilder.doneFast();
|
||||
|
||||
b.append("info", buildInfoField(opCtx, true /*readOnly*/, view.name(), boost::none /*uuid*/));
|
||||
b.append("info",
|
||||
buildInfoField(opCtx,
|
||||
true /*readOnly*/,
|
||||
view.name(),
|
||||
boost::none /*uuid*/,
|
||||
boost::none /*recordIdsReplicated*/));
|
||||
return b.obj();
|
||||
}
|
||||
|
||||
@@ -241,8 +251,12 @@ BSONObj buildTimeseriesBson(OperationContext* opCtx, const Collection* collectio
|
||||
builder.append("options",
|
||||
collection->getCollectionOptions().toBSON(
|
||||
false /* includeUUID */, timeseries::kAllowedCollectionCreationOptions));
|
||||
builder.append(
|
||||
"info", buildInfoField(opCtx, opCtx->readOnly(), collection->ns(), boost::none /*uuid*/));
|
||||
builder.append("info",
|
||||
buildInfoField(opCtx,
|
||||
opCtx->readOnly(),
|
||||
collection->ns(),
|
||||
boost::none /*uuid*/,
|
||||
boost::none /*recordIdsReplicated*/));
|
||||
return builder.obj();
|
||||
}
|
||||
|
||||
@@ -256,7 +270,12 @@ BSONObj buildTimeseriesBson(OperationContext* opCtx, const NamespaceString& nss,
|
||||
}
|
||||
|
||||
builder.append("options", BSONObj{});
|
||||
builder.append("info", buildInfoField(opCtx, opCtx->readOnly(), nss, boost::none /*uuid*/));
|
||||
builder.append("info",
|
||||
buildInfoField(opCtx,
|
||||
opCtx->readOnly(),
|
||||
nss,
|
||||
boost::none /*uuid*/,
|
||||
boost::none /*recordIdsReplicated*/));
|
||||
|
||||
return builder.obj();
|
||||
}
|
||||
@@ -306,7 +325,12 @@ BSONObj buildCollectionBson(OperationContext* opCtx,
|
||||
// unsettable read-only property, so put it in the 'info' section. Pass 'false' to toBSON so
|
||||
// it doesn't include 'uuid' here.
|
||||
b.append("options", options.toBSON(false /* includeUUID */, includeOptionsFields));
|
||||
b.append("info", buildInfoField(opCtx, opCtx->readOnly(), collection->ns(), options.uuid));
|
||||
b.append("info",
|
||||
buildInfoField(opCtx,
|
||||
opCtx->readOnly(),
|
||||
collection->ns(),
|
||||
options.uuid,
|
||||
collection->areRecordIdsReplicated()));
|
||||
|
||||
auto idIndex = collection->getIndexCatalog()->findIdIndex(opCtx);
|
||||
if (idIndex) {
|
||||
|
||||
@@ -56,6 +56,11 @@ structs:
|
||||
(e.g., mongodump).
|
||||
optional: true
|
||||
stability: unstable
|
||||
recordIdsReplicated:
|
||||
type: bool
|
||||
description: "If true, the collection has record Ids replicated."
|
||||
optional: true
|
||||
stability: unstable
|
||||
|
||||
ListCollectionsReplyItem:
|
||||
description: "Individual result"
|
||||
|
||||
Reference in New Issue
Block a user