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