/** * Test change stream 'updateDescription' with 'showExpandedEvents'. */ import {assertDropAndRecreateCollection} from "jstests/libs/collection_drop_recreate.js"; import {ChangeStreamTest} from "jstests/libs/query/change_stream_util.js"; // Drop and recreate the collections to be used in this set of tests. assertDropAndRecreateCollection(db, "coll"); const cst = new ChangeStreamTest(db); const kLargeStr = "*".repeat(128); assert.commandWorked( db.coll.insert({ _id: 100, "topLevelArray": [{subArray: [0, [0, [{bottomArray: [1, 2, kLargeStr]}]], 2, 3, kLargeStr]}], "arrayForReplacement": [0, 1, 2, 3], "arrayForResize": [kLargeStr, 1], obj: { "sub.obj": {"d.o.t.t.e.d.a.r.r.a.y..": [[{a: {"b.c": 1, field: kLargeStr}}, "truncated"]]}, }, "d.o.t.t.e.d.o.b.j.": {"sub.obj": {"b.c": 2}}, "objectWithNumericField": {"0": {"1": "numeric", field: kLargeStr}}, "arrayWithNumericField": [[{"0": "numeric", a: {"b.c": 1}, field: kLargeStr}]], "arrayWithDotted.AndNumericFields": [[{"0": [{"1.2": {"a.b": null, c: kLargeStr}}]}]], }), ); const changeStreamCursor = cst.startWatchingChanges({ pipeline: [{$changeStream: {showExpandedEvents: true}}], collection: db.coll, }); // Test that a path which only contains non-dotted fields and array indices is not reported under // 'disambiguatedPaths'. assert.commandWorked( db.coll.update( {_id: 100}, { $set: {"a": 2, "topLevelArray.0.subArray.1.1.0.bottomArray.2": 3, "arrayForReplacement": [0]}, }, ), ); let expected = { documentKey: {_id: 100}, ns: {db: "test", coll: "coll"}, operationType: "update", updateDescription: { updatedFields: {"arrayForReplacement": [0], "a": 2, "topLevelArray.0.subArray.1.1.0.bottomArray.2": 3}, removedFields: [], truncatedArrays: [], disambiguatedPaths: {}, }, }; cst.assertNextChangesEqual({cursor: changeStreamCursor, expectedChanges: [expected]}); // Tests that an update modifying a non-array numeric field name is reported as a string rather than // as an integer under 'disambiguatedPaths'. Array indexes are reported as integers. assert.commandWorked(db.coll.update({_id: 100}, {$set: {"arrayWithNumericField.0.0.1": {"b.c": 1}}})); expected = { documentKey: {_id: 100}, ns: {db: "test", coll: "coll"}, operationType: "update", updateDescription: { updatedFields: {"arrayWithNumericField.0.0.1": {"b.c": 1}}, removedFields: [], truncatedArrays: [], disambiguatedPaths: {"arrayWithNumericField.0.0.1": ["arrayWithNumericField", 0, 0, "1"]}, }, }; cst.assertNextChangesEqual({cursor: changeStreamCursor, expectedChanges: [expected]}); // Tests that an update modifying a non-array numeric field name is reported when no array indices // or dotted fields are present. assert.commandWorked(db.coll.update({_id: 100}, {$set: {"objectWithNumericField.0.1": "updated"}})); expected = { documentKey: {_id: 100}, ns: {db: "test", coll: "coll"}, operationType: "update", updateDescription: { updatedFields: {"objectWithNumericField.0.1": "updated"}, removedFields: [], truncatedArrays: [], disambiguatedPaths: {"objectWithNumericField.0.1": ["objectWithNumericField", "0", "1"]}, }, }; cst.assertNextChangesEqual({cursor: changeStreamCursor, expectedChanges: [expected]}); // Tests that an update with $unset array does not report the array under 'disambiguatedPaths'. assert.commandWorked(db.coll.update({_id: 100}, [{$unset: ["arrayForReplacement"]}])); expected = { documentKey: {_id: 100}, ns: {db: "test", coll: "coll"}, operationType: "update", updateDescription: { updatedFields: {}, removedFields: ["arrayForReplacement"], truncatedArrays: [], disambiguatedPaths: {}, }, }; cst.assertNextChangesEqual({cursor: changeStreamCursor, expectedChanges: [expected]}); // Tests that an update with 'truncatedArrays' does not report the array under 'disambiguatedPaths'. assert.commandWorked( db.coll.update({_id: 100}, [ {$replaceWith: {$setField: {field: "arrayForResize", input: "$$ROOT", value: [kLargeStr]}}}, ]), ); expected = { documentKey: {_id: 100}, ns: {db: "test", coll: "coll"}, operationType: "update", updateDescription: { updatedFields: {}, removedFields: [], truncatedArrays: [{field: "arrayForResize", newSize: 1}], disambiguatedPaths: {}, }, }; cst.assertNextChangesEqual({cursor: changeStreamCursor, expectedChanges: [expected]}); // Verify that top-level dotted fields are reported under 'disambiguatedPaths'. assert.commandWorked( db.coll.update({_id: 100}, [ { $replaceWith: {$setField: {field: "d.o.t.t.e.d.o.b.j.", input: "$$ROOT", value: {"subObj": 1}}}, }, {$replaceWith: {$setField: {field: "new.Field.", input: "$$ROOT", value: 1}}}, ]), ); expected = { documentKey: {_id: 100}, ns: {db: "test", coll: "coll"}, operationType: "update", updateDescription: { updatedFields: {"d.o.t.t.e.d.o.b.j.": {subObj: 1}, "new.Field.": 1}, removedFields: [], truncatedArrays: [], disambiguatedPaths: {"d.o.t.t.e.d.o.b.j.": ["d.o.t.t.e.d.o.b.j."], "new.Field.": ["new.Field."]}, }, }; cst.assertNextChangesEqual({cursor: changeStreamCursor, expectedChanges: [expected]}); // Test that a combination of dotted fields and array indices are reported in 'disambiguatedPaths'. assert.commandWorked( db.coll.update({_id: 100}, [ { $set: { obj: { $setField: { field: "sub.obj", input: "$obj", value: { $literal: {"d.o.t.t.e.d.a.r.r.a.y..": [[{a: {"b.c": 2, field: kLargeStr}}]]}, }, }, }, }, }, ]), ); expected = { documentKey: {_id: 100}, ns: {db: "test", coll: "coll"}, operationType: "update", updateDescription: { updatedFields: {"obj.sub.obj.d.o.t.t.e.d.a.r.r.a.y...0.0.a.b.c": 2}, removedFields: [], truncatedArrays: [{field: "obj.sub.obj.d.o.t.t.e.d.a.r.r.a.y...0", newSize: 1}], disambiguatedPaths: { "obj.sub.obj.d.o.t.t.e.d.a.r.r.a.y...0": ["obj", "sub.obj", "d.o.t.t.e.d.a.r.r.a.y..", 0], "obj.sub.obj.d.o.t.t.e.d.a.r.r.a.y...0.0.a.b.c": [ "obj", "sub.obj", "d.o.t.t.e.d.a.r.r.a.y..", 0, 0, "a", "b.c", ], }, }, }; cst.assertNextChangesEqual({cursor: changeStreamCursor, expectedChanges: [expected]}); // Test that an update which modifies a path containing dotted, numeric and array index fields // distinguishes all three in 'disambiguatedPaths'. assert.commandWorked( db.coll.update({_id: 100}, [ { $replaceWith: { $setField: { field: "arrayWithDotted.AndNumericFields", input: "$$ROOT", value: {$literal: [[{"0": [{"1.2": {"a.b": true, c: kLargeStr}}]}]]}, }, }, }, ]), ); expected = { documentKey: {_id: 100}, ns: {db: "test", coll: "coll"}, operationType: "update", updateDescription: { updatedFields: {"arrayWithDotted.AndNumericFields.0.0.0.0.1.2.a.b": true}, removedFields: [], truncatedArrays: [], disambiguatedPaths: { "arrayWithDotted.AndNumericFields.0.0.0.0.1.2.a.b": [ "arrayWithDotted.AndNumericFields", 0, 0, "0", 0, "1.2", "a.b", ], }, }, }; cst.assertNextChangesEqual({cursor: changeStreamCursor, expectedChanges: [expected]}); cst.cleanUp();