summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDavid Storch <david.storch@10gen.com>2017-06-28 12:31:01 -0400
committerDavid Storch <david.storch@10gen.com>2017-06-29 15:23:55 -0400
commitf37ca39a93b5344992384392b3e57eed9c4cd1b0 (patch)
tree8aa5792d3741b6ae9a3a737baebea6f3dc27af40 /src
parent57e3704f443df1fb071e0af81421ce2999fad767 (diff)
downloadmongo-f37ca39a93b5344992384392b3e57eed9c4cd1b0.tar.gz
SERVER-28769 Implement PopNode.
Diffstat (limited to 'src')
-rw-r--r--src/mongo/db/update/SConscript60
-rw-r--r--src/mongo/db/update/modifier_table.cpp3
-rw-r--r--src/mongo/db/update/path_support.cpp30
-rw-r--r--src/mongo/db/update/path_support.h6
-rw-r--r--src/mongo/db/update/pop_node.cpp121
-rw-r--r--src/mongo/db/update/pop_node.h68
-rw-r--r--src/mongo/db/update/pop_node_test.cpp537
-rw-r--r--src/mongo/db/update/update_object_node_test.cpp59
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