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:
Ernesto Rodriguez Reina
2026-03-02 13:53:59 -05:00
committed by MongoDB Bot
parent 20e138bf7c
commit f08740250e
17 changed files with 78 additions and 42 deletions

View File

@@ -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);

View File

@@ -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",
);

View File

@@ -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",
);

View File

@@ -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;
});
}

View File

@@ -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();

View File

@@ -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: [

View File

@@ -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) {

View File

@@ -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",
);

View File

@@ -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

View File

@@ -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});

View File

@@ -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();

View File

@@ -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});

View File

@@ -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;
}

View File

@@ -54,6 +54,9 @@ structs:
optional: true
uuid:
type: uuid
recordIdsReplicated:
type: bool
optional: true
mod_visibility: pub
ListCollectionResult:

View File

@@ -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]));
}

View File

@@ -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) {

View File

@@ -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"