mirror of https://github.com/mongodb/mongo
418 lines
14 KiB
JavaScript
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();
|