Skip to content

Commit e54b26a

Browse files
jsteemannMongoDB Bot
authored andcommitted
SERVER-100345 Make match expressions with BSON builders (#32078)
GitOrigin-RevId: 9867ff03994cf7ab3eec41c4d4faa62d748e622b
1 parent 6451d97 commit e54b26a

File tree

3 files changed

+208
-3
lines changed

3 files changed

+208
-3
lines changed

etc/backports_required_for_multiversion_tests.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
#
2222
last-continuous:
2323
all:
24+
- test_file: jstests/change_streams/change_streams_dynamic_match_expressions.js
25+
ticket: SERVER-100345
2426
- test_file: jstests/aggregation/bugs/lookup_using_hashed_index.js
2527
ticket: SERVER-92668
2628
- test_file: jstests/replsets/dbcheck_skip_applying_batch_on_secondary_parameter.js
@@ -576,6 +578,8 @@ last-continuous:
576578
suites: null
577579
last-lts:
578580
all:
581+
- test_file: jstests/change_streams/change_streams_dynamic_match_expressions.js
582+
ticket: SERVER-100345
579583
- test_file: jstests/aggregation/bugs/lookup_using_hashed_index.js
580584
ticket: SERVER-92668
581585
- test_file: jstests/replsets/dbcheck_skip_applying_batch_on_secondary_parameter.js
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
// Tests rewrites for $match expression fields in change streams in combination with special
2+
// characters inside the field path.
3+
4+
(function() {
5+
"use strict";
6+
7+
load('jstests/libs/change_stream_util.js');
8+
load("jstests/libs/collection_drop_recreate.js");
9+
10+
// Collection name with special characters inside it.
11+
const kCollName = "booooo '\"bar baz]]]}}}}{{{{{{{{{{{{{{{{{{{{{{{{{";
12+
13+
function runTest(inserts, pipelineStages, expectedChanges) {
14+
assertDropAndRecreateCollection(db, kCollName);
15+
16+
const cst = new ChangeStreamTest(db);
17+
const pipeline = [{$changeStream: {}}].concat(pipelineStages);
18+
19+
jsTestLog("Running test with pipeline: " + tojson(pipeline));
20+
let cursor = cst.startWatchingChanges({pipeline, collection: kCollName});
21+
assert.eq(0, cursor.firstBatch.length, "Cursor had changes: " + tojson(cursor));
22+
23+
assert.commandWorked(db[kCollName].insert(inserts));
24+
cst.assertNextChangesEqual({cursor, expectedChanges});
25+
cst.cleanUp();
26+
}
27+
28+
// Pipeline without any match expressions.
29+
runTest(
30+
[
31+
{_id: "one", value: 1},
32+
{_id: {sub: "two"}, value: 2},
33+
],
34+
[],
35+
[
36+
{
37+
operationType: "insert",
38+
fullDocument: {_id: "one", value: 1},
39+
ns: {db: "test", coll: kCollName},
40+
documentKey: {_id: "one"},
41+
},
42+
{
43+
operationType: "insert",
44+
fullDocument: {_id: {sub: "two"}, value: 2},
45+
ns: {db: "test", coll: kCollName},
46+
documentKey: {_id: {sub: "two"}},
47+
},
48+
]);
49+
50+
// Value with characters in it that can a have special meaning inside strings.
51+
const fieldValue = "fx {{{\"'x\\";
52+
53+
// Pipeline with literal match expression on _id with special value.
54+
runTest(
55+
[
56+
{_id: fieldValue},
57+
],
58+
[
59+
{$match: {"fullDocument._id": fieldValue}},
60+
],
61+
[
62+
{
63+
operationType: "insert",
64+
fullDocument: {_id: fieldValue},
65+
ns: {db: "test", coll: kCollName},
66+
documentKey: {_id: fieldValue},
67+
},
68+
]);
69+
70+
// Field name with characters in it that can a have special meaning inside strings.
71+
const subField = "fx {{{\"'x";
72+
73+
// Pipeline with $expr match expression on $fullDocument _id with subfield that has a special name.
74+
runTest(
75+
[
76+
{_id: {[subField]: "x"}},
77+
],
78+
[
79+
{$match: {["fullDocument._id." + subField]: "x"}},
80+
],
81+
[
82+
{
83+
operationType: "insert",
84+
fullDocument: {_id: {[subField]: "x"}},
85+
ns: {db: "test", coll: kCollName},
86+
documentKey: {_id: {[subField]: "x"}},
87+
},
88+
]);
89+
90+
// Pipeline with $expr match expression on $fullDocument _id with subfield that has a special name.
91+
runTest(
92+
[
93+
{_id: {[subField]: "x"}},
94+
],
95+
[
96+
{$match: {$expr: {$eq: ["$fullDocument._id", {[subField]: "x"}]}}},
97+
],
98+
[
99+
{
100+
operationType: "insert",
101+
fullDocument: {_id: {[subField]: "x"}},
102+
ns: {db: "test", coll: kCollName},
103+
documentKey: {_id: {[subField]: "x"}},
104+
},
105+
]);
106+
107+
// Pipeline with $expr match expression on $fullDocument _id with subfield that has a special name.
108+
runTest(
109+
[
110+
{_id: {[subField]: "x"}},
111+
],
112+
[
113+
{$match: {$expr: {$eq: ["$fullDocument._id." + subField, "x"]}}},
114+
],
115+
[
116+
{
117+
operationType: "insert",
118+
fullDocument: {_id: {[subField]: "x"}},
119+
ns: {db: "test", coll: kCollName},
120+
documentKey: {_id: {[subField]: "x"}},
121+
},
122+
]);
123+
124+
// Pipeline with $expr match expression on subfield that has a special name.
125+
runTest(
126+
[
127+
{_id: "z", [subField]: "y"},
128+
],
129+
[
130+
{$match: {$expr: {$eq: ["$fullDocument." + subField, "y"]}}},
131+
],
132+
[
133+
{
134+
operationType: "insert",
135+
fullDocument: {_id: "z", [subField]: "y"},
136+
ns: {db: "test", coll: kCollName},
137+
documentKey: {_id: "z"},
138+
},
139+
]);
140+
141+
// Pipeline with $expr match expression on $documentKey subfield that has a special name.
142+
runTest(
143+
[
144+
{_id: {[subField]: "x"}},
145+
],
146+
[
147+
{$match: {$expr: {$not: {$eq: ["$documentKey." + subField, "x"]}}}},
148+
],
149+
[
150+
{
151+
operationType: "insert",
152+
fullDocument: {_id: {[subField]: "x"}},
153+
ns: {db: "test", coll: kCollName},
154+
documentKey: {_id: {[subField]: "x"}},
155+
},
156+
]);
157+
158+
// Pipeline with $expr match expression on $documentKey subfield that has a special name.
159+
runTest(
160+
[
161+
{_id: {[subField]: "x"}},
162+
],
163+
[
164+
{$match: {$expr: {$lte: ["$documentKey." + subField, "a"]}}},
165+
],
166+
[
167+
{
168+
operationType: "insert",
169+
fullDocument: {_id: {[subField]: "x"}},
170+
ns: {db: "test", coll: kCollName},
171+
documentKey: {_id: {[subField]: "x"}},
172+
},
173+
]);
174+
175+
// Pipeline with $expr match expression on $documentKey subfield opening 1K braces.
176+
runTest(
177+
[
178+
{_id: {[subField]: "x"}},
179+
],
180+
[
181+
{
182+
$match: {
183+
$expr: {
184+
$not: {
185+
$eq: [
186+
"$documentKey.abc', 'test': " +
187+
"{a: ".repeat(2048),
188+
"x"
189+
]
190+
}
191+
}
192+
}
193+
},
194+
],
195+
[]);
196+
})();

src/mongo/db/pipeline/change_stream_rewrite_helpers.cpp

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -307,14 +307,19 @@ boost::intrusive_ptr<Expression> exprRewriteDocumentKey(
307307
auto deletePath = cloneWithSubstitution(expr, {{"documentKey", "o"}})
308308
->getFieldPathWithoutCurrentPrefix()
309309
.fullPathWithPrefix();
310-
opCases.push_back(fromjson("{case: {$eq: ['$op', 'd']}, then: '" + deletePath + "'}"));
310+
auto deleteFilter = BSON("case" << BSON("$eq" << BSON_ARRAY("$op"
311+
<< "d"))
312+
<< "then" << deletePath);
313+
opCases.push_back(std::move(deleteFilter));
311314

312315
// Cases for 'insert', 'update' and 'replace'.
313316
auto insertUpdateAndReplacePath = cloneWithSubstitution(expr, {{"documentKey", "o2"}})
314317
->getFieldPathWithoutCurrentPrefix()
315318
.fullPathWithPrefix();
316-
opCases.push_back(
317-
fromjson("{case: {$in: ['$op', ['i', 'u']]}, then: '" + insertUpdateAndReplacePath + "'}"));
319+
auto insertFilter = BSON("case" << BSON("$in" << BSON_ARRAY("$op" << BSON_ARRAY("i"
320+
<< "u")))
321+
<< "then" << insertUpdateAndReplacePath);
322+
opCases.push_back(std::move(insertFilter));
318323

319324
// The default case, if nothing matches.
320325
auto defaultCase = ExpressionConstant::create(expCtx.get(), Value())->serialize();

0 commit comments

Comments
 (0)