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