mongo/jstests/change_streams/show_raw_update_description.js

418 lines
14 KiB
JavaScript

/**
* Tests that change streams with the 'showRawUpdateDescription' option enabled will return update
* events with the 'rawUpdateDescription' field instead of the 'updateDescription' field, and tests
* that the 'showRawUpdateDescription' option has no effect on replacements or other types of
* events.
* @tags: [
* requires_fcv_60,
* uses_change_streams,
* ]
*/
import {arrayEq} from "jstests/aggregation/extras/utils.js";
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, "t1");
assertDropAndRecreateCollection(db, "t2");
assertDropAndRecreateCollection(db, "t1Copy");
assertDropAndRecreateCollection(db, "t2Copy");
assert.commandWorked(
db.t1.insert([
{_id: 3, a: 5, b: 1},
{_id: 4, a: 0, b: 1},
{_id: 5, a: 0, b: 1},
{_id: 6, a: 1, b: 1},
{_id: 7, a: 1, b: 1},
{_id: 8, a: 2, b: {c: 1}},
]),
);
const kGiantStr = "*".repeat(1024);
const kMediumStr = "*".repeat(128);
const kSmallStr = "*".repeat(32);
assert.commandWorked(
db.t2.insert({
_id: 100,
"a": 1,
"b": 2,
"arrayForSubdiff": [kGiantStr, {a: kMediumStr}, 1, 2, 3],
"arrayForReplacement": [0, 1, 2, 3],
"giantStr": kGiantStr,
}),
);
const cst = new ChangeStreamTest(db);
let cursor = cst.startWatchingChanges({
pipeline: [{$changeStream: {showRawUpdateDescription: true}}],
collection: db.t1,
});
//
// Test insert, replace, and delete operations and verify the corresponding change stream events
// are unaffected by the 'showRawUpdateDescription' option.
//
jsTestLog("Testing insert");
assert.commandWorked(db.t1.insert({_id: 1, a: 1}));
let expected = {
documentKey: {_id: 1},
fullDocument: {_id: 1, a: 1},
ns: {db: "test", coll: "t1"},
operationType: "insert",
};
cst.assertNextChangesEqual({cursor: cursor, expectedChanges: [expected]});
jsTestLog("Testing upsert");
assert.commandWorked(db.t1.update({_id: 2}, {_id: 2, a: 4}, {upsert: true}));
expected = {
documentKey: {_id: 2},
fullDocument: {_id: 2, a: 4},
ns: {db: "test", coll: "t1"},
operationType: "insert",
};
cst.assertNextChangesEqual({cursor: cursor, expectedChanges: [expected]});
jsTestLog("Testing replacement");
assert.commandWorked(db.t1.update({_id: 1}, {_id: 1, a: 3}));
expected = {
documentKey: {_id: 1},
fullDocument: {_id: 1, a: 3},
ns: {db: "test", coll: "t1"},
operationType: "replace",
};
cst.assertNextChangesEqual({cursor: cursor, expectedChanges: [expected]});
jsTestLog("Testing another replacement");
assert.commandWorked(db.t1.update({_id: 1}, {_id: 1, b: 3}));
expected = {
documentKey: {_id: 1},
fullDocument: {_id: 1, b: 3},
ns: {db: "test", coll: "t1"},
operationType: "replace",
};
cst.assertNextChangesEqual({cursor: cursor, expectedChanges: [expected]});
jsTestLog("Testing delete");
assert.commandWorked(db.t1.remove({_id: 1}));
expected = {
documentKey: {_id: 1},
ns: {db: "test", coll: "t1"},
operationType: "delete",
};
cst.assertNextChangesEqual({cursor: cursor, expectedChanges: [expected]});
jsTestLog("Testing delete with justOne:false");
assert.commandWorked(db.t1.remove({a: 1}, {justOne: false}));
expected = [
{
documentKey: {_id: 6},
ns: {db: "test", coll: "t1"},
operationType: "delete",
},
{
documentKey: {_id: 7},
ns: {db: "test", coll: "t1"},
operationType: "delete",
},
];
cst.assertNextChangesEqualUnordered({cursor: cursor, expectedChanges: expected});
//
// The remainder of the test-cases below exercise various update scenarios that produce
// 'rawUpdateDescription'.
//
function assertCollectionsAreIdentical(coll1, coll2) {
// Some passthrough suites use secondary read preference, so we use assert.soon to ensure that
// the updates have time to replicate to secondary nodes.
assert.soon(() => {
const values1 = coll1.find().toArray();
const values2 = coll2.find().toArray();
return arrayEq(values1, values2), () => "actual: " + tojson(values1) + " expected: " + tojson(values2);
});
}
function assertCanApplyRawUpdate(origColl, copyColl, events) {
if (!Array.isArray(events)) {
events = [events];
}
for (let event of events) {
assert.commandWorked(
copyColl.update(event.documentKey, [
{$_internalApplyOplogUpdate: {oplogUpdate: event.rawUpdateDescription}},
]),
);
}
assertCollectionsAreIdentical(origColl, copyColl);
}
assert.commandWorked(db.t1Copy.insert(db.t1.find().toArray()));
assertCollectionsAreIdentical(db.t1, db.t1Copy);
assert.commandWorked(db.t2Copy.insert(db.t2.find().toArray()));
assertCollectionsAreIdentical(db.t2, db.t2Copy);
//
// Test op-style updates.
//
jsTestLog("Testing op-style update with $inc");
assert.commandWorked(db.t1.update({_id: 3}, {$inc: {b: 2}}));
expected = {
documentKey: {_id: 3},
ns: {db: "test", coll: "t1"},
operationType: "update",
rawUpdateDescription: {"$v": NumberInt(2), diff: {u: {b: 3}}},
};
cst.assertNextChangesEqual({cursor: cursor, expectedChanges: [expected]});
assertCanApplyRawUpdate(db.t1, db.t1Copy, expected);
jsTestLog("Testing op-style update with $set and multi:true");
assert.commandWorked(db.t1.update({a: 0}, {$set: {b: 2}}, {multi: true}));
expected = [
{
documentKey: {_id: 4},
ns: {db: "test", coll: "t1"},
operationType: "update",
rawUpdateDescription: {"$v": NumberInt(2), diff: {u: {b: 2}}},
},
{
documentKey: {_id: 5},
ns: {db: "test", coll: "t1"},
operationType: "update",
rawUpdateDescription: {"$v": NumberInt(2), diff: {u: {b: 2}}},
},
];
cst.assertNextChangesEqualUnordered({cursor: cursor, expectedChanges: expected});
assertCanApplyRawUpdate(db.t1, db.t1Copy, expected);
jsTestLog("Testing op-style update with $unset");
assert.commandWorked(db.t1.update({_id: 3}, {$unset: {b: ""}}));
expected = {
documentKey: {_id: 3},
ns: {db: "test", coll: "t1"},
operationType: "update",
rawUpdateDescription: {"$v": NumberInt(2), diff: {d: {b: false}}},
};
cst.assertNextChangesEqual({cursor: cursor, expectedChanges: [expected]});
assertCanApplyRawUpdate(db.t1, db.t1Copy, expected);
jsTestLog("Testing op-style update with $set on nested field");
assert.commandWorked(db.t1.update({_id: 8}, {$set: {"b.d": 2}}));
expected = {
documentKey: {_id: 8},
ns: {db: "test", coll: "t1"},
operationType: "update",
rawUpdateDescription: {"$v": NumberInt(2), diff: {sb: {i: {d: 2}}}},
};
cst.assertNextChangesEqual({cursor: cursor, expectedChanges: [expected]});
assertCanApplyRawUpdate(db.t1, db.t1Copy, expected);
//
// Test pipeline-style updates.
//
cursor = cst.startWatchingChanges({pipeline: [{$changeStream: {showRawUpdateDescription: true}}], collection: db.t2});
// Also test a second change stream with the 'fullDocument' option enabled.
const fullDocCursor = cst.startWatchingChanges({
pipeline: [{$changeStream: {showRawUpdateDescription: true, fullDocument: "updateLookup"}}],
collection: db.t2,
});
jsTestLog("Testing pipeline-style update with $set");
assert.commandWorked(
db.t2.update({_id: 100}, [
{
$set: {
a: 2,
arrayForSubdiff: [kGiantStr, {a: kMediumStr, b: 3}],
arrayForReplacement: [0],
c: 3,
},
},
]),
);
expected = {
documentKey: {_id: 100},
fullDocument: {
_id: 100,
a: 2,
b: 2,
arrayForSubdiff: [kGiantStr, {a: kMediumStr, b: 3}],
arrayForReplacement: [0],
giantStr: kGiantStr,
c: 3,
},
ns: {db: "test", coll: "t2"},
operationType: "update",
rawUpdateDescription: {
"$v": NumberInt(2),
diff: {
u: {a: 2, arrayForReplacement: [0]},
i: {c: 3},
sarrayForSubdiff: {a: true, l: NumberInt(2), s1: {i: {b: 3}}},
},
},
};
cst.assertNextChangesEqual({cursor: fullDocCursor, expectedChanges: [expected]});
delete expected.fullDocument;
cst.assertNextChangesEqual({cursor: cursor, expectedChanges: [expected]});
assertCanApplyRawUpdate(db.t2, db.t2Copy, expected);
jsTestLog("Testing pipeline-style update with $unset");
assert.commandWorked(db.t2.update({_id: 100}, [{$unset: ["a"]}]));
expected = {
documentKey: {_id: 100},
fullDocument: {
_id: 100,
b: 2,
arrayForSubdiff: [kGiantStr, {a: kMediumStr, b: 3}],
arrayForReplacement: [0],
giantStr: kGiantStr,
c: 3,
},
ns: {db: "test", coll: "t2"},
operationType: "update",
rawUpdateDescription: {"$v": NumberInt(2), diff: {d: {a: false}}},
};
cst.assertNextChangesEqual({cursor: fullDocCursor, expectedChanges: [expected]});
delete expected.fullDocument;
cst.assertNextChangesEqual({cursor: cursor, expectedChanges: [expected]});
assertCanApplyRawUpdate(db.t2, db.t2Copy, expected);
jsTestLog("Testing pipeline-style update with $replaceRoot");
assert.commandWorked(db.t2.update({_id: 100}, [{$replaceRoot: {newRoot: {_id: 100, "giantStr": kGiantStr}}}]));
expected = {
documentKey: {_id: 100},
fullDocument: {_id: 100, giantStr: kGiantStr},
ns: {db: "test", coll: "t2"},
operationType: "update",
rawUpdateDescription: {
"$v": NumberInt(2),
diff: {d: {c: false, arrayForReplacement: false, arrayForSubdiff: false, b: false}},
},
};
cst.assertNextChangesEqual({cursor: fullDocCursor, expectedChanges: [expected]});
delete expected.fullDocument;
cst.assertNextChangesEqual({cursor: cursor, expectedChanges: [expected]});
assertCanApplyRawUpdate(db.t2, db.t2Copy, expected);
jsTestLog("Testing pipeline-style update with a complex pipeline");
assert.commandWorked(
db.t2.update({_id: 100}, [
{
$replaceRoot: {
// Also constructing a new doc for later test.
newRoot: {
_id: 100,
giantStr: kGiantStr,
arr: [{x: 1, y: kSmallStr}, kMediumStr],
arr_a: [1, kMediumStr],
arr_b: [[1, kSmallStr], kMediumStr],
arr_c: [[kSmallStr, 1, 2, 3], kMediumStr],
obj: {x: {a: 1, b: 1, c: [kMediumStr, 1, 2, 3], str: kMediumStr}},
},
},
},
{$addFields: {a: "updated", b: 2, doc: {a: {0: "foo"}}}},
{
$project: {
a: true,
giantStr: true,
doc: true,
arr: true,
arr_a: true,
arr_b: true,
arr_c: true,
obj: true,
},
},
]),
);
expected = {
documentKey: {_id: 100},
fullDocument: {
_id: 100,
giantStr: kGiantStr,
arr: [{x: 1, y: kSmallStr}, kMediumStr],
arr_a: [1, kMediumStr],
arr_b: [[1, kSmallStr], kMediumStr],
arr_c: [[kSmallStr, 1, 2, 3], kMediumStr],
obj: {x: {a: 1, b: 1, c: [kMediumStr, 1, 2, 3], str: kMediumStr}},
a: "updated",
doc: {a: {0: "foo"}},
},
ns: {db: "test", coll: "t2"},
operationType: "update",
rawUpdateDescription: {
"$v": NumberInt(2),
diff: {
i: {
arr: [{x: 1, y: kSmallStr}, kMediumStr],
arr_a: [1, kMediumStr],
arr_b: [[1, kSmallStr], kMediumStr],
arr_c: [[kSmallStr, 1, 2, 3], kMediumStr],
obj: {x: {a: 1, b: 1, c: [kMediumStr, 1, 2, 3], str: kMediumStr}},
a: "updated",
doc: {a: {0: "foo"}},
},
},
},
};
cst.assertNextChangesEqual({cursor: fullDocCursor, expectedChanges: [expected]});
delete expected.fullDocument;
cst.assertNextChangesEqual({cursor: cursor, expectedChanges: [expected]});
assertCanApplyRawUpdate(db.t2, db.t2Copy, expected);
jsTestLog("Testing pipeline-style update with modifications to nested elements");
assert.commandWorked(
db.t2.update({_id: 100}, [
{
$replaceRoot: {
newRoot: {
_id: 100,
giantStr: kGiantStr,
arr: [{y: kSmallStr}, kMediumStr],
arr_a: [2, kMediumStr],
arr_b: [[2, kSmallStr], kMediumStr],
arr_c: [[kSmallStr], kMediumStr],
obj: {x: {b: 2, c: [kMediumStr], str: kMediumStr}},
},
},
},
]),
);
expected = {
documentKey: {_id: 100},
fullDocument: {
_id: 100,
giantStr: kGiantStr,
arr: [{y: kSmallStr}, kMediumStr],
arr_a: [2, kMediumStr],
arr_b: [[2, kSmallStr], kMediumStr],
arr_c: [[kSmallStr], kMediumStr],
obj: {x: {b: 2, c: [kMediumStr], str: kMediumStr}},
},
ns: {db: "test", coll: "t2"},
operationType: "update",
rawUpdateDescription: {
"$v": NumberInt(2),
diff: {
d: {a: false, doc: false},
sarr: {a: true, s0: {d: {x: false}}},
sarr_a: {a: true, u0: 2},
sarr_b: {a: true, s0: {a: true, u0: 2}},
sarr_c: {a: true, s0: {a: true, l: NumberInt(1)}},
sobj: {sx: {d: {a: false}, u: {b: 2}, sc: {a: true, l: NumberInt(1)}}},
},
},
};
cst.assertNextChangesEqual({cursor: fullDocCursor, expectedChanges: [expected]});
delete expected.fullDocument;
cst.assertNextChangesEqual({cursor: cursor, expectedChanges: [expected]});
assertCanApplyRawUpdate(db.t2, db.t2Copy, expected);
cst.cleanUp();