/** * Test that change streams returns create events which captures the options specified on the * original user command. * * @tags: [ * requires_fcv_60, * # TODO (SERVER-89668): Remove tag. Currently incompatible due to change * # events containing the recordIdsReplicated:true option, which * # this test dislikes. * exclude_when_record_ids_replicated * ] */ import {assertDropCollection} from "jstests/libs/collection_drop_recreate.js"; import {FixtureHelpers} from "jstests/libs/fixture_helpers.js"; import { assertChangeStreamEventEq, ChangeStreamTest } from "jstests/libs/query/change_stream_util.js"; const testDB = db.getSiblingDB(jsTestName()); const dbName = testDB.getName(); const collName = jsTestName(); const ns = { db: dbName, coll: collName }; const test = new ChangeStreamTest(testDB); function getCollectionUuid(coll) { const collInfo = testDB.getCollectionInfos({name: coll})[0]; return collInfo.info.uuid; } function assertNextChangeEvent(cursor, expectedEvent) { const event = test.getOneChange(cursor); // Check the presence and the type of 'wallTime' field. We have no way to check the correctness // of 'wallTime' value, so we delete it afterwards. assert(event.wallTime instanceof Date); delete event.wallTime; expectedEvent.collectionUUID = getCollectionUuid(collName); assertChangeStreamEventEq(event, expectedEvent); } function runTest(startChangeStream) { function validateExpectedEventAndDropCollection(command, expectedOutput) { const cursor = startChangeStream(); assert.commandWorked(testDB.runCommand(command)); assertNextChangeEvent(cursor, expectedOutput); assertDropCollection(testDB, collName); } // Basic create collection command. validateExpectedEventAndDropCollection({create: collName}, { operationType: "create", ns: ns, operationDescription: {idIndex: {v: 2, key: {_id: 1}, name: "_id_"}, type: "collection"}, }); // With implicit create collection through insert. let cursor = startChangeStream(); assert.commandWorked(testDB.runCommand({insert: collName, documents: [{_id: 0}]})); assertNextChangeEvent(cursor, { operationType: "create", ns: ns, operationDescription: {idIndex: {v: 2, key: {_id: 1}, name: "_id_"}, type: "collection"} }); assertNextChangeEvent( cursor, {operationType: "insert", fullDocument: {_id: 0}, ns: ns, documentKey: {_id: 0}}); assertDropCollection(testDB, collName); // With capped collection parameters. let expectedSize = 1000; validateExpectedEventAndDropCollection({create: collName, capped: true, size: 1000, max: 1000}, { operationType: "create", ns: ns, operationDescription: { idIndex: {v: 2, key: {_id: 1}, name: "_id_"}, capped: true, size: expectedSize, max: 1000, type: "collection", } }); // With wired tiger setting. const customWiredTigerSettings = {wiredTiger: {configString: "block_compressor=zlib"}}; validateExpectedEventAndDropCollection( { create: collName, indexOptionDefaults: {storageEngine: customWiredTigerSettings}, storageEngine: customWiredTigerSettings }, { operationType: "create", ns: ns, operationDescription: { idIndex: {v: 2, key: {_id: 1}, name: "_id_"}, indexOptionDefaults: {storageEngine: customWiredTigerSettings}, storageEngine: customWiredTigerSettings, type: "collection", } }); // With validator collection parameters. validateExpectedEventAndDropCollection( {create: collName, validator: {a: 1}, validationLevel: "off", validationAction: "warn"}, { operationType: "create", ns: ns, operationDescription: { idIndex: {v: 2, key: {_id: 1}, name: "_id_"}, validator: {a: 1}, validationLevel: "off", validationAction: "warn", type: "collection", } }); // With collation. const collation = { locale: "en", "caseLevel": false, "caseFirst": "off", "strength": 3, "numericOrdering": false, "alternate": "non-ignorable", "maxVariable": "punct", "normalization": false, "backwards": true, "version": "57.1" }; validateExpectedEventAndDropCollection({create: collName, collation: collation}, { operationType: "create", ns: ns, operationDescription: { collation: collation, idIndex: {v: 2, key: {_id: 1}, name: "_id_", collation: collation}, type: "collection", } }); // With clustered index. validateExpectedEventAndDropCollection( { create: collName, clusteredIndex: {key: {_id: 1}, name: "newName", unique: true}, expireAfterSeconds: 10, }, { operationType: "create", ns: ns, operationDescription: { clusteredIndex: {v: 2, key: {_id: 1}, name: "newName", unique: true}, expireAfterSeconds: NumberLong(10), type: "collection", } }); // With idIndex field. validateExpectedEventAndDropCollection( {create: collName, idIndex: {v: 2, key: {_id: 1}, name: "ignored"}}, { operationType: "create", ns: ns, operationDescription: { idIndex: {v: 2, key: {_id: 1}, name: "_id_"}, type: "collection", } }); validateExpectedEventAndDropCollection( {create: collName, idIndex: {v: 1, key: {_id: 1}, name: "new"}}, {operationType: "create", ns: ns, operationDescription: {type: "collection"}}); // Verify that the time-series create command does not produce an event. cursor = startChangeStream(); assert.commandWorked(testDB.runCommand({create: collName, timeseries: {timeField: "t"}})); test.assertNextChangesEqual({cursor: cursor, expectedChanges: []}); assertDropCollection(testDB, collName); if (FixtureHelpers.isMongos(db)) { cursor = startChangeStream(); assert.commandWorked(testDB.adminCommand({enableSharding: ns.db})); assert.commandWorked( testDB.adminCommand({shardCollection: ns.db + "." + collName, key: {a: 1}})); assertNextChangeEvent(cursor, { operationType: "create", ns, operationDescription: { idIndex: {v: 2, key: {_id: 1}, name: "_id_"}, type: "collection", } }); assertDropCollection(testDB, collName); } } const pipeline = [{$changeStream: {showExpandedEvents: true}}]; runTest(() => test.startWatchingChanges({pipeline, collection: 1})); runTest(() => test.startWatchingChanges({pipeline, collection: collName}));