1
2 /**
3 * Copyright (C) 2018-present MongoDB, Inc.
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the Server Side Public License, version 1,
7 * as published by MongoDB, Inc.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * Server Side Public License for more details.
13 *
14 * You should have received a copy of the Server Side Public License
15 * along with this program. If not, see
16 * <http://www.mongodb.com/licensing/server-side-public-license>.
17 *
18 * As a special exception, the copyright holders give permission to link the
19 * code of portions of this program with the OpenSSL library under certain
20 * conditions as described in each individual source file and distribute
21 * linked combinations including the program with the OpenSSL library. You
22 * must comply with the Server Side Public License in all respects for
23 * all of the code used other than as permitted herein. If you modify file(s)
24 * with this exception, you may extend this exception to your version of the
25 * file(s), but you are not obligated to do so. If you do not wish to do so,
26 * delete this exception statement from your version. If you delete this
27 * exception statement from all source files in the program, then also delete
28 * it in the license file.
29 */
30
31 #include "mongo/db/ops/modifier_compare.h"
32
33 #include <cstdint>
34
35 #include "mongo/base/string_data.h"
36 #include "mongo/bson/mutable/document.h"
37 #include "mongo/bson/mutable/mutable_bson_test_utils.h"
38 #include "mongo/db/jsobj.h"
39 #include "mongo/db/json.h"
40 #include "mongo/db/pipeline/expression_context_for_test.h"
41 #include "mongo/db/query/collation/collator_interface_mock.h"
42 #include "mongo/db/update/log_builder.h"
43 #include "mongo/unittest/unittest.h"
44
45 namespace {
46
47 using mongo::BSONObj;
48 using mongo::CollatorInterfaceMock;
49 using mongo::ExpressionContextForTest;
50 using mongo::LogBuilder;
51 using mongo::ModifierCompare;
52 using mongo::ModifierInterface;
53 using mongo::Status;
54 using mongo::StringData;
55 using mongo::fromjson;
56 using mongo::mutablebson::ConstElement;
57 using mongo::mutablebson::Document;
58 using mongo::mutablebson::Element;
59
60 const char kModNameMin[] = "$min";
61 const char kModNameMax[] = "$max";
62
63 /** Helper to build and manipulate a $min/max mod. */
64 class Mod {
65 public:
Mod()66 Mod() : _mod() {}
67
Mod(BSONObj modObj,ModifierInterface::Options options=ModifierInterface::Options::normal (new ExpressionContextForTest ()))68 explicit Mod(BSONObj modObj,
69 ModifierInterface::Options options =
70 ModifierInterface::Options::normal(new ExpressionContextForTest()))
71 : _modObj(modObj),
72 _mod((modObj.firstElement().fieldNameStringData() == "$min") ? ModifierCompare::MIN
73 : ModifierCompare::MAX) {
74 StringData modName = modObj.firstElement().fieldName();
75 ASSERT_OK(_mod.init(modObj[modName].embeddedObject().firstElement(), options));
76 }
77
prepare(Element root,StringData matchedField,ModifierInterface::ExecInfo * execInfo)78 Status prepare(Element root, StringData matchedField, ModifierInterface::ExecInfo* execInfo) {
79 return _mod.prepare(root, matchedField, execInfo);
80 }
81
apply() const82 Status apply() const {
83 return _mod.apply();
84 }
85
log(LogBuilder * logBuilder) const86 Status log(LogBuilder* logBuilder) const {
87 return _mod.log(logBuilder);
88 }
89
mod()90 ModifierCompare& mod() {
91 return _mod;
92 }
93
94 private:
95 BSONObj _modObj;
96 ModifierCompare _mod;
97 };
98
TEST(Init,ValidValues)99 TEST(Init, ValidValues) {
100 BSONObj modObj;
101 ModifierCompare mod;
102 boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
103
104 modObj = fromjson("{ $min : { a : 2 } }");
105 ASSERT_OK(mod.init(modObj[kModNameMin].embeddedObject().firstElement(),
106 ModifierInterface::Options::normal(expCtx)));
107
108 modObj = fromjson("{ $max : { a : 1 } }");
109 ASSERT_OK(mod.init(modObj[kModNameMax].embeddedObject().firstElement(),
110 ModifierInterface::Options::normal(expCtx)));
111
112 modObj = fromjson("{ $min : { a : {$date : 0 } } }");
113 ASSERT_OK(mod.init(modObj[kModNameMin].embeddedObject().firstElement(),
114 ModifierInterface::Options::normal(expCtx)));
115 }
116
TEST(ExistingNumber,MaxSameNumber)117 TEST(ExistingNumber, MaxSameNumber) {
118 Document doc(fromjson("{a: 1 }"));
119 Mod mod(fromjson("{$max: {a: 1} }"));
120
121 ModifierInterface::ExecInfo execInfo;
122 ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
123 ASSERT_TRUE(execInfo.noOp);
124 }
125
TEST(ExistingNumber,MinSameNumber)126 TEST(ExistingNumber, MinSameNumber) {
127 Document doc(fromjson("{a: 1 }"));
128 Mod mod(fromjson("{$min: {a: 1} }"));
129
130 ModifierInterface::ExecInfo execInfo;
131 ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
132 ASSERT_TRUE(execInfo.noOp);
133 }
134
TEST(ExistingNumber,MaxNumberIsLess)135 TEST(ExistingNumber, MaxNumberIsLess) {
136 Document doc(fromjson("{a: 1 }"));
137 Mod mod(fromjson("{$max: {a: 0} }"));
138
139 ModifierInterface::ExecInfo execInfo;
140 ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
141 ASSERT_TRUE(execInfo.noOp);
142 }
143
TEST(ExistingNumber,MinNumberIsMore)144 TEST(ExistingNumber, MinNumberIsMore) {
145 Document doc(fromjson("{a: 1 }"));
146 Mod mod(fromjson("{$min: {a: 2} }"));
147
148 ModifierInterface::ExecInfo execInfo;
149 ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
150 ASSERT_TRUE(execInfo.noOp);
151 }
152
TEST(ExistingDouble,MaxSameValInt)153 TEST(ExistingDouble, MaxSameValInt) {
154 Document doc(fromjson("{a: 1.0 }"));
155 Mod mod(BSON("$max" << BSON("a" << 1LL)));
156
157 ModifierInterface::ExecInfo execInfo;
158 ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
159 ASSERT_TRUE(execInfo.noOp);
160 }
161
TEST(ExistingDoubleZero,MaxSameValIntZero)162 TEST(ExistingDoubleZero, MaxSameValIntZero) {
163 Document doc(fromjson("{a: 0.0 }"));
164 Mod mod(BSON("$max" << BSON("a" << 0LL)));
165
166 ModifierInterface::ExecInfo execInfo;
167 ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
168 ASSERT_TRUE(execInfo.noOp);
169 }
170
TEST(ExistingDoubleZero,MinSameValIntZero)171 TEST(ExistingDoubleZero, MinSameValIntZero) {
172 Document doc(fromjson("{a: 0.0 }"));
173 Mod mod(BSON("$min" << BSON("a" << 0LL)));
174
175 ModifierInterface::ExecInfo execInfo;
176 ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
177 ASSERT_TRUE(execInfo.noOp);
178 }
179
TEST(MissingField,MinNumber)180 TEST(MissingField, MinNumber) {
181 Document doc(fromjson("{}"));
182 Mod mod(fromjson("{$min: {a: 0} }"));
183
184 ModifierInterface::ExecInfo execInfo;
185 ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
186 ASSERT_FALSE(execInfo.noOp);
187 ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
188
189 ASSERT_OK(mod.apply());
190 ASSERT_EQUALS(fromjson("{a : 0}"), doc);
191 ASSERT_FALSE(doc.isInPlaceModeEnabled());
192
193 Document logDoc;
194 LogBuilder logBuilder(logDoc.root());
195 ASSERT_OK(mod.log(&logBuilder));
196 ASSERT_EQUALS(fromjson("{ $set : { a : 0 } }"), logDoc);
197 }
198
TEST(ExistingNumber,MinNumber)199 TEST(ExistingNumber, MinNumber) {
200 Document doc(fromjson("{a: 1 }"));
201 Mod mod(fromjson("{$min: {a: 0} }"));
202
203 ModifierInterface::ExecInfo execInfo;
204 ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
205 ASSERT_FALSE(execInfo.noOp);
206 ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
207
208 ASSERT_OK(mod.apply());
209 ASSERT_EQUALS(fromjson("{a : 0}"), doc);
210 ASSERT_TRUE(doc.isInPlaceModeEnabled());
211
212 Document logDoc;
213 LogBuilder logBuilder(logDoc.root());
214 ASSERT_OK(mod.log(&logBuilder));
215 ASSERT_EQUALS(fromjson("{ $set : { a : 0 } }"), logDoc);
216 }
217
TEST(MissingField,MaxNumber)218 TEST(MissingField, MaxNumber) {
219 Document doc(fromjson("{}"));
220 Mod mod(fromjson("{$max: {a: 0} }"));
221
222 ModifierInterface::ExecInfo execInfo;
223 ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
224 ASSERT_FALSE(execInfo.noOp);
225 ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
226
227 ASSERT_OK(mod.apply());
228 ASSERT_EQUALS(fromjson("{a : 0}"), doc);
229 ASSERT_FALSE(doc.isInPlaceModeEnabled());
230
231 Document logDoc;
232 LogBuilder logBuilder(logDoc.root());
233 ASSERT_OK(mod.log(&logBuilder));
234 ASSERT_EQUALS(fromjson("{ $set : { a : 0 } }"), logDoc);
235 }
236
TEST(ExistingNumber,MaxNumber)237 TEST(ExistingNumber, MaxNumber) {
238 Document doc(fromjson("{a: 1 }"));
239 Mod mod(fromjson("{$max: {a: 2} }"));
240
241 ModifierInterface::ExecInfo execInfo;
242 ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
243 ASSERT_FALSE(execInfo.noOp);
244 ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
245
246 ASSERT_OK(mod.apply());
247 ASSERT_EQUALS(fromjson("{a : 2}"), doc);
248 ASSERT_TRUE(doc.isInPlaceModeEnabled());
249
250 Document logDoc;
251 LogBuilder logBuilder(logDoc.root());
252 ASSERT_OK(mod.log(&logBuilder));
253 ASSERT_EQUALS(fromjson("{ $set : { a : 2 } }"), logDoc);
254 }
255
TEST(ExistingDate,MaxDate)256 TEST(ExistingDate, MaxDate) {
257 Document doc(fromjson("{a: {$date: 0} }"));
258 Mod mod(fromjson("{$max: {a: {$date: 123123123}} }"));
259
260 ModifierInterface::ExecInfo execInfo;
261 ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
262 ASSERT_FALSE(execInfo.noOp);
263 ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
264
265 ASSERT_OK(mod.apply());
266 ASSERT_EQUALS(fromjson("{a: {$date: 123123123}}"), doc);
267 ASSERT_TRUE(doc.isInPlaceModeEnabled());
268
269 Document logDoc;
270 LogBuilder logBuilder(logDoc.root());
271 ASSERT_OK(mod.log(&logBuilder));
272 ASSERT_EQUALS(fromjson("{$set: {a: {$date: 123123123}} }"), logDoc);
273 }
274
TEST(ExistingEmbeddedDoc,MaxDoc)275 TEST(ExistingEmbeddedDoc, MaxDoc) {
276 Document doc(fromjson("{a: {b: 2}}"));
277 Mod mod(fromjson("{$max: {a: {b: 3}}}"));
278
279 ModifierInterface::ExecInfo execInfo;
280 ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
281 ASSERT_FALSE(execInfo.noOp);
282 ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
283
284 ASSERT_OK(mod.apply());
285 ASSERT_EQUALS(fromjson("{a: {b: 3}}}"), doc);
286
287 Document logDoc;
288 LogBuilder logBuilder(logDoc.root());
289 ASSERT_OK(mod.log(&logBuilder));
290 ASSERT_EQUALS(fromjson("{$set: {a: {b: 3}} }"), logDoc);
291 }
292
TEST(ExistingEmbeddedDoc,MaxNumber)293 TEST(ExistingEmbeddedDoc, MaxNumber) {
294 Document doc(fromjson("{a: {b: 2}}"));
295 Mod mod(fromjson("{$max: {a: 3}}"));
296
297 ModifierInterface::ExecInfo execInfo;
298 ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
299 ASSERT_TRUE(execInfo.noOp);
300 ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
301 }
302
TEST(Collation,MinRespectsCollationFromModifierInterfaceOptions)303 TEST(Collation, MinRespectsCollationFromModifierInterfaceOptions) {
304 CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString);
305 Document doc(fromjson("{a: 'cbc'}"));
306 boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
307 expCtx->setCollator(&collator);
308 ModifierInterface::Options options = ModifierInterface::Options::normal(expCtx);
309 Mod mod(fromjson("{$min: {a: 'dba'}}"), options);
310
311 ModifierInterface::ExecInfo execInfo;
312 ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
313 ASSERT_FALSE(execInfo.noOp);
314
315 ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
316
317 ASSERT_OK(mod.apply());
318 ASSERT_EQUALS(fromjson("{a : 'dba'}"), doc);
319 }
320
TEST(Collation,MinRespectsCollationFromSetCollator)321 TEST(Collation, MinRespectsCollationFromSetCollator) {
322 CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString);
323 Document doc(fromjson("{a: 'cbc'}"));
324 Mod mod(fromjson("{$min: {a: 'dba'}}"));
325 mod.mod().setCollator(&collator);
326
327 ModifierInterface::ExecInfo execInfo;
328 ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
329 ASSERT_FALSE(execInfo.noOp);
330
331 ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
332
333 ASSERT_OK(mod.apply());
334 ASSERT_EQUALS(fromjson("{a : 'dba'}"), doc);
335 }
336
TEST(Collation,MaxRespectsCollationFromSetCollator)337 TEST(Collation, MaxRespectsCollationFromSetCollator) {
338 CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString);
339 Document doc(fromjson("{a: 'cbc'}"));
340 Mod mod(fromjson("{$max: {a: 'abd'}}"));
341 mod.mod().setCollator(&collator);
342
343 ModifierInterface::ExecInfo execInfo;
344 ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
345 ASSERT_FALSE(execInfo.noOp);
346
347 ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
348
349 ASSERT_OK(mod.apply());
350 ASSERT_EQUALS(fromjson("{a : 'abd'}"), doc);
351 }
352
TEST(IndexedMod,PrepareReportCreatedArrayElement)353 TEST(IndexedMod, PrepareReportCreatedArrayElement) {
354 Document doc(fromjson("{a: [{b: 0}]}"));
355 Mod mod(fromjson("{$min: {'a.1.c': 2}}"));
356
357 ModifierInterface::ExecInfo execInfo;
358 ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
359
360 ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1.c");
361 ASSERT_TRUE(execInfo.indexOfArrayWithNewElement[0]);
362 ASSERT_EQUALS(*execInfo.indexOfArrayWithNewElement[0], 0u);
363 ASSERT_FALSE(execInfo.noOp);
364 }
365
TEST(IndexedMod,PrepareDoNotReportModifiedArrayElement)366 TEST(IndexedMod, PrepareDoNotReportModifiedArrayElement) {
367 Document doc(fromjson("{a: [{b: 0}]}"));
368 Mod mod(fromjson("{$min: {'a.0.c': 2}}"));
369
370 ModifierInterface::ExecInfo execInfo;
371 ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
372
373 ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0.c");
374 ASSERT_FALSE(execInfo.indexOfArrayWithNewElement[0]);
375 ASSERT_FALSE(execInfo.noOp);
376 }
377
TEST(IndexedMod,PrepareDoNotReportCreatedNumericObjectField)378 TEST(IndexedMod, PrepareDoNotReportCreatedNumericObjectField) {
379 Document doc(fromjson("{a: {'0': {b: 0}}}"));
380 Mod mod(fromjson("{$min: {'a.1.c': 2}}"));
381
382 ModifierInterface::ExecInfo execInfo;
383 ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
384
385 ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1.c");
386 ASSERT_FALSE(execInfo.indexOfArrayWithNewElement[0]);
387 ASSERT_FALSE(execInfo.noOp);
388 }
389 } // namespace
390