diff options
author | David Storch <david.storch@10gen.com> | 2017-06-28 12:31:01 -0400 |
---|---|---|
committer | David Storch <david.storch@10gen.com> | 2017-06-29 15:23:55 -0400 |
commit | f37ca39a93b5344992384392b3e57eed9c4cd1b0 (patch) | |
tree | 8aa5792d3741b6ae9a3a737baebea6f3dc27af40 /src | |
parent | 57e3704f443df1fb071e0af81421ce2999fad767 (diff) | |
download | mongo-f37ca39a93b5344992384392b3e57eed9c4cd1b0.tar.gz |
SERVER-28769 Implement PopNode.
Diffstat (limited to 'src')
-rw-r--r-- | src/mongo/db/update/SConscript | 60 | ||||
-rw-r--r-- | src/mongo/db/update/modifier_table.cpp | 3 | ||||
-rw-r--r-- | src/mongo/db/update/path_support.cpp | 30 | ||||
-rw-r--r-- | src/mongo/db/update/path_support.h | 6 | ||||
-rw-r--r-- | src/mongo/db/update/pop_node.cpp | 121 | ||||
-rw-r--r-- | src/mongo/db/update/pop_node.h | 68 | ||||
-rw-r--r-- | src/mongo/db/update/pop_node_test.cpp | 537 | ||||
-rw-r--r-- | src/mongo/db/update/update_object_node_test.cpp | 59 |
8 files changed, 821 insertions, 63 deletions
diff --git a/src/mongo/db/update/SConscript b/src/mongo/db/update/SConscript index 03ae64ac717..5f6dc7ff8cf 100644 --- a/src/mongo/db/update/SConscript +++ b/src/mongo/db/update/SConscript @@ -59,11 +59,12 @@ env.Library( 'arithmetic_node.cpp', 'modifier_table.cpp', 'path_creating_node.cpp', + 'pop_node.cpp', 'set_node.cpp', 'unset_node.cpp', - 'update_node.cpp', 'update_array_node.cpp', 'update_internal_node.cpp', + 'update_node.cpp', 'update_object_node.cpp', ], LIBDEPS=[ @@ -75,8 +76,16 @@ env.Library( ) env.CppUnitTest( - target='addtoset_node_test', - source='addtoset_node_test.cpp', + target='update_nodes_test', + source=[ + 'addtoset_node_test.cpp', + 'arithmetic_node_test.cpp', + 'pop_node_test.cpp', + 'set_node_test.cpp', + 'unset_node_test.cpp', + 'update_array_node_test.cpp', + 'update_object_node_test.cpp', + ], LIBDEPS=[ '$BUILD_DIR/mongo/bson/mutable/mutable_bson_test_utils', '$BUILD_DIR/mongo/db/query/collation/collator_interface_mock', @@ -85,14 +94,6 @@ env.CppUnitTest( ) env.CppUnitTest( - target='arithmetic_node_test', - source='arithmetic_node_test.cpp', - LIBDEPS=[ - '$BUILD_DIR/mongo/bson/mutable/mutable_bson_test_utils', - 'update', - ], -) -env.CppUnitTest( target='modifier_table_test', source='modifier_table_test.cpp', LIBDEPS=[ @@ -100,43 +101,6 @@ env.CppUnitTest( ], ) -env.CppUnitTest( - target='set_node_test', - source='set_node_test.cpp', - LIBDEPS=[ - '$BUILD_DIR/mongo/bson/mutable/mutable_bson_test_utils', - 'update', - ], -) - -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=[ - '$BUILD_DIR/mongo/bson/mutable/mutable_bson_test_utils', - '$BUILD_DIR/mongo/db/query/collation/collator_interface_mock', - 'update', - ], -) - -env.CppUnitTest( - target='update_array_node_test', - source='update_array_node_test.cpp', - LIBDEPS=[ - '$BUILD_DIR/mongo/bson/mutable/mutable_bson_test_utils', - 'update', - ], -) - env.Library( target='update_driver', source=[ diff --git a/src/mongo/db/update/modifier_table.cpp b/src/mongo/db/update/modifier_table.cpp index 52bd035437a..27badf33643 100644 --- a/src/mongo/db/update/modifier_table.cpp +++ b/src/mongo/db/update/modifier_table.cpp @@ -48,6 +48,7 @@ #include "mongo/db/ops/modifier_unset.h" #include "mongo/db/update/addtoset_node.h" #include "mongo/db/update/arithmetic_node.h" +#include "mongo/db/update/pop_node.h" #include "mongo/db/update/set_node.h" #include "mongo/db/update/unset_node.h" #include "mongo/platform/unordered_map.h" @@ -188,6 +189,8 @@ std::unique_ptr<UpdateLeafNode> makeUpdateLeafNode(ModifierType modType) { return stdx::make_unique<ArithmeticNode>(ArithmeticNode::ArithmeticOp::kAdd); case MOD_MUL: return stdx::make_unique<ArithmeticNode>(ArithmeticNode::ArithmeticOp::kMultiply); + case MOD_POP: + return stdx::make_unique<PopNode>(); case MOD_SET: return stdx::make_unique<SetNode>(); case MOD_UNSET: diff --git a/src/mongo/db/update/path_support.cpp b/src/mongo/db/update/path_support.cpp index 993d4195bc4..5538f581385 100644 --- a/src/mongo/db/update/path_support.cpp +++ b/src/mongo/db/update/path_support.cpp @@ -43,19 +43,6 @@ using mongoutils::str::stream; namespace { -bool isNumeric(StringData str, size_t* num) { - size_t res = 0; - for (size_t i = 0; i < str.size(); ++i) { - if (str[i] < '0' || str[i] > '9') { - return false; - } else { - res = res * 10 + (str[i] - '0'); - } - } - *num = res; - return true; -} - Status maybePadTo(mutablebson::Element* elemArray, size_t sizeRequired) { dassert(elemArray->getType() == Array); @@ -82,6 +69,19 @@ Status maybePadTo(mutablebson::Element* elemArray, size_t sizeRequired) { } // unnamed namespace +bool isNumericPathComponent(StringData str, size_t* num) { + size_t res = 0; + for (size_t i = 0; i < str.size(); ++i) { + if (str[i] < '0' || str[i] > '9') { + return false; + } else { + res = res * 10 + (str[i] - '0'); + } + } + *num = res; + return true; +} + Status findLongestPrefix(const FieldRef& prefix, mutablebson::Element root, size_t* idxFound, @@ -111,7 +111,7 @@ Status findLongestPrefix(const FieldRef& prefix, break; case Array: - if (!isNumeric(prefixPart, &numericPart)) { + if (!isNumericPathComponent(prefixPart, &numericPart)) { viable = false; } else { curr = prev[numericPart]; @@ -184,7 +184,7 @@ Status createPathAt(const FieldRef& prefix, bool inArray = false; if (elemFound.getType() == mongo::Array) { size_t newIdx = 0; - if (!isNumeric(prefix.getPart(idxFound), &newIdx)) { + if (!isNumericPathComponent(prefix.getPart(idxFound), &newIdx)) { return Status(ErrorCodes::PathNotViable, str::stream() << "Cannot create field '" << prefix.getPart(idxFound) << "' in element {" diff --git a/src/mongo/db/update/path_support.h b/src/mongo/db/update/path_support.h index 6fbd95ec0a9..69106ea5010 100644 --- a/src/mongo/db/update/path_support.h +++ b/src/mongo/db/update/path_support.h @@ -183,6 +183,12 @@ BSONElement findParentEqualityElement(const EqualityMatches& equalities, */ Status addEqualitiesToDoc(const EqualityMatches& equalities, mutablebson::Document* doc); +/** + * Returns true if the path component represents an array index, and returns that array index in + * 'num'. Otherwise, returns false. + */ +bool isNumericPathComponent(StringData pathComponent, size_t* num); + } // namespace pathsupport } // namespace mongo diff --git a/src/mongo/db/update/pop_node.cpp b/src/mongo/db/update/pop_node.cpp new file mode 100644 index 00000000000..c0a81132118 --- /dev/null +++ b/src/mongo/db/update/pop_node.cpp @@ -0,0 +1,121 @@ +/** + * 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/pop_node.h" + +#include "mongo/db/update/path_support.h" + +namespace mongo { + +Status PopNode::init(BSONElement modExpr, const CollatorInterface* collator) { + _popFromFront = modExpr.isNumber() && modExpr.number() < 0; + return Status::OK(); +} + +void PopNode::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 (pathTaken->empty()) { + // No components of the path existed. The pop is treated as a no-op in this case. + *noop = true; + return; + } + + if (!pathToCreate->empty()) { + // There were path components which we could not traverse. If 'element' is a nested object + // which does not contain 'pathToCreate', then this is a no-op (e.g. {$pop: {"a.b.c": 1}} + // for document {a: {b: {}}}). + // + // If the element is an array, but the numeric path does not exist, then this is also a + // no-op (e.g. {$pop: {"a.2.b": 1}} for document {a: [{b: 0}, {b: 1}]}). + // + // Otherwise, this the path contains a blocking leaf or array element, which is an error. + if (element.getType() == BSONType::Object) { + *noop = true; + return; + } + + size_t arrayIndex; + if (element.getType() == BSONType::Array && + pathsupport::isNumericPathComponent(pathToCreate->getPart(0), &arrayIndex)) { + *noop = true; + return; + } + + uasserted(ErrorCodes::PathNotViable, + str::stream() << "Cannot use the part (" << pathToCreate->getPart(0) << ") of (" + << pathTaken->dottedField() + << "." + << pathToCreate->dottedField() + << ") to traverse the element ({" + << element.toString() + << "})"); + } + + invariant(!pathTaken->empty()); + invariant(pathToCreate->empty()); + + // The full path existed, but we must fail if the element at that path is not an array. + invariant(element.ok()); + uassert(ErrorCodes::TypeMismatch, + str::stream() << "Path '" << pathTaken->dottedField() + << "' contains an element of non-array type '" + << typeName(element.getType()) + << "'", + element.getType() == BSONType::Array); + + if (!element.hasChildren()) { + // The path exists and contains an array, but the array is empty. + *noop = true; + return; + } + + if (indexData && indexData->mightBeIndexed(pathTaken->dottedField())) { + *indexesAffected = true; + } + + auto elementToRemove = _popFromFront ? element.leftChild() : element.rightChild(); + invariantOK(elementToRemove.remove()); + + if (logBuilder) { + uassertStatusOK(logBuilder->addToSetsWithNewFieldName(pathTaken->dottedField(), element)); + } +} + +} // namespace mongo diff --git a/src/mongo/db/update/pop_node.h b/src/mongo/db/update/pop_node.h new file mode 100644 index 00000000000..99f6f6fbb32 --- /dev/null +++ b/src/mongo/db/update/pop_node.h @@ -0,0 +1,68 @@ +/** + * 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 { + +class PopNode final : public UpdateLeafNode { +public: + Status init(BSONElement modExpr, const CollatorInterface* collator) final; + + void apply(mutablebson::Element element, + FieldRef* pathToCreate, + FieldRef* pathTaken, + StringData matchedField, + bool fromReplication, + const UpdateIndexData* indexData, + LogBuilder* logBuilder, + bool* indexesAffected, + bool* noop) const final; + + std::unique_ptr<UpdateNode> clone() const final { + return stdx::make_unique<PopNode>(*this); + } + + void setCollator(const CollatorInterface* collator) final {} + + /** + * Returns true if this node is popping the first element off the front of the array. Returns + * false if this node is popping the last element off the back of the array. + */ + bool popFromFront() const { + return _popFromFront; + } + +private: + bool _popFromFront = true; +}; + +} // namespace mongo diff --git a/src/mongo/db/update/pop_node_test.cpp b/src/mongo/db/update/pop_node_test.cpp new file mode 100644 index 00000000000..733f4082112 --- /dev/null +++ b/src/mongo/db/update/pop_node_test.cpp @@ -0,0 +1,537 @@ +/** + * 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/pop_node.h" + +#include "mongo/bson/json.h" +#include "mongo/bson/mutable/mutable_bson_test_utils.h" +#include "mongo/unittest/death_test.h" +#include "mongo/unittest/unittest.h" + +namespace mongo { +namespace { + +namespace mmb = mongo::mutablebson; + +TEST(PopNodeTest, InitSucceedsPositiveOne) { + auto update = fromjson("{$pop: {a: 1}}"); + const CollatorInterface* collator = nullptr; + PopNode popNode; + ASSERT_OK(popNode.init(update["$pop"]["a"], collator)); + ASSERT_FALSE(popNode.popFromFront()); +} + +TEST(PopNodeTest, InitSucceedsNegativeOne) { + auto update = fromjson("{$pop: {a: -1}}"); + const CollatorInterface* collator = nullptr; + PopNode popNode; + ASSERT_OK(popNode.init(update["$pop"]["a"], collator)); + ASSERT_TRUE(popNode.popFromFront()); +} + +TEST(PopNodeTest, InitSucceedsZero) { + auto update = fromjson("{$pop: {a: 0}}"); + const CollatorInterface* collator = nullptr; + PopNode popNode; + ASSERT_OK(popNode.init(update["$pop"]["a"], collator)); + ASSERT_FALSE(popNode.popFromFront()); +} + +TEST(PopNodeTest, InitSucceedsString) { + auto update = fromjson("{$pop: {a: 'foo'}}"); + const CollatorInterface* collator = nullptr; + PopNode popNode; + ASSERT_OK(popNode.init(update["$pop"]["a"], collator)); + ASSERT_FALSE(popNode.popFromFront()); +} + +TEST(PopNodeTest, InitSucceedsNestedObject) { + auto update = fromjson("{$pop: {a: {b: 1}}}"); + const CollatorInterface* collator = nullptr; + PopNode popNode; + ASSERT_OK(popNode.init(update["$pop"]["a"], collator)); + ASSERT_FALSE(popNode.popFromFront()); +} + +TEST(PopNodeTest, InitSucceedsNestedArray) { + auto update = fromjson("{$pop: {a: [{b: 1}]}}"); + const CollatorInterface* collator = nullptr; + PopNode popNode; + ASSERT_OK(popNode.init(update["$pop"]["a"], collator)); + ASSERT_FALSE(popNode.popFromFront()); +} + +TEST(PopNodeTest, NoopWhenFirstPathComponentDoesNotExist) { + auto update = fromjson("{$pop: {'a.b': 1}}"); + const CollatorInterface* collator = nullptr; + PopNode popNode; + ASSERT_OK(popNode.init(update["$pop"]["a.b"], collator)); + + mmb::Document doc(fromjson("{b: [1, 2, 3]}")); + FieldRef pathToCreate("a.b"); + FieldRef pathTaken(""); + StringData matchedField; + auto fromReplication = false; + UpdateIndexData indexData; + indexData.addPath("a.b"); + mmb::Document logDoc; + LogBuilder logBuilder(logDoc.root()); + bool indexesAffected; + bool noop; + popNode.apply(doc.root(), + &pathToCreate, + &pathTaken, + matchedField, + fromReplication, + &indexData, + &logBuilder, + &indexesAffected, + &noop); + ASSERT_TRUE(noop); + ASSERT_FALSE(indexesAffected); + ASSERT_EQUALS(fromjson("{b: [1, 2, 3]}"), doc); + ASSERT_EQUALS(fromjson("{}"), logDoc); +} + +TEST(PopNodeTest, NoopWhenPathPartiallyExists) { + auto update = fromjson("{$pop: {'a.b.c': 1}}"); + const CollatorInterface* collator = nullptr; + PopNode popNode; + ASSERT_OK(popNode.init(update["$pop"]["a.b.c"], collator)); + + mmb::Document doc(fromjson("{a: {}}")); + FieldRef pathToCreate("b.c"); + FieldRef pathTaken("a"); + StringData matchedField; + auto fromReplication = false; + UpdateIndexData indexData; + indexData.addPath("a.b.c"); + mmb::Document logDoc; + LogBuilder logBuilder(logDoc.root()); + bool indexesAffected; + bool noop; + popNode.apply(doc.root()["a"], + &pathToCreate, + &pathTaken, + matchedField, + fromReplication, + &indexData, + &logBuilder, + &indexesAffected, + &noop); + ASSERT_TRUE(noop); + ASSERT_FALSE(indexesAffected); + ASSERT_EQUALS(fromjson("{a: {}}"), doc); + ASSERT_EQUALS(fromjson("{}"), logDoc); +} + +TEST(PopNodeTest, NoopWhenNumericalPathComponentExceedsArrayLength) { + auto update = fromjson("{$pop: {'a.0': 1}}"); + const CollatorInterface* collator = nullptr; + PopNode popNode; + ASSERT_OK(popNode.init(update["$pop"]["a.0"], collator)); + + mmb::Document doc(fromjson("{a: []}")); + FieldRef pathToCreate("0"); + FieldRef pathTaken("a"); + StringData matchedField; + auto fromReplication = false; + UpdateIndexData indexData; + indexData.addPath("a.0"); + mmb::Document logDoc; + LogBuilder logBuilder(logDoc.root()); + bool indexesAffected; + bool noop; + popNode.apply(doc.root()["a"], + &pathToCreate, + &pathTaken, + matchedField, + fromReplication, + &indexData, + &logBuilder, + &indexesAffected, + &noop); + ASSERT_TRUE(noop); + ASSERT_FALSE(indexesAffected); + ASSERT_EQUALS(fromjson("{a: []}"), doc); + ASSERT_EQUALS(fromjson("{}"), logDoc); +} + +TEST(PopNodeTest, ThrowsWhenPathIsBlockedByAScalar) { + auto update = fromjson("{$pop: {'a.b': 1}}"); + const CollatorInterface* collator = nullptr; + PopNode popNode; + ASSERT_OK(popNode.init(update["$pop"]["a.b"], collator)); + + mmb::Document doc(fromjson("{a: 'foo'}")); + FieldRef pathToCreate("b"); + FieldRef pathTaken("a"); + StringData matchedField; + auto fromReplication = false; + UpdateIndexData indexData; + indexData.addPath("a.b"); + mmb::Document logDoc; + LogBuilder logBuilder(logDoc.root()); + bool indexesAffected; + bool noop; + ASSERT_THROWS_CODE_AND_WHAT( + popNode.apply(doc.root()["a"], + &pathToCreate, + &pathTaken, + matchedField, + fromReplication, + &indexData, + &logBuilder, + &indexesAffected, + &noop), + UserException, + ErrorCodes::PathNotViable, + "Cannot use the part (b) of (a.b) to traverse the element ({a: \"foo\"})"); +} + +DEATH_TEST(PopNodeTest, NonOkElementWhenPathExistsIsFatal, "Invariant failure element.ok()") { + auto update = fromjson("{$pop: {'a.b': 1}}"); + const CollatorInterface* collator = nullptr; + PopNode popNode; + ASSERT_OK(popNode.init(update["$pop"]["a.b"], collator)); + + mmb::Document doc(fromjson("{a: {b: [1, 2, 3]}}")); + FieldRef pathToCreate(""); + FieldRef pathTaken("a.b"); + StringData matchedField; + auto fromReplication = false; + UpdateIndexData indexData; + indexData.addPath("a.b"); + mmb::Document logDoc; + LogBuilder logBuilder(logDoc.root()); + bool indexesAffected; + bool noop; + popNode.apply(doc.end(), + &pathToCreate, + &pathTaken, + matchedField, + fromReplication, + &indexData, + &logBuilder, + &indexesAffected, + &noop); +} + +TEST(PopNodeTest, ThrowsWhenPathExistsButDoesNotContainAnArray) { + auto update = fromjson("{$pop: {'a.b': 1}}"); + const CollatorInterface* collator = nullptr; + PopNode popNode; + ASSERT_OK(popNode.init(update["$pop"]["a.b"], collator)); + + mmb::Document doc(fromjson("{a: {b: 'foo'}}")); + FieldRef pathToCreate(""); + FieldRef pathTaken("a.b"); + StringData matchedField; + auto fromReplication = false; + UpdateIndexData indexData; + indexData.addPath("a.b"); + mmb::Document logDoc; + LogBuilder logBuilder(logDoc.root()); + bool indexesAffected; + bool noop; + ASSERT_THROWS_CODE_AND_WHAT(popNode.apply(doc.root()["a"]["b"], + &pathToCreate, + &pathTaken, + matchedField, + fromReplication, + &indexData, + &logBuilder, + &indexesAffected, + &noop), + UserException, + ErrorCodes::TypeMismatch, + "Path 'a.b' contains an element of non-array type 'string'"); +} + +TEST(PopNodeTest, NoopWhenPathContainsAnEmptyArray) { + auto update = fromjson("{$pop: {'a.b': 1}}"); + const CollatorInterface* collator = nullptr; + PopNode popNode; + ASSERT_OK(popNode.init(update["$pop"]["a.b"], collator)); + + mmb::Document doc(fromjson("{a: {b: []}}")); + FieldRef pathToCreate(""); + FieldRef pathTaken("a.b"); + StringData matchedField; + auto fromReplication = false; + UpdateIndexData indexData; + indexData.addPath("a.b"); + mmb::Document logDoc; + LogBuilder logBuilder(logDoc.root()); + bool indexesAffected; + bool noop; + popNode.apply(doc.root()["a"]["b"], + &pathToCreate, + &pathTaken, + matchedField, + fromReplication, + &indexData, + &logBuilder, + &indexesAffected, + &noop); + ASSERT_TRUE(noop); + ASSERT_FALSE(indexesAffected); + ASSERT_EQUALS(fromjson("{a: {b: []}}"), doc); + ASSERT_EQUALS(fromjson("{}"), logDoc); +} + +TEST(PopNodeTest, PopsSingleElementFromTheBack) { + auto update = fromjson("{$pop: {'a.b': 1}}"); + const CollatorInterface* collator = nullptr; + PopNode popNode; + ASSERT_OK(popNode.init(update["$pop"]["a.b"], collator)); + ASSERT_FALSE(popNode.popFromFront()); + + mmb::Document doc(fromjson("{a: {b: [1]}}")); + FieldRef pathToCreate(""); + FieldRef pathTaken("a.b"); + StringData matchedField; + auto fromReplication = false; + UpdateIndexData indexData; + indexData.addPath("a.b"); + mmb::Document logDoc; + LogBuilder logBuilder(logDoc.root()); + bool indexesAffected; + bool noop; + popNode.apply(doc.root()["a"]["b"], + &pathToCreate, + &pathTaken, + matchedField, + fromReplication, + &indexData, + &logBuilder, + &indexesAffected, + &noop); + ASSERT_FALSE(noop); + ASSERT_TRUE(indexesAffected); + ASSERT_EQUALS(fromjson("{a: {b: []}}"), doc); + ASSERT_EQUALS(fromjson("{$set: {'a.b': []}}"), logDoc); +} + +TEST(PopNodeTest, PopsSingleElementFromTheFront) { + auto update = fromjson("{$pop: {'a.b': -1}}"); + const CollatorInterface* collator = nullptr; + PopNode popNode; + ASSERT_OK(popNode.init(update["$pop"]["a.b"], collator)); + ASSERT_TRUE(popNode.popFromFront()); + + mmb::Document doc(fromjson("{a: {b: [[1]]}}")); + FieldRef pathToCreate(""); + FieldRef pathTaken("a.b"); + StringData matchedField; + auto fromReplication = false; + UpdateIndexData indexData; + indexData.addPath("a"); + mmb::Document logDoc; + LogBuilder logBuilder(logDoc.root()); + bool indexesAffected; + bool noop; + popNode.apply(doc.root()["a"]["b"], + &pathToCreate, + &pathTaken, + matchedField, + fromReplication, + &indexData, + &logBuilder, + &indexesAffected, + &noop); + ASSERT_FALSE(noop); + ASSERT_TRUE(indexesAffected); + ASSERT_EQUALS(fromjson("{a: {b: []}}"), doc); + ASSERT_EQUALS(fromjson("{$set: {'a.b': []}}"), logDoc); +} + +TEST(PopNodeTest, PopsFromTheBackOfMultiElementArray) { + auto update = fromjson("{$pop: {'a.b': 1}}"); + const CollatorInterface* collator = nullptr; + PopNode popNode; + ASSERT_OK(popNode.init(update["$pop"]["a.b"], collator)); + ASSERT_FALSE(popNode.popFromFront()); + + mmb::Document doc(fromjson("{a: {b: [1, 2, 3]}}")); + FieldRef pathToCreate(""); + FieldRef pathTaken("a.b"); + StringData matchedField; + auto fromReplication = false; + UpdateIndexData indexData; + indexData.addPath("a.b.c"); + mmb::Document logDoc; + LogBuilder logBuilder(logDoc.root()); + bool indexesAffected; + bool noop; + popNode.apply(doc.root()["a"]["b"], + &pathToCreate, + &pathTaken, + matchedField, + fromReplication, + &indexData, + &logBuilder, + &indexesAffected, + &noop); + ASSERT_FALSE(noop); + ASSERT_TRUE(indexesAffected); + ASSERT_EQUALS(fromjson("{a: {b: [1, 2]}}"), doc); + ASSERT_EQUALS(fromjson("{$set: {'a.b': [1, 2]}}"), logDoc); +} + +TEST(PopNodeTest, PopsFromTheFrontOfMultiElementArray) { + auto update = fromjson("{$pop: {'a.b': -1}}"); + const CollatorInterface* collator = nullptr; + PopNode popNode; + ASSERT_OK(popNode.init(update["$pop"]["a.b"], collator)); + ASSERT_TRUE(popNode.popFromFront()); + + mmb::Document doc(fromjson("{a: {b: [1, 2, 3]}}")); + FieldRef pathToCreate(""); + FieldRef pathTaken("a.b"); + StringData matchedField; + auto fromReplication = false; + UpdateIndexData indexData; + indexData.addPath("a.b"); + mmb::Document logDoc; + LogBuilder logBuilder(logDoc.root()); + bool indexesAffected; + bool noop; + popNode.apply(doc.root()["a"]["b"], + &pathToCreate, + &pathTaken, + matchedField, + fromReplication, + &indexData, + &logBuilder, + &indexesAffected, + &noop); + ASSERT_FALSE(noop); + ASSERT_TRUE(indexesAffected); + ASSERT_EQUALS(fromjson("{a: {b: [2, 3]}}"), doc); + ASSERT_EQUALS(fromjson("{$set: {'a.b': [2, 3]}}"), logDoc); +} + +TEST(PopNodeTest, PopsFromTheFrontOfMultiElementArrayWithoutAffectingIndexes) { + auto update = fromjson("{$pop: {'a.b': -1}}"); + const CollatorInterface* collator = nullptr; + PopNode popNode; + ASSERT_OK(popNode.init(update["$pop"]["a.b"], collator)); + ASSERT_TRUE(popNode.popFromFront()); + + mmb::Document doc(fromjson("{a: {b: [1, 2, 3]}}")); + FieldRef pathToCreate(""); + FieldRef pathTaken("a.b"); + StringData matchedField; + auto fromReplication = false; + UpdateIndexData indexData; + indexData.addPath("unrelated.path"); + mmb::Document logDoc; + LogBuilder logBuilder(logDoc.root()); + bool indexesAffected; + bool noop; + popNode.apply(doc.root()["a"]["b"], + &pathToCreate, + &pathTaken, + matchedField, + fromReplication, + &indexData, + &logBuilder, + &indexesAffected, + &noop); + ASSERT_FALSE(noop); + ASSERT_FALSE(indexesAffected); + ASSERT_EQUALS(fromjson("{a: {b: [2, 3]}}"), doc); + ASSERT_EQUALS(fromjson("{$set: {'a.b': [2, 3]}}"), logDoc); +} + +TEST(PopNodeTest, SucceedsWithNullUpdateIndexData) { + auto update = fromjson("{$pop: {'a.b': 1}}"); + const CollatorInterface* collator = nullptr; + PopNode popNode; + ASSERT_OK(popNode.init(update["$pop"]["a.b"], collator)); + ASSERT_FALSE(popNode.popFromFront()); + + mmb::Document doc(fromjson("{a: {b: [1, 2, 3]}}")); + FieldRef pathToCreate(""); + FieldRef pathTaken("a.b"); + StringData matchedField; + auto fromReplication = false; + mmb::Document logDoc; + LogBuilder logBuilder(logDoc.root()); + bool indexesAffected; + bool noop; + popNode.apply(doc.root()["a"]["b"], + &pathToCreate, + &pathTaken, + matchedField, + fromReplication, + nullptr, + &logBuilder, + &indexesAffected, + &noop); + ASSERT_FALSE(noop); + ASSERT_FALSE(indexesAffected); + ASSERT_EQUALS(fromjson("{a: {b: [1, 2]}}"), doc); + ASSERT_EQUALS(fromjson("{$set: {'a.b': [1, 2]}}"), logDoc); +} + +TEST(PopNodeTest, SucceedsWithNullLogBuilder) { + auto update = fromjson("{$pop: {'a.b': 1}}"); + const CollatorInterface* collator = nullptr; + PopNode popNode; + ASSERT_OK(popNode.init(update["$pop"]["a.b"], collator)); + ASSERT_FALSE(popNode.popFromFront()); + + mmb::Document doc(fromjson("{a: {b: [1, 2, 3]}}")); + FieldRef pathToCreate(""); + FieldRef pathTaken("a.b"); + StringData matchedField; + auto fromReplication = false; + UpdateIndexData indexData; + indexData.addPath("a.b.c"); + bool indexesAffected; + bool noop; + popNode.apply(doc.root()["a"]["b"], + &pathToCreate, + &pathTaken, + matchedField, + fromReplication, + &indexData, + nullptr, + &indexesAffected, + &noop); + ASSERT_FALSE(noop); + ASSERT_TRUE(indexesAffected); + ASSERT_EQUALS(fromjson("{a: {b: [1, 2]}}"), doc); +} + +} // namespace +} // namespace mongo diff --git a/src/mongo/db/update/update_object_node_test.cpp b/src/mongo/db/update/update_object_node_test.cpp index 7a02c0f935e..f3a08e6baee 100644 --- a/src/mongo/db/update/update_object_node_test.cpp +++ b/src/mongo/db/update/update_object_node_test.cpp @@ -132,6 +132,20 @@ TEST(UpdateObjectNodeTest, ValidAddToSetPathParsesSuccessfully) { foundIdentifiers)); } +TEST(UpdateObjectNodeTest, ValidPopPathParsesSuccessfully) { + auto update = fromjson("{$pop: {'a.b': 5}}"); + const CollatorInterface* collator = nullptr; + std::map<StringData, std::unique_ptr<ArrayFilter>> arrayFilters; + std::set<std::string> foundIdentifiers; + UpdateObjectNode root; + ASSERT_OK(UpdateObjectNode::parseAndMerge(&root, + modifiertable::ModifierType::MOD_POP, + update["$pop"]["a.b"], + collator, + arrayFilters, + foundIdentifiers)); +} + TEST(UpdateObjectNodeTest, MultiplePositionalElementsFailToParse) { auto update = fromjson("{$set: {'a.$.b.$': 5}}"); const CollatorInterface* collator = nullptr; @@ -2682,5 +2696,50 @@ TEST(UpdateObjectNodeTest, ApplyDoNotUseStoredMergedPositional) { logDoc2.getObject()); } +TEST(UpdateObjectNodeTest, SetAndPopModifiersWithCommonPrefixApplySuccessfully) { + auto update = fromjson("{$set: {'a.b': 5}, $pop: {'a.c': -1}}"); + const CollatorInterface* collator = nullptr; + std::map<StringData, std::unique_ptr<ArrayFilter>> arrayFilters; + std::set<std::string> foundIdentifiers; + UpdateObjectNode root; + ASSERT_OK(UpdateObjectNode::parseAndMerge(&root, + modifiertable::ModifierType::MOD_SET, + update["$set"]["a.b"], + collator, + arrayFilters, + foundIdentifiers)); + ASSERT_OK(UpdateObjectNode::parseAndMerge(&root, + modifiertable::ModifierType::MOD_POP, + update["$pop"]["a.c"], + collator, + arrayFilters, + foundIdentifiers)); + + Document doc(fromjson("{a: {b: 3, c: [1, 2, 3, 4]}}")); + FieldRef pathToCreate(""); + FieldRef pathTaken(""); + StringData matchedField; + auto fromReplication = false; + const UpdateIndexData* indexData = nullptr; + Document logDoc; + LogBuilder logBuilder(logDoc.root()); + bool indexesAffected; + bool noop; + root.apply(doc.root(), + &pathToCreate, + &pathTaken, + matchedField, + fromReplication, + indexData, + &logBuilder, + &indexesAffected, + &noop); + ASSERT_FALSE(noop); + ASSERT_FALSE(indexesAffected); + ASSERT_BSONOBJ_EQ(fromjson("{a: {b: 5, c: [2, 3, 4]}}"), doc.getObject()); + ASSERT_FALSE(doc.isInPlaceModeEnabled()); + ASSERT_BSONOBJ_EQ(fromjson("{$set: {'a.b': 5, 'a.c': [2, 3, 4]}}"), logDoc.getObject()); +} + } // namespace } // namespace mongo |