diff options
-rw-r--r-- | src/mongo/db/update/SConscript | 10 | ||||
-rw-r--r-- | src/mongo/db/update/modifier_table.cpp | 3 | ||||
-rw-r--r-- | src/mongo/db/update/unset_node.cpp | 84 | ||||
-rw-r--r-- | src/mongo/db/update/unset_node.h | 60 | ||||
-rw-r--r-- | src/mongo/db/update/unset_node_test.cpp | 579 | ||||
-rw-r--r-- | src/mongo/db/update/update_object_node_test.cpp | 10 |
6 files changed, 745 insertions, 1 deletions
diff --git a/src/mongo/db/update/SConscript b/src/mongo/db/update/SConscript index e1273516d44..e985acbbca1 100644 --- a/src/mongo/db/update/SConscript +++ b/src/mongo/db/update/SConscript @@ -57,6 +57,7 @@ env.Library( source=[ 'modifier_table.cpp', 'set_node.cpp', + 'unset_node.cpp', 'update_node.cpp', 'update_object_node.cpp', ], @@ -85,6 +86,15 @@ env.CppUnitTest( ) env.CppUnitTest( + target='unset_node_test', + source='unset_node_test.cpp', + LIBDEPS=[ + '$BUILD_DIR/mongo/bson/mutable/mutable_bson_test_utils', + 'update', + ], +) + +env.CppUnitTest( target='update_object_node_test', source='update_object_node_test.cpp', LIBDEPS=[ diff --git a/src/mongo/db/update/modifier_table.cpp b/src/mongo/db/update/modifier_table.cpp index 008973980d8..d98e6f40e95 100644 --- a/src/mongo/db/update/modifier_table.cpp +++ b/src/mongo/db/update/modifier_table.cpp @@ -47,6 +47,7 @@ #include "mongo/db/ops/modifier_set.h" #include "mongo/db/ops/modifier_unset.h" #include "mongo/db/update/set_node.h" +#include "mongo/db/update/unset_node.h" #include "mongo/platform/unordered_map.h" #include "mongo/stdx/memory.h" @@ -181,6 +182,8 @@ std::unique_ptr<UpdateLeafNode> makeUpdateLeafNode(ModifierType modType) { switch (modType) { case MOD_SET: return stdx::make_unique<SetNode>(); + case MOD_UNSET: + return stdx::make_unique<UnsetNode>(); default: return nullptr; } diff --git a/src/mongo/db/update/unset_node.cpp b/src/mongo/db/update/unset_node.cpp new file mode 100644 index 00000000000..05dde507980 --- /dev/null +++ b/src/mongo/db/update/unset_node.cpp @@ -0,0 +1,84 @@ +/** + * Copyright (C) 2017 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/db/update/unset_node.h" + +namespace mongo { + +Status UnsetNode::init(BSONElement modExpr, const CollatorInterface* collator) { + // Note that we don't need to store modExpr, because $unset does not do anything with its value. + invariant(modExpr.ok()); + return Status::OK(); +} + +Status UnsetNode::apply(mutablebson::Element element, + FieldRef* pathToCreate, + FieldRef* pathTaken, + StringData matchedField, + bool fromReplication, + const UpdateIndexData* indexData, + LogBuilder* logBuilder, + bool* indexesAffected, + bool* noop) const { + *indexesAffected = false; + *noop = false; + + if (!pathToCreate->empty()) { + // A non-empty "pathToCreate" implies that our search did not find the field that we wanted + // to delete. We employ a simple and efficient strategy for deleting fields that don't yet + // exist. + *noop = true; + return Status::OK(); + } + + // Determine if indexes are affected. + if (indexData && indexData->mightBeIndexed(pathTaken->dottedField())) { + *indexesAffected = true; + } + + auto parent = element.parent(); + invariant(parent.ok()); + if (!parent.isType(BSONType::Array)) { + invariantOK(element.remove()); + } else { + // Special case: An $unset on an array element sets it to null instead of removing it from + // the array. + invariantOK(element.setValueNull()); + } + + // Log the unset. + if (logBuilder) { + return logBuilder->addToUnsets(pathTaken->dottedField()); + } + + return Status::OK(); +} + +} // namespace mongo diff --git a/src/mongo/db/update/unset_node.h b/src/mongo/db/update/unset_node.h new file mode 100644 index 00000000000..4751bcf96a8 --- /dev/null +++ b/src/mongo/db/update/unset_node.h @@ -0,0 +1,60 @@ +/** + * Copyright (C) 2017 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#pragma once + +#include "mongo/db/update/update_leaf_node.h" +#include "mongo/stdx/memory.h" + +namespace mongo { + +/** + * Represents the application of a $unset to the value at the end of a path. + */ +class UnsetNode : public UpdateLeafNode { +public: + Status init(BSONElement modExpr, const CollatorInterface* collator) final; + + std::unique_ptr<UpdateNode> clone() const final { + return stdx::make_unique<UnsetNode>(*this); + } + + void setCollator(const CollatorInterface* collator) final {} + + Status apply(mutablebson::Element element, + FieldRef* pathToCreate, + FieldRef* pathTaken, + StringData matchedField, + bool fromReplication, + const UpdateIndexData* indexData, + LogBuilder* logBuilder, + bool* indexesAffected, + bool* noop) const final; +}; + +} // namespace mongo diff --git a/src/mongo/db/update/unset_node_test.cpp b/src/mongo/db/update/unset_node_test.cpp new file mode 100644 index 00000000000..3a79dc2e860 --- /dev/null +++ b/src/mongo/db/update/unset_node_test.cpp @@ -0,0 +1,579 @@ +/** + * Copyright (C) 2017 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/db/update/unset_node.h" + +#include "mongo/bson/mutable/algorithm.h" +#include "mongo/bson/mutable/mutable_bson_test_utils.h" +#include "mongo/db/json.h" +#include "mongo/unittest/death_test.h" +#include "mongo/unittest/unittest.h" + +namespace { + +using namespace mongo; +using mongo::mutablebson::Document; +using mongo::mutablebson::Element; +using mongo::mutablebson::countChildren; + +DEATH_TEST(UnsetNodeTest, InitFailsForEmptyElement, "Invariant failure modExpr.ok()") { + auto update = fromjson("{$unset: {}}"); + const CollatorInterface* collator = nullptr; + UnsetNode node; + node.init(update["$unset"].embeddedObject().firstElement(), collator); +} + +DEATH_TEST(UnsetNodeTest, ApplyToRootFails, "Invariant failure parent.ok()") { + auto update = fromjson("{$unset: {}}"); + const CollatorInterface* collator = nullptr; + UnsetNode node; + ASSERT_OK(node.init(update["$unset"], collator)); + + Document doc(fromjson("{a: 5}")); + FieldRef pathToCreate(""); + FieldRef pathTaken(""); + StringData matchedField; + auto fromReplication = false; + const UpdateIndexData* indexData = nullptr; + LogBuilder* logBuilder = nullptr; + auto indexesAffected = false; + auto noop = false; + node.apply(doc.root(), + &pathToCreate, + &pathTaken, + matchedField, + fromReplication, + indexData, + logBuilder, + &indexesAffected, + &noop); +} + +TEST(UnsetNodeTest, InitSucceedsForNonemptyElement) { + auto update = fromjson("{$unset: {a: 5}}"); + const CollatorInterface* collator = nullptr; + UnsetNode node; + ASSERT_OK(node.init(update["$unset"]["a"], collator)); +} + +/* This is a no-op because we are unsetting a field that does not exit. */ +TEST(UnsetNodeTest, UnsetNoOp) { + auto update = fromjson("{$unset: {a: 1}}"); + const CollatorInterface* collator = nullptr; + UnsetNode node; + ASSERT_OK(node.init(update["$unset"]["a"], collator)); + + Document doc(fromjson("{b: 5}")); + FieldRef pathToCreate("a"); + FieldRef pathTaken(""); + StringData matchedField; + auto fromReplication = false; + UpdateIndexData indexData; + indexData.addPath("a"); + Document logDoc; + LogBuilder logBuilder(logDoc.root()); + auto indexesAffected = false; + auto noop = false; + ASSERT_OK(node.apply(doc.root(), + &pathToCreate, + &pathTaken, + matchedField, + fromReplication, + &indexData, + &logBuilder, + &indexesAffected, + &noop)); + ASSERT_TRUE(noop); + ASSERT_FALSE(indexesAffected); + ASSERT_EQUALS(fromjson("{b: 5}"), doc); + ASSERT_TRUE(doc.isInPlaceModeEnabled()); + ASSERT_EQUALS(fromjson("{}"), logDoc); +} + +TEST(UnsetNodeTest, UnsetNoOpDottedPath) { + auto update = fromjson("{$unset: {'a.b': 1}}"); + const CollatorInterface* collator = nullptr; + UnsetNode node; + ASSERT_OK(node.init(update["$unset"]["a.b"], collator)); + + Document doc(fromjson("{a: 5}")); + FieldRef pathToCreate("b"); + FieldRef pathTaken("a"); + StringData matchedField; + auto fromReplication = false; + UpdateIndexData indexData; + indexData.addPath("a"); + Document logDoc; + LogBuilder logBuilder(logDoc.root()); + auto indexesAffected = false; + auto noop = false; + ASSERT_OK(node.apply(doc.root()["a"], + &pathToCreate, + &pathTaken, + matchedField, + fromReplication, + &indexData, + &logBuilder, + &indexesAffected, + &noop)); + ASSERT_TRUE(noop); + ASSERT_FALSE(indexesAffected); + ASSERT_EQUALS(fromjson("{a: 5}"), doc); + ASSERT_TRUE(doc.isInPlaceModeEnabled()); + ASSERT_EQUALS(fromjson("{}"), logDoc); +} + +TEST(UnsetNodeTest, UnsetNoOpThroughArray) { + auto update = fromjson("{$unset: {'a.b': 1}}"); + const CollatorInterface* collator = nullptr; + UnsetNode node; + ASSERT_OK(node.init(update["$unset"]["a.b"], collator)); + + Document doc(fromjson("{a:[{b:1}]}")); + FieldRef pathToCreate("b"); + FieldRef pathTaken("a"); + StringData matchedField; + auto fromReplication = false; + UpdateIndexData indexData; + indexData.addPath("a"); + Document logDoc; + LogBuilder logBuilder(logDoc.root()); + auto indexesAffected = false; + auto noop = false; + ASSERT_OK(node.apply(doc.root()["a"], + &pathToCreate, + &pathTaken, + matchedField, + fromReplication, + &indexData, + &logBuilder, + &indexesAffected, + &noop)); + ASSERT_TRUE(noop); + ASSERT_FALSE(indexesAffected); + ASSERT_EQUALS(fromjson("{a:[{b:1}]}"), doc); + ASSERT_TRUE(doc.isInPlaceModeEnabled()); + ASSERT_EQUALS(fromjson("{}"), logDoc); +} + +TEST(UnsetNodeTest, UnsetNoOpEmptyDoc) { + auto update = fromjson("{$unset: {a: 1}}"); + const CollatorInterface* collator = nullptr; + UnsetNode node; + ASSERT_OK(node.init(update["$unset"]["a"], collator)); + + Document doc(fromjson("{}")); + FieldRef pathToCreate("a"); + FieldRef pathTaken(""); + StringData matchedField; + auto fromReplication = false; + UpdateIndexData indexData; + indexData.addPath("a"); + Document logDoc; + LogBuilder logBuilder(logDoc.root()); + auto indexesAffected = false; + auto noop = false; + ASSERT_OK(node.apply(doc.root(), + &pathToCreate, + &pathTaken, + matchedField, + fromReplication, + &indexData, + &logBuilder, + &indexesAffected, + &noop)); + ASSERT_TRUE(noop); + ASSERT_FALSE(indexesAffected); + ASSERT_EQUALS(fromjson("{}"), doc); + ASSERT_TRUE(doc.isInPlaceModeEnabled()); + ASSERT_EQUALS(fromjson("{}"), logDoc); +} + +TEST(UnsetNodeTest, UnsetTopLevelPath) { + auto update = fromjson("{$unset: {a: 1}}"); + const CollatorInterface* collator = nullptr; + UnsetNode node; + ASSERT_OK(node.init(update["$unset"]["a"], collator)); + + Document doc(fromjson("{a: 5}")); + FieldRef pathToCreate(""); + FieldRef pathTaken("a"); + StringData matchedField; + auto fromReplication = false; + UpdateIndexData indexData; + indexData.addPath("a"); + Document logDoc; + LogBuilder logBuilder(logDoc.root()); + auto indexesAffected = false; + auto noop = false; + ASSERT_OK(node.apply(doc.root()["a"], + &pathToCreate, + &pathTaken, + matchedField, + fromReplication, + &indexData, + &logBuilder, + &indexesAffected, + &noop)); + ASSERT_FALSE(noop); + ASSERT_TRUE(indexesAffected); + ASSERT_EQUALS(fromjson("{}"), doc); + ASSERT_FALSE(doc.isInPlaceModeEnabled()); + ASSERT_EQUALS(fromjson("{$unset: {a: true}}"), logDoc); +} + +TEST(UnsetNodeTest, UnsetNestedPath) { + auto update = fromjson("{$unset: {'a.b.c': 1}}"); + const CollatorInterface* collator = nullptr; + UnsetNode node; + ASSERT_OK(node.init(update["$unset"]["a.b.c"], collator)); + + Document doc(fromjson("{a: {b: {c: 6}}}}")); + FieldRef pathToCreate(""); + FieldRef pathTaken("a.b.c"); + StringData matchedField; + auto fromReplication = false; + UpdateIndexData indexData; + indexData.addPath("a"); + Document logDoc; + LogBuilder logBuilder(logDoc.root()); + auto indexesAffected = false; + auto noop = false; + ASSERT_OK(node.apply(doc.root()["a"]["b"]["c"], + &pathToCreate, + &pathTaken, + matchedField, + fromReplication, + &indexData, + &logBuilder, + &indexesAffected, + &noop)); + ASSERT_FALSE(noop); + ASSERT_TRUE(indexesAffected); + ASSERT_EQUALS(fromjson("{a: {b: {}}}"), doc); + ASSERT_FALSE(doc.isInPlaceModeEnabled()); + ASSERT_EQUALS(fromjson("{$unset: {'a.b.c': true}}"), logDoc); +} + +TEST(UnsetNodeTest, UnsetObject) { + auto update = fromjson("{$unset: {'a.b': 1}}"); + const CollatorInterface* collator = nullptr; + UnsetNode node; + ASSERT_OK(node.init(update["$unset"]["a.b"], collator)); + + Document doc(fromjson("{a: {b: {c: 6}}}}")); + FieldRef pathToCreate(""); + FieldRef pathTaken("a.b"); + StringData matchedField; + auto fromReplication = false; + UpdateIndexData indexData; + indexData.addPath("a"); + Document logDoc; + LogBuilder logBuilder(logDoc.root()); + auto indexesAffected = false; + auto noop = false; + ASSERT_OK(node.apply(doc.root()["a"]["b"], + &pathToCreate, + &pathTaken, + matchedField, + fromReplication, + &indexData, + &logBuilder, + &indexesAffected, + &noop)); + ASSERT_FALSE(noop); + ASSERT_TRUE(indexesAffected); + ASSERT_EQUALS(fromjson("{a: {}}"), doc); + ASSERT_FALSE(doc.isInPlaceModeEnabled()); + ASSERT_EQUALS(fromjson("{$unset: {'a.b': true}}"), logDoc); +} + +TEST(UnsetNodeTest, UnsetArrayElement) { + auto update = fromjson("{$unset: {'a.0': 1}}"); + const CollatorInterface* collator = nullptr; + UnsetNode node; + ASSERT_OK(node.init(update["$unset"]["a.0"], collator)); + + Document doc(fromjson("{a:[1], b:1}")); + FieldRef pathToCreate(""); + FieldRef pathTaken("a.0"); + StringData matchedField; + auto fromReplication = false; + UpdateIndexData indexData; + indexData.addPath("a"); + Document logDoc; + LogBuilder logBuilder(logDoc.root()); + auto indexesAffected = false; + auto noop = false; + ASSERT_OK(node.apply(doc.root()["a"]["0"], + &pathToCreate, + &pathTaken, + matchedField, + fromReplication, + &indexData, + &logBuilder, + &indexesAffected, + &noop)); + ASSERT_FALSE(noop); + ASSERT_TRUE(indexesAffected); + ASSERT_EQUALS(fromjson("{a:[null], b:1}"), doc); + ASSERT_FALSE(doc.isInPlaceModeEnabled()); + ASSERT_EQUALS(fromjson("{$unset: {'a.0': true}}"), logDoc); +} + +TEST(UnsetNodeTest, UnsetPositional) { + auto update = fromjson("{$unset: {'a.$': 1}}"); + const CollatorInterface* collator = nullptr; + UnsetNode node; + ASSERT_OK(node.init(update["$unset"]["a.$"], collator)); + + Document doc(fromjson("{a: [0, 1, 2]}")); + FieldRef pathToCreate(""); + FieldRef pathTaken("a.1"); + StringData matchedField = "1"; + auto fromReplication = false; + UpdateIndexData indexData; + indexData.addPath("a"); + Document logDoc; + LogBuilder logBuilder(logDoc.root()); + auto indexesAffected = false; + auto noop = false; + ASSERT_OK(node.apply(doc.root()["a"]["1"], + &pathToCreate, + &pathTaken, + matchedField, + fromReplication, + &indexData, + &logBuilder, + &indexesAffected, + &noop)); + ASSERT_FALSE(noop); + ASSERT_TRUE(indexesAffected); + ASSERT_EQUALS(fromjson("{a: [0, null, 2]}"), doc); + ASSERT_FALSE(doc.isInPlaceModeEnabled()); + ASSERT_EQUALS(fromjson("{$unset: {'a.1': true}}"), logDoc); +} + +TEST(UnsetNodeTest, UnsetEntireArray) { + auto update = fromjson("{$unset: {'a': 1}}"); + const CollatorInterface* collator = nullptr; + UnsetNode node; + ASSERT_OK(node.init(update["$unset"]["a"], collator)); + + Document doc(fromjson("{a: [0, 1, 2]}")); + FieldRef pathToCreate(""); + FieldRef pathTaken("a"); + StringData matchedField; + auto fromReplication = false; + UpdateIndexData indexData; + indexData.addPath("a"); + Document logDoc; + LogBuilder logBuilder(logDoc.root()); + auto indexesAffected = false; + auto noop = false; + ASSERT_OK(node.apply(doc.root()["a"], + &pathToCreate, + &pathTaken, + matchedField, + fromReplication, + &indexData, + &logBuilder, + &indexesAffected, + &noop)); + ASSERT_FALSE(noop); + ASSERT_TRUE(indexesAffected); + ASSERT_EQUALS(fromjson("{}"), doc); + ASSERT_FALSE(doc.isInPlaceModeEnabled()); + ASSERT_EQUALS(fromjson("{$unset: {a: true}}"), logDoc); +} + +TEST(UnsetNodeTest, UnsetFromObjectInArray) { + auto update = fromjson("{$unset: {'a.0.b': 1}}"); + const CollatorInterface* collator = nullptr; + UnsetNode node; + ASSERT_OK(node.init(update["$unset"]["a.0.b"], collator)); + + Document doc(fromjson("{a: [{b: 1}]}")); + FieldRef pathToCreate(""); + FieldRef pathTaken("a.0.b"); + StringData matchedField; + auto fromReplication = false; + UpdateIndexData indexData; + indexData.addPath("a"); + Document logDoc; + LogBuilder logBuilder(logDoc.root()); + auto indexesAffected = false; + auto noop = false; + ASSERT_OK(node.apply(doc.root()["a"]["0"]["b"], + &pathToCreate, + &pathTaken, + matchedField, + fromReplication, + &indexData, + &logBuilder, + &indexesAffected, + &noop)); + ASSERT_FALSE(noop); + ASSERT_TRUE(indexesAffected); + ASSERT_EQUALS(fromjson("{a:[{}]}"), doc); + ASSERT_FALSE(doc.isInPlaceModeEnabled()); + ASSERT_EQUALS(fromjson("{$unset: {'a.0.b': true}}"), logDoc); +} + +TEST(UnsetNodeTest, CanUnsetInvalidField) { + auto update = fromjson("{$unset: {'a.$.$b': true}}"); + const CollatorInterface* collator = nullptr; + UnsetNode node; + ASSERT_OK(node.init(update["$unset"]["a.$.$b"], collator)); + + Document doc(fromjson("{b: 1, a: [{$b: 1}]}")); + FieldRef pathToCreate(""); + FieldRef pathTaken("a.0.$b"); + StringData matchedField = "0"; + auto fromReplication = false; + UpdateIndexData indexData; + indexData.addPath("a"); + Document logDoc; + LogBuilder logBuilder(logDoc.root()); + auto indexesAffected = false; + auto noop = false; + ASSERT_OK(node.apply(doc.root()["a"]["0"]["$b"], + &pathToCreate, + &pathTaken, + matchedField, + fromReplication, + &indexData, + &logBuilder, + &indexesAffected, + &noop)); + ASSERT_FALSE(noop); + ASSERT_TRUE(indexesAffected); + ASSERT_EQUALS(fromjson("{b: 1, a: [{}]}"), doc); + ASSERT_FALSE(doc.isInPlaceModeEnabled()); + ASSERT_EQUALS(fromjson("{$unset: {'a.0.$b': true}}"), logDoc); +} + +TEST(UnsetNodeTest, ApplyNoIndexDataNoLogBuilder) { + auto update = fromjson("{$unset: {a: 1}}"); + const CollatorInterface* collator = nullptr; + UnsetNode node; + ASSERT_OK(node.init(update["$unset"]["a"], collator)); + + Document doc(fromjson("{a: 5}")); + FieldRef pathToCreate(""); + FieldRef pathTaken("a"); + StringData matchedField; + auto fromReplication = false; + const UpdateIndexData* indexData = nullptr; + LogBuilder* logBuilder = nullptr; + auto indexesAffected = false; + auto noop = false; + ASSERT_OK(node.apply(doc.root()["a"], + &pathToCreate, + &pathTaken, + matchedField, + fromReplication, + indexData, + logBuilder, + &indexesAffected, + &noop)); + ASSERT_FALSE(noop); + ASSERT_FALSE(indexesAffected); + ASSERT_EQUALS(fromjson("{}"), doc); + ASSERT_FALSE(doc.isInPlaceModeEnabled()); +} + +TEST(UnsetNodeTest, ApplyDoesNotAffectIndexes) { + auto update = fromjson("{$unset: {a: 1}}"); + const CollatorInterface* collator = nullptr; + UnsetNode node; + ASSERT_OK(node.init(update["$unset"]["a"], collator)); + + Document doc(fromjson("{a: 5}")); + FieldRef pathToCreate(""); + FieldRef pathTaken("a"); + StringData matchedField; + auto fromReplication = false; + UpdateIndexData indexData; + indexData.addPath("b"); + Document logDoc; + LogBuilder logBuilder(logDoc.root()); + auto indexesAffected = false; + auto noop = false; + ASSERT_OK(node.apply(doc.root()["a"], + &pathToCreate, + &pathTaken, + matchedField, + fromReplication, + &indexData, + &logBuilder, + &indexesAffected, + &noop)); + ASSERT_FALSE(noop); + ASSERT_FALSE(indexesAffected); + ASSERT_EQUALS(fromjson("{}"), doc); + ASSERT_FALSE(doc.isInPlaceModeEnabled()); + ASSERT_EQUALS(fromjson("{$unset: {a: true}}"), logDoc); +} + +TEST(UnsetNodeTest, ApplyFieldWithDot) { + auto update = fromjson("{$unset: {'a.b': 1}}"); + const CollatorInterface* collator = nullptr; + UnsetNode node; + ASSERT_OK(node.init(update["$unset"]["a.b"], collator)); + + Document doc(fromjson("{'a.b':4, a: {b: 2}}")); + FieldRef pathToCreate(""); + FieldRef pathTaken("a.b"); + StringData matchedField; + auto fromReplication = false; + UpdateIndexData indexData; + indexData.addPath("a"); + Document logDoc; + LogBuilder logBuilder(logDoc.root()); + auto indexesAffected = false; + auto noop = false; + ASSERT_OK(node.apply(doc.root()["a"]["b"], + &pathToCreate, + &pathTaken, + matchedField, + fromReplication, + &indexData, + &logBuilder, + &indexesAffected, + &noop)); + ASSERT_FALSE(noop); + ASSERT_TRUE(indexesAffected); + ASSERT_EQUALS(fromjson("{'a.b':4, a: {}}"), doc); + ASSERT_FALSE(doc.isInPlaceModeEnabled()); + ASSERT_EQUALS(fromjson("{$unset: {'a.b': true}}"), logDoc); +} + +} // namespace diff --git a/src/mongo/db/update/update_object_node_test.cpp b/src/mongo/db/update/update_object_node_test.cpp index 15e146f5127..be71ef001fe 100644 --- a/src/mongo/db/update/update_object_node_test.cpp +++ b/src/mongo/db/update/update_object_node_test.cpp @@ -53,7 +53,7 @@ TEST(UpdateObjectNodeTest, InvalidPathFailsToParse) { ASSERT_EQ(result.getStatus().reason(), "An empty update path is not valid."); } -TEST(UpdateObjectNodeTest, ValidPathParsesSuccessfully) { +TEST(UpdateObjectNodeTest, ValidSetPathParsesSuccessfully) { auto update = fromjson("{$set: {'a.b': 5}}"); const CollatorInterface* collator = nullptr; UpdateObjectNode root; @@ -61,6 +61,14 @@ TEST(UpdateObjectNodeTest, ValidPathParsesSuccessfully) { &root, modifiertable::ModifierType::MOD_SET, update["$set"]["a.b"], collator)); } +TEST(UpdateObjectNodeTest, ValidUnsetPathParsesSuccessfully) { + auto update = fromjson("{$unset: {'a.b': 5}}"); + const CollatorInterface* collator = nullptr; + UpdateObjectNode root; + ASSERT_OK(UpdateObjectNode::parseAndMerge( + &root, modifiertable::ModifierType::MOD_UNSET, update["$unset"]["a.b"], collator)); +} + TEST(UpdateObjectNodeTest, MultiplePositionalElementsFailToParse) { auto update = fromjson("{$set: {'a.$.b.$': 5}}"); const CollatorInterface* collator = nullptr; |