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/platform/basic.h"
32 
33 #include "mongo/db/update/unset_node.h"
34 
35 #include "mongo/bson/mutable/algorithm.h"
36 #include "mongo/bson/mutable/mutable_bson_test_utils.h"
37 #include "mongo/db/json.h"
38 #include "mongo/db/pipeline/expression_context_for_test.h"
39 #include "mongo/db/update/update_node_test_fixture.h"
40 #include "mongo/unittest/death_test.h"
41 #include "mongo/unittest/unittest.h"
42 
43 namespace mongo {
44 namespace {
45 
46 using UnsetNodeTest = UpdateNodeTest;
47 using mongo::mutablebson::Element;
48 using mongo::mutablebson::countChildren;
49 
50 DEATH_TEST(UnsetNodeTest, InitFailsForEmptyElement, "Invariant failure modExpr.ok()") {
51     auto update = fromjson("{$unset: {}}");
52     boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
53     UnsetNode node;
54     node.init(update["$unset"].embeddedObject().firstElement(), expCtx).transitional_ignore();
55 }
56 
57 DEATH_TEST_F(UnsetNodeTest, ApplyToRootFails, "Invariant failure !applyParams.pathTaken->empty()") {
58     auto update = fromjson("{$unset: {}}");
59     boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
60     UnsetNode node;
61     ASSERT_OK(node.init(update["$unset"], expCtx));
62 
63     mutablebson::Document doc(fromjson("{a: 5}"));
64     node.apply(getApplyParams(doc.root()));
65 }
66 
TEST(UnsetNodeTest,InitSucceedsForNonemptyElement)67 TEST(UnsetNodeTest, InitSucceedsForNonemptyElement) {
68     auto update = fromjson("{$unset: {a: 5}}");
69     boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
70     UnsetNode node;
71     ASSERT_OK(node.init(update["$unset"]["a"], expCtx));
72 }
73 
74 /* This is a no-op because we are unsetting a field that does not exit. */
TEST_F(UnsetNodeTest,UnsetNoOp)75 TEST_F(UnsetNodeTest, UnsetNoOp) {
76     auto update = fromjson("{$unset: {a: 1}}");
77     boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
78     UnsetNode node;
79     ASSERT_OK(node.init(update["$unset"]["a"], expCtx));
80 
81     mutablebson::Document doc(fromjson("{b: 5}"));
82     setPathToCreate("a");
83     addIndexedPath("a");
84     auto result = node.apply(getApplyParams(doc.root()));
85     ASSERT_TRUE(result.noop);
86     ASSERT_FALSE(result.indexesAffected);
87     ASSERT_EQUALS(fromjson("{b: 5}"), doc);
88     ASSERT_TRUE(doc.isInPlaceModeEnabled());
89     ASSERT_EQUALS(fromjson("{}"), getLogDoc());
90 }
91 
TEST_F(UnsetNodeTest,UnsetNoOpDottedPath)92 TEST_F(UnsetNodeTest, UnsetNoOpDottedPath) {
93     auto update = fromjson("{$unset: {'a.b': 1}}");
94     boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
95     UnsetNode node;
96     ASSERT_OK(node.init(update["$unset"]["a.b"], expCtx));
97 
98     mutablebson::Document doc(fromjson("{a: 5}"));
99     setPathToCreate("b");
100     setPathTaken("a");
101     addIndexedPath("a");
102     auto result = node.apply(getApplyParams(doc.root()["a"]));
103     ASSERT_TRUE(result.noop);
104     ASSERT_FALSE(result.indexesAffected);
105     ASSERT_EQUALS(fromjson("{a: 5}"), doc);
106     ASSERT_TRUE(doc.isInPlaceModeEnabled());
107     ASSERT_EQUALS(fromjson("{}"), getLogDoc());
108 }
109 
TEST_F(UnsetNodeTest,UnsetNoOpThroughArray)110 TEST_F(UnsetNodeTest, UnsetNoOpThroughArray) {
111     auto update = fromjson("{$unset: {'a.b': 1}}");
112     boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
113     UnsetNode node;
114     ASSERT_OK(node.init(update["$unset"]["a.b"], expCtx));
115 
116     mutablebson::Document doc(fromjson("{a:[{b:1}]}"));
117     setPathToCreate("b");
118     setPathTaken("a");
119     addIndexedPath("a");
120     auto result = node.apply(getApplyParams(doc.root()["a"]));
121     ASSERT_TRUE(result.noop);
122     ASSERT_FALSE(result.indexesAffected);
123     ASSERT_EQUALS(fromjson("{a:[{b:1}]}"), doc);
124     ASSERT_TRUE(doc.isInPlaceModeEnabled());
125     ASSERT_EQUALS(fromjson("{}"), getLogDoc());
126 }
127 
TEST_F(UnsetNodeTest,UnsetNoOpEmptyDoc)128 TEST_F(UnsetNodeTest, UnsetNoOpEmptyDoc) {
129     auto update = fromjson("{$unset: {a: 1}}");
130     boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
131     UnsetNode node;
132     ASSERT_OK(node.init(update["$unset"]["a"], expCtx));
133 
134     mutablebson::Document doc(fromjson("{}"));
135     setPathToCreate("a");
136     addIndexedPath("a");
137     auto result = node.apply(getApplyParams(doc.root()));
138     ASSERT_TRUE(result.noop);
139     ASSERT_FALSE(result.indexesAffected);
140     ASSERT_EQUALS(fromjson("{}"), doc);
141     ASSERT_TRUE(doc.isInPlaceModeEnabled());
142     ASSERT_EQUALS(fromjson("{}"), getLogDoc());
143 }
144 
TEST_F(UnsetNodeTest,UnsetTopLevelPath)145 TEST_F(UnsetNodeTest, UnsetTopLevelPath) {
146     auto update = fromjson("{$unset: {a: 1}}");
147     boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
148     UnsetNode node;
149     ASSERT_OK(node.init(update["$unset"]["a"], expCtx));
150 
151     mutablebson::Document doc(fromjson("{a: 5}"));
152     setPathTaken("a");
153     addIndexedPath("a");
154     auto result = node.apply(getApplyParams(doc.root()["a"]));
155     ASSERT_FALSE(result.noop);
156     ASSERT_TRUE(result.indexesAffected);
157     ASSERT_EQUALS(fromjson("{}"), doc);
158     ASSERT_FALSE(doc.isInPlaceModeEnabled());
159     ASSERT_EQUALS(fromjson("{$unset: {a: true}}"), getLogDoc());
160 }
161 
TEST_F(UnsetNodeTest,UnsetNestedPath)162 TEST_F(UnsetNodeTest, UnsetNestedPath) {
163     auto update = fromjson("{$unset: {'a.b.c': 1}}");
164     boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
165     UnsetNode node;
166     ASSERT_OK(node.init(update["$unset"]["a.b.c"], expCtx));
167 
168     mutablebson::Document doc(fromjson("{a: {b: {c: 6}}}}"));
169     setPathTaken("a.b.c");
170     addIndexedPath("a");
171     auto result = node.apply(getApplyParams(doc.root()["a"]["b"]["c"]));
172     ASSERT_FALSE(result.noop);
173     ASSERT_TRUE(result.indexesAffected);
174     ASSERT_EQUALS(fromjson("{a: {b: {}}}"), doc);
175     ASSERT_FALSE(doc.isInPlaceModeEnabled());
176     ASSERT_EQUALS(fromjson("{$unset: {'a.b.c': true}}"), getLogDoc());
177 }
178 
TEST_F(UnsetNodeTest,UnsetObject)179 TEST_F(UnsetNodeTest, UnsetObject) {
180     auto update = fromjson("{$unset: {'a.b': 1}}");
181     boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
182     UnsetNode node;
183     ASSERT_OK(node.init(update["$unset"]["a.b"], expCtx));
184 
185     mutablebson::Document doc(fromjson("{a: {b: {c: 6}}}}"));
186     setPathTaken("a.b");
187     addIndexedPath("a");
188     auto result = node.apply(getApplyParams(doc.root()["a"]["b"]));
189     ASSERT_FALSE(result.noop);
190     ASSERT_TRUE(result.indexesAffected);
191     ASSERT_EQUALS(fromjson("{a: {}}"), doc);
192     ASSERT_FALSE(doc.isInPlaceModeEnabled());
193     ASSERT_EQUALS(fromjson("{$unset: {'a.b': true}}"), getLogDoc());
194 }
195 
TEST_F(UnsetNodeTest,UnsetArrayElement)196 TEST_F(UnsetNodeTest, UnsetArrayElement) {
197     auto update = fromjson("{$unset: {'a.0': 1}}");
198     boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
199     UnsetNode node;
200     ASSERT_OK(node.init(update["$unset"]["a.0"], expCtx));
201 
202     mutablebson::Document doc(fromjson("{a:[1], b:1}"));
203     setPathTaken("a.0");
204     addIndexedPath("a");
205     auto result = node.apply(getApplyParams(doc.root()["a"][0]));
206     ASSERT_FALSE(result.noop);
207     ASSERT_TRUE(result.indexesAffected);
208     ASSERT_EQUALS(fromjson("{a:[null], b:1}"), doc);
209     ASSERT_FALSE(doc.isInPlaceModeEnabled());
210     ASSERT_EQUALS(fromjson("{$unset: {'a.0': true}}"), getLogDoc());
211 }
212 
TEST_F(UnsetNodeTest,UnsetPositional)213 TEST_F(UnsetNodeTest, UnsetPositional) {
214     auto update = fromjson("{$unset: {'a.$': 1}}");
215     boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
216     UnsetNode node;
217     ASSERT_OK(node.init(update["$unset"]["a.$"], expCtx));
218 
219     mutablebson::Document doc(fromjson("{a: [0, 1, 2]}"));
220     setPathTaken("a.1");
221     setMatchedField("1");
222     addIndexedPath("a");
223     auto result = node.apply(getApplyParams(doc.root()["a"][1]));
224     ASSERT_FALSE(result.noop);
225     ASSERT_TRUE(result.indexesAffected);
226     ASSERT_EQUALS(fromjson("{a: [0, null, 2]}"), doc);
227     ASSERT_FALSE(doc.isInPlaceModeEnabled());
228     ASSERT_EQUALS(fromjson("{$unset: {'a.1': true}}"), getLogDoc());
229 }
230 
TEST_F(UnsetNodeTest,UnsetEntireArray)231 TEST_F(UnsetNodeTest, UnsetEntireArray) {
232     auto update = fromjson("{$unset: {'a': 1}}");
233     boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
234     UnsetNode node;
235     ASSERT_OK(node.init(update["$unset"]["a"], expCtx));
236 
237     mutablebson::Document doc(fromjson("{a: [0, 1, 2]}"));
238     setPathTaken("a");
239     addIndexedPath("a");
240     auto result = node.apply(getApplyParams(doc.root()["a"]));
241     ASSERT_FALSE(result.noop);
242     ASSERT_TRUE(result.indexesAffected);
243     ASSERT_EQUALS(fromjson("{}"), doc);
244     ASSERT_FALSE(doc.isInPlaceModeEnabled());
245     ASSERT_EQUALS(fromjson("{$unset: {a: true}}"), getLogDoc());
246 }
247 
TEST_F(UnsetNodeTest,UnsetFromObjectInArray)248 TEST_F(UnsetNodeTest, UnsetFromObjectInArray) {
249     auto update = fromjson("{$unset: {'a.0.b': 1}}");
250     boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
251     UnsetNode node;
252     ASSERT_OK(node.init(update["$unset"]["a.0.b"], expCtx));
253 
254     mutablebson::Document doc(fromjson("{a: [{b: 1}]}"));
255     setPathTaken("a.0.b");
256     addIndexedPath("a");
257     auto result = node.apply(getApplyParams(doc.root()["a"][0]["b"]));
258     ASSERT_FALSE(result.noop);
259     ASSERT_TRUE(result.indexesAffected);
260     ASSERT_EQUALS(fromjson("{a:[{}]}"), doc);
261     ASSERT_FALSE(doc.isInPlaceModeEnabled());
262     ASSERT_EQUALS(fromjson("{$unset: {'a.0.b': true}}"), getLogDoc());
263 }
264 
TEST_F(UnsetNodeTest,CanUnsetInvalidField)265 TEST_F(UnsetNodeTest, CanUnsetInvalidField) {
266     auto update = fromjson("{$unset: {'a.$.$b': true}}");
267     boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
268     UnsetNode node;
269     ASSERT_OK(node.init(update["$unset"]["a.$.$b"], expCtx));
270 
271     mutablebson::Document doc(fromjson("{b: 1, a: [{$b: 1}]}"));
272     setPathTaken("a.0.$b");
273     addIndexedPath("a");
274     auto result = node.apply(getApplyParams(doc.root()["a"][0]["$b"]));
275     ASSERT_FALSE(result.noop);
276     ASSERT_TRUE(result.indexesAffected);
277     ASSERT_EQUALS(fromjson("{b: 1, a: [{}]}"), doc);
278     ASSERT_FALSE(doc.isInPlaceModeEnabled());
279     ASSERT_EQUALS(fromjson("{$unset: {'a.0.$b': true}}"), getLogDoc());
280 }
281 
TEST_F(UnsetNodeTest,ApplyNoIndexDataNoLogBuilder)282 TEST_F(UnsetNodeTest, ApplyNoIndexDataNoLogBuilder) {
283     auto update = fromjson("{$unset: {a: 1}}");
284     boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
285     UnsetNode node;
286     ASSERT_OK(node.init(update["$unset"]["a"], expCtx));
287 
288     mutablebson::Document doc(fromjson("{a: 5}"));
289     setPathTaken("a");
290     setLogBuilderToNull();
291     auto result = node.apply(getApplyParams(doc.root()["a"]));
292     ASSERT_FALSE(result.noop);
293     ASSERT_FALSE(result.indexesAffected);
294     ASSERT_EQUALS(fromjson("{}"), doc);
295     ASSERT_FALSE(doc.isInPlaceModeEnabled());
296 }
297 
TEST_F(UnsetNodeTest,ApplyDoesNotAffectIndexes)298 TEST_F(UnsetNodeTest, ApplyDoesNotAffectIndexes) {
299     auto update = fromjson("{$unset: {a: 1}}");
300     boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
301     UnsetNode node;
302     ASSERT_OK(node.init(update["$unset"]["a"], expCtx));
303 
304     mutablebson::Document doc(fromjson("{a: 5}"));
305     setPathTaken("a");
306     addIndexedPath("b");
307     auto result = node.apply(getApplyParams(doc.root()["a"]));
308     ASSERT_FALSE(result.noop);
309     ASSERT_FALSE(result.indexesAffected);
310     ASSERT_EQUALS(fromjson("{}"), doc);
311     ASSERT_FALSE(doc.isInPlaceModeEnabled());
312     ASSERT_EQUALS(fromjson("{$unset: {a: true}}"), getLogDoc());
313 }
314 
TEST_F(UnsetNodeTest,ApplyFieldWithDot)315 TEST_F(UnsetNodeTest, ApplyFieldWithDot) {
316     auto update = fromjson("{$unset: {'a.b': 1}}");
317     boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
318     UnsetNode node;
319     ASSERT_OK(node.init(update["$unset"]["a.b"], expCtx));
320 
321     mutablebson::Document doc(fromjson("{'a.b':4, a: {b: 2}}"));
322     setPathTaken("a.b");
323     addIndexedPath("a");
324     auto result = node.apply(getApplyParams(doc.root()["a"]["b"]));
325     ASSERT_FALSE(result.noop);
326     ASSERT_TRUE(result.indexesAffected);
327     ASSERT_EQUALS(fromjson("{'a.b':4, a: {}}"), doc);
328     ASSERT_FALSE(doc.isInPlaceModeEnabled());
329     ASSERT_EQUALS(fromjson("{$unset: {'a.b': true}}"), getLogDoc());
330 }
331 
TEST_F(UnsetNodeTest,ApplyCannotRemoveRequiredPartOfDBRef)332 TEST_F(UnsetNodeTest, ApplyCannotRemoveRequiredPartOfDBRef) {
333     auto update = fromjson("{$unset: {'a.$id': true}}");
334     boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
335     UnsetNode node;
336     ASSERT_OK(node.init(update["$unset"]["a.$id"], expCtx));
337 
338     mutablebson::Document doc(fromjson("{a: {$ref: 'c', $id: 0}}"));
339     setPathTaken("a.$id");
340     ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()["a"]["$id"])),
341                                 AssertionException,
342                                 ErrorCodes::InvalidDBRef,
343                                 "The DBRef $ref field must be followed by a $id field");
344 }
345 
TEST_F(UnsetNodeTest,ApplyCanRemoveRequiredPartOfDBRefIfValidateForStorageIsFalse)346 TEST_F(UnsetNodeTest, ApplyCanRemoveRequiredPartOfDBRefIfValidateForStorageIsFalse) {
347     auto update = fromjson("{$unset: {'a.$id': true}}");
348     boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
349     UnsetNode node;
350     ASSERT_OK(node.init(update["$unset"]["a.$id"], expCtx));
351 
352     mutablebson::Document doc(fromjson("{a: {$ref: 'c', $id: 0}}"));
353     setPathTaken("a.$id");
354     addIndexedPath("a");
355     setValidateForStorage(false);
356     auto result = node.apply(getApplyParams(doc.root()["a"]["$id"]));
357     ASSERT_FALSE(result.noop);
358     ASSERT_TRUE(result.indexesAffected);
359     auto updated = BSON("a" << BSON("$ref"
360                                     << "c"));
361     ASSERT_EQUALS(updated, doc);
362     ASSERT_FALSE(doc.isInPlaceModeEnabled());
363     ASSERT_EQUALS(fromjson("{$unset: {'a.$id': true}}"), getLogDoc());
364 }
365 
TEST_F(UnsetNodeTest,ApplyCannotRemoveImmutablePath)366 TEST_F(UnsetNodeTest, ApplyCannotRemoveImmutablePath) {
367     auto update = fromjson("{$unset: {'a.b': true}}");
368     boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
369     UnsetNode node;
370     ASSERT_OK(node.init(update["$unset"]["a.b"], expCtx));
371 
372     mutablebson::Document doc(fromjson("{a: {b: 1}}"));
373     setPathTaken("a.b");
374     addImmutablePath("a.b");
375     ASSERT_THROWS_CODE_AND_WHAT(
376         node.apply(getApplyParams(doc.root()["a"]["b"])),
377         AssertionException,
378         ErrorCodes::ImmutableField,
379         "Performing an update on the path 'a.b' would modify the immutable field 'a.b'");
380 }
381 
TEST_F(UnsetNodeTest,ApplyCannotRemovePrefixOfImmutablePath)382 TEST_F(UnsetNodeTest, ApplyCannotRemovePrefixOfImmutablePath) {
383     auto update = fromjson("{$unset: {a: true}}");
384     boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
385     UnsetNode node;
386     ASSERT_OK(node.init(update["$unset"]["a"], expCtx));
387 
388     mutablebson::Document doc(fromjson("{a: {b: 1}}"));
389     setPathTaken("a");
390     addImmutablePath("a.b");
391     ASSERT_THROWS_CODE_AND_WHAT(
392         node.apply(getApplyParams(doc.root()["a"])),
393         AssertionException,
394         ErrorCodes::ImmutableField,
395         "Performing an update on the path 'a' would modify the immutable field 'a.b'");
396 }
397 
TEST_F(UnsetNodeTest,ApplyCannotRemoveSuffixOfImmutablePath)398 TEST_F(UnsetNodeTest, ApplyCannotRemoveSuffixOfImmutablePath) {
399     auto update = fromjson("{$unset: {'a.b.c': true}}");
400     boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
401     UnsetNode node;
402     ASSERT_OK(node.init(update["$unset"]["a.b.c"], expCtx));
403 
404     mutablebson::Document doc(fromjson("{a: {b: {c: 1}}}"));
405     setPathTaken("a.b.c");
406     addImmutablePath("a.b");
407     ASSERT_THROWS_CODE_AND_WHAT(
408         node.apply(getApplyParams(doc.root()["a"]["b"]["c"])),
409         AssertionException,
410         ErrorCodes::ImmutableField,
411         "Performing an update on the path 'a.b.c' would modify the immutable field 'a.b'");
412 }
413 
TEST_F(UnsetNodeTest,ApplyCanRemoveImmutablePathIfNoop)414 TEST_F(UnsetNodeTest, ApplyCanRemoveImmutablePathIfNoop) {
415     auto update = fromjson("{$unset: {'a.b.c': true}}");
416     boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
417     UnsetNode node;
418     ASSERT_OK(node.init(update["$unset"]["a.b.c"], expCtx));
419 
420     mutablebson::Document doc(fromjson("{a: {b: 1}}"));
421     setPathToCreate("c");
422     setPathTaken("a.b");
423     addImmutablePath("a.b");
424     addIndexedPath("a");
425     auto result = node.apply(getApplyParams(doc.root()["a"]["b"]));
426     ASSERT_TRUE(result.noop);
427     ASSERT_FALSE(result.indexesAffected);
428     ASSERT_EQUALS(fromjson("{a: {b: 1}}"), doc);
429     ASSERT_TRUE(doc.isInPlaceModeEnabled());
430     ASSERT_EQUALS(fromjson("{}"), getLogDoc());
431 }
432 
433 }  // namespace
434 }  // namespace mongo
435