summaryrefslogtreecommitdiff
path: root/jstests/noPassthrough/change_streams_update_lookup_collation.js
blob: dff1cf7c07ec114fc02085c28aa0f8231002f60f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
// Tests that the update lookup of an unsharded change stream will use the collection-default
// collation, regardless of the collation on the change stream.
//
// @tags: [
//   requires_majority_read_concern,
//   uses_change_streams,
// ]
(function() {
"use strict";

// Skip this test if running with --nojournal and WiredTiger.
if (jsTest.options().noJournal &&
    (!jsTest.options().storageEngine || jsTest.options().storageEngine === "wiredTiger")) {
    print("Skipping test because running WiredTiger without journaling isn't a valid" +
          " replica set configuration");
    return;
}

const rst = new ReplSetTest({nodes: 1});
rst.startSet();
rst.initiate();

const db = rst.getPrimary().getDB("test");
const coll = db[jsTestName()];
const caseInsensitive = {
    locale: "en_US",
    strength: 2
};
assert.commandWorked(db.createCollection(coll.getName(), {collation: caseInsensitive}));

// Insert some documents that have similar _ids, but differ by case and diacritics. These _ids
// would all match the collation on the strengthOneChangeStream, but should not be confused
// during the update lookup using the strength 2 collection default collation.
assert.commandWorked(coll.insert({_id: "abc", x: "abc"}));
assert.commandWorked(coll.insert({_id: "abç", x: "ABC"}));
assert.commandWorked(coll.insert({_id: "åbC", x: "AbÇ"}));

const changeStreamDefaultCollation = coll.aggregate(
    [{$changeStream: {fullDocument: "updateLookup"}}, {$match: {"fullDocument.x": "abc"}}],
    {collation: caseInsensitive});

// Strength one will consider "ç" equal to "c" and "C".
const strengthOneCollation = {
    locale: "en_US",
    strength: 1
};
const strengthOneChangeStream = coll.aggregate(
    [{$changeStream: {fullDocument: "updateLookup"}}, {$match: {"fullDocument.x": "abc"}}],
    {collation: strengthOneCollation});

assert.commandWorked(coll.update({_id: "abc"}, {$set: {updated: true}}));

// Track the number of _id index usages to prove that the update lookup uses the _id index (and
// therefore is using the correct collation for the lookup).
function numIdIndexUsages() {
    return coll.aggregate([{$indexStats: {}}, {$match: {name: "_id_"}}]).toArray()[0].accesses.ops;
}
const idIndexUsagesBeforeIteration = numIdIndexUsages();

// Both cursors should produce a document describing this update, since the "x" value of the
// first document will match both filters.
assert.soon(() => changeStreamDefaultCollation.hasNext());
assert.docEq(changeStreamDefaultCollation.next().fullDocument,
             {_id: "abc", x: "abc", updated: true});
assert.eq(numIdIndexUsages(), idIndexUsagesBeforeIteration + 1);
assert.docEq(strengthOneChangeStream.next().fullDocument, {_id: "abc", x: "abc", updated: true});
assert.eq(numIdIndexUsages(), idIndexUsagesBeforeIteration + 2);

assert.commandWorked(coll.update({_id: "abç"}, {$set: {updated: true}}));
assert.eq(numIdIndexUsages(), idIndexUsagesBeforeIteration + 3);

// Again, both cursors should produce a document describing this update.
assert.soon(() => changeStreamDefaultCollation.hasNext());
assert.docEq(changeStreamDefaultCollation.next().fullDocument,
             {_id: "abç", x: "ABC", updated: true});
assert.eq(numIdIndexUsages(), idIndexUsagesBeforeIteration + 4);
assert.docEq(strengthOneChangeStream.next().fullDocument, {_id: "abç", x: "ABC", updated: true});
assert.eq(numIdIndexUsages(), idIndexUsagesBeforeIteration + 5);

assert.commandWorked(coll.update({_id: "åbC"}, {$set: {updated: true}}));
assert.eq(numIdIndexUsages(), idIndexUsagesBeforeIteration + 6);

// Both $changeStream stages will see this update and both will look up the full document using
// the foreign collection's default collation. However, the changeStreamDefaultCollation's
// subsequent $match stage will reject the document because it does not consider "AbÇ" equal to
// "abc". Only the strengthOneChangeStream will output the final document.
assert.soon(() => strengthOneChangeStream.hasNext());
assert.docEq(strengthOneChangeStream.next().fullDocument, {_id: "åbC", x: "AbÇ", updated: true});
assert.eq(numIdIndexUsages(), idIndexUsagesBeforeIteration + 7);
assert(!changeStreamDefaultCollation.hasNext());
assert.eq(numIdIndexUsages(), idIndexUsagesBeforeIteration + 8);

changeStreamDefaultCollation.close();
strengthOneChangeStream.close();
rst.stopSet();
}());