summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTess Avitabile <tess.avitabile@mongodb.com>2017-05-23 17:17:43 -0400
committerTess Avitabile <tess.avitabile@mongodb.com>2017-05-25 16:12:58 -0400
commite761432fa04c0a6bdf4b38d4d6268f492825b903 (patch)
treecc620b7335bd73ca6ef979509d2920ff4cd4faab
parent39fba85f6e50aec24848467f67093734eeda2669 (diff)
downloadmongo-e761432fa04c0a6bdf4b38d4d6268f492825b903.tar.gz
SERVER-28761 Implement UpdateObjectNode::apply()
-rw-r--r--src/mongo/db/update/SConscript2
-rw-r--r--src/mongo/db/update/set_node.cpp2
-rw-r--r--src/mongo/db/update/set_node.h2
-rw-r--r--src/mongo/db/update/update_leaf_node.h31
-rw-r--r--src/mongo/db/update/update_node.h29
-rw-r--r--src/mongo/db/update/update_object_node.cpp167
-rw-r--r--src/mongo/db/update/update_object_node.h21
-rw-r--r--src/mongo/db/update/update_object_node_test.cpp795
8 files changed, 1012 insertions, 37 deletions
diff --git a/src/mongo/db/update/SConscript b/src/mongo/db/update/SConscript
index 41709dafa29..e1273516d44 100644
--- a/src/mongo/db/update/SConscript
+++ b/src/mongo/db/update/SConscript
@@ -88,6 +88,8 @@ 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',
],
)
diff --git a/src/mongo/db/update/set_node.cpp b/src/mongo/db/update/set_node.cpp
index 3cc44424600..21c40ec5a69 100644
--- a/src/mongo/db/update/set_node.cpp
+++ b/src/mongo/db/update/set_node.cpp
@@ -50,7 +50,7 @@ Status SetNode::apply(mutablebson::Element element,
const UpdateIndexData* indexData,
LogBuilder* logBuilder,
bool* indexesAffected,
- bool* noop) {
+ bool* noop) const {
*indexesAffected = false;
*noop = false;
diff --git a/src/mongo/db/update/set_node.h b/src/mongo/db/update/set_node.h
index a18b9f2433a..5446ca05a2c 100644
--- a/src/mongo/db/update/set_node.h
+++ b/src/mongo/db/update/set_node.h
@@ -54,7 +54,7 @@ public:
const UpdateIndexData* indexData,
LogBuilder* logBuilder,
bool* indexesAffected,
- bool* noop) final;
+ bool* noop) const final;
private:
BSONElement _val;
diff --git a/src/mongo/db/update/update_leaf_node.h b/src/mongo/db/update/update_leaf_node.h
index 8f51dbf4a14..739ee538f62 100644
--- a/src/mongo/db/update/update_leaf_node.h
+++ b/src/mongo/db/update/update_leaf_node.h
@@ -28,12 +28,7 @@
#pragma once
-#include "mongo/bson/bsonelement.h"
-#include "mongo/bson/mutable/element.h"
-#include "mongo/db/field_ref.h"
-#include "mongo/db/update/log_builder.h"
#include "mongo/db/update/update_node.h"
-#include "mongo/db/update_index_data.h"
namespace mongo {
@@ -57,32 +52,6 @@ public:
* multiple documents.
*/
virtual Status init(BSONElement modExpr, const CollatorInterface* collator) = 0;
-
- /**
- * Applies the update node to 'element', creating the fields in 'pathToCreate' if required by
- * the leaves (i.e. the leaves are not all $unset). 'pathTaken' is the path through the root
- * document to 'element', ending with the field name of 'element'. 'pathToCreate' is the path
- * taken through the UpdateNode tree beyond where the path existed in the document. For example,
- * if the update is {$set: {'a.b.c': 5}}, and the document is {a: {}}, then at the leaf node,
- * pathTaken="a" and pathToCreate="b.c" If there was a positional ($) element in the update
- * expression, 'matchedField' is the index of the array element that caused the query to match
- * the document. 'fromReplication' is provided because some modifiers may ignore certain errors
- * when the update is from replication. Uses the index information in 'indexData' to determine
- * whether indexes are affected. If a LogBuilder is provided, logs the update. Outputs whether
- * the operation was a no-op. Returns a non-OK status if the update node cannot be applied to
- * the document.
- *
- * TODO SERVER-28761: Move this virtual method into UpdateNode.
- */
- virtual Status apply(mutablebson::Element element,
- FieldRef* pathToCreate,
- FieldRef* pathTaken,
- StringData matchedField,
- bool fromReplication,
- const UpdateIndexData* indexData,
- LogBuilder* logBuilder,
- bool* indexesAffected,
- bool* noop) = 0;
};
} // namespace mongo
diff --git a/src/mongo/db/update/update_node.h b/src/mongo/db/update/update_node.h
index ba442a9e75a..e2fe155b9db 100644
--- a/src/mongo/db/update/update_node.h
+++ b/src/mongo/db/update/update_node.h
@@ -30,6 +30,11 @@
#include <memory>
+#include "mongo/bson/bsonelement.h"
+#include "mongo/bson/mutable/element.h"
+#include "mongo/db/field_ref.h"
+#include "mongo/db/update/log_builder.h"
+#include "mongo/db/update_index_data.h"
#include "mongo/util/assert_util.h"
namespace mongo {
@@ -71,6 +76,30 @@ public:
virtual void setCollator(const CollatorInterface* collator) = 0;
/**
+ * Applies the update node to 'element', creating the fields in 'pathToCreate' if required by
+ * the leaves (i.e. the leaves are not all $unset). 'pathTaken' is the path through the root
+ * document to 'element', ending with the field name of 'element'. 'pathToCreate' is the path
+ * taken through the UpdateNode tree beyond where the path existed in the document. For example,
+ * if the update is {$set: {'a.b.c': 5}}, and the document is {a: {}}, then at the leaf node,
+ * pathTaken="a" and pathToCreate="b.c" If there was a positional ($) element in the update
+ * expression, 'matchedField' is the index of the array element that caused the query to match
+ * the document. 'fromReplication' is provided because some modifiers may ignore certain errors
+ * when the update is from replication. Uses the index information in 'indexData' to determine
+ * whether indexes are affected. If a LogBuilder is provided, logs the update. Outputs whether
+ * the operation was a no-op. Returns a non-OK status if the update node cannot be applied to
+ * the document.
+ */
+ virtual Status apply(mutablebson::Element element,
+ FieldRef* pathToCreate,
+ FieldRef* pathTaken,
+ StringData matchedField,
+ bool fromReplication,
+ const UpdateIndexData* indexData,
+ LogBuilder* logBuilder,
+ bool* indexesAffected,
+ bool* noop) const = 0;
+
+ /**
* Creates a new node by merging the contents of two input nodes. The semantics of the merge
* operation depend on the types of the input nodes. When the nodes have the same type, this
* function dispatches the merge to a createUpdateNodeByMerging implementation defined for that
diff --git a/src/mongo/db/update/update_object_node.cpp b/src/mongo/db/update/update_object_node.cpp
index 5217e4077eb..c9ccdbba638 100644
--- a/src/mongo/db/update/update_object_node.cpp
+++ b/src/mongo/db/update/update_object_node.cpp
@@ -43,6 +43,87 @@ bool isPositionalElement(const std::string& field) {
return field.length() == 1 && field[0] == '$';
}
+/**
+ * Applies 'child' to the child of 'element' named 'field' (which will create it, if it does not
+ * exist). If 'pathToCreate' is created, then 'pathToCreate' is moved to the end of 'pathTaken', and
+ * 'element' is advanced to the end of 'pathTaken'.
+ */
+void applyChild(const UpdateNode& child,
+ StringData field,
+ mutablebson::Element* element,
+ FieldRef* pathToCreate,
+ FieldRef* pathTaken,
+ StringData matchedField,
+ bool fromReplication,
+ const UpdateIndexData* indexData,
+ LogBuilder* logBuilder,
+ bool* indexesAffected,
+ bool* noop) {
+
+ auto childElement = *element;
+ auto pathTakenSizeBefore = pathTaken->numParts();
+
+ // If 'field' exists in 'element', append 'field' to the end of 'pathTaken' and advance
+ // 'childElement'. Otherwise, append 'field' to the end of 'pathToCreate'.
+ if (pathToCreate->empty() && (childElement = (*element)[field]).ok()) {
+ pathTaken->appendPart(field);
+ } else {
+ childElement = *element;
+ pathToCreate->appendPart(field);
+ }
+
+ bool childAffectsIndexes = false;
+ bool childNoop = false;
+
+ uassertStatusOK(child.apply(childElement,
+ pathToCreate,
+ pathTaken,
+ matchedField,
+ fromReplication,
+ indexData,
+ logBuilder,
+ &childAffectsIndexes,
+ &childNoop));
+
+ *indexesAffected = *indexesAffected || childAffectsIndexes;
+ *noop = *noop && childNoop;
+
+ // Pop 'field' off of 'pathToCreate' or 'pathTaken'.
+ if (!pathToCreate->empty()) {
+ pathToCreate->removeLastPart();
+ } else {
+ pathTaken->removeLastPart();
+ }
+
+ // If the child is an internal node, it may have created 'pathToCreate' and moved 'pathToCreate'
+ // to the end of 'pathTaken'. We should advance 'element' to the end of 'pathTaken'.
+ if (pathTaken->numParts() > pathTakenSizeBefore) {
+ for (auto i = pathTakenSizeBefore; i < pathTaken->numParts(); ++i) {
+ *element = (*element)[pathTaken->getPart(i)];
+ invariant(element->ok());
+ }
+ } else if (!pathToCreate->empty()) {
+
+ // If the child is a leaf node, it may have created 'pathToCreate' without moving
+ // 'pathToCreate' to the end of 'pathTaken'. We should move 'pathToCreate' to the end of
+ // 'pathTaken' and advance 'element' to the end of 'pathTaken'.
+ childElement = (*element)[pathToCreate->getPart(0)];
+ if (childElement.ok()) {
+ *element = childElement;
+ pathTaken->appendPart(pathToCreate->getPart(0));
+
+ // Either the path was fully created or not created at all.
+ for (size_t i = 1; i < pathToCreate->numParts(); ++i) {
+ *element = (*element)[pathToCreate->getPart(i)];
+ invariant(element->ok());
+ pathTaken->appendPart(pathToCreate->getPart(i));
+ }
+
+ pathToCreate->clear();
+ }
+ }
+}
+
} // namespace
// static
@@ -185,7 +266,6 @@ std::unique_ptr<UpdateNode> UpdateObjectNode::createUpdateNodeByMerging(
}
// Create an entry in mergedNode->children for all the fields we found.
- mergedNode->_children.reserve(allFields.size());
for (const std::string& fieldName : allFields) {
auto leftChildIt = leftNode._children.find(fieldName);
auto rightChildIt = rightNode._children.find(fieldName);
@@ -229,4 +309,89 @@ void UpdateObjectNode::setChild(std::string field, std::unique_ptr<UpdateNode> c
}
}
+Status UpdateObjectNode::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 = true;
+
+ bool applyPositional = _positionalChild.get();
+ if (applyPositional) {
+ uassert(ErrorCodes::BadValue,
+ "The positional operator did not find the match needed from the query.",
+ !matchedField.empty());
+ }
+
+ // Capture arguments to applyChild() to avoid code duplication.
+ auto applyChildClosure = [=, &element](const UpdateNode& child, StringData field) {
+ applyChild(child,
+ field,
+ &element,
+ pathToCreate,
+ pathTaken,
+ matchedField,
+ fromReplication,
+ indexData,
+ logBuilder,
+ indexesAffected,
+ noop);
+ };
+
+ for (const auto& pair : _children) {
+
+ // If this child has the same field name as the positional child, they must be merged and
+ // applied.
+ if (applyPositional && pair.first == matchedField) {
+
+ // Check if we have stored the result of merging the positional child with this child.
+ auto mergedChild = _mergedChildrenCache.find(pair.first);
+ if (mergedChild == _mergedChildrenCache.end()) {
+
+ // The full path to the merged field is required for error reporting.
+ for (size_t i = 0; i < pathToCreate->numParts(); ++i) {
+ pathTaken->appendPart(pathToCreate->getPart(i));
+ }
+ pathTaken->appendPart(matchedField);
+ auto insertResult = _mergedChildrenCache.emplace(
+ std::make_pair(pair.first,
+ UpdateNode::createUpdateNodeByMerging(
+ *_positionalChild, *pair.second, pathTaken)));
+ for (size_t i = 0; i < pathToCreate->numParts() + 1; ++i) {
+ pathTaken->removeLastPart();
+ }
+ invariant(insertResult.second);
+ mergedChild = insertResult.first;
+ }
+
+ applyChildClosure(*mergedChild->second.get(), pair.first);
+
+ applyPositional = false;
+ continue;
+ }
+
+ // If 'matchedField' is alphabetically before the current child, we should apply the
+ // positional child now.
+ if (applyPositional && matchedField < pair.first) {
+ applyChildClosure(*_positionalChild.get(), matchedField);
+ applyPositional = false;
+ }
+
+ // Apply the current child.
+ applyChildClosure(*pair.second, pair.first);
+ }
+
+ // 'matchedField' is alphabetically after all children, so we apply it now.
+ if (applyPositional) {
+ applyChildClosure(*_positionalChild.get(), matchedField);
+ }
+
+ return Status::OK();
+}
+
} // namespace mongo
diff --git a/src/mongo/db/update/update_object_node.h b/src/mongo/db/update/update_object_node.h
index 4d589efecd5..e4bc82f0520 100644
--- a/src/mongo/db/update/update_object_node.h
+++ b/src/mongo/db/update/update_object_node.h
@@ -28,12 +28,15 @@
#pragma once
+#include <map>
+#include <string>
+
#include "mongo/base/clonable_ptr.h"
#include "mongo/bson/bsonelement.h"
#include "mongo/db/update/modifier_table.h"
#include "mongo/db/update/update_node.h"
-#include "mongo/platform/unordered_map.h"
#include "mongo/stdx/memory.h"
+#include "mongo/stdx/unordered_map.h"
namespace mongo {
@@ -80,6 +83,16 @@ public:
_positionalChild->setCollator(collator);
}
+ Status apply(mutablebson::Element element,
+ FieldRef* pathToCreate,
+ FieldRef* pathTaken,
+ StringData matchedField,
+ bool fromReplication,
+ const UpdateIndexData* indexData,
+ LogBuilder* logBuilder,
+ bool* indexesAffected,
+ bool* noop) const final;
+
/**
* Returns the child with field name 'field' or nullptr if there is no such child.
*/
@@ -92,8 +105,12 @@ public:
void setChild(std::string field, std::unique_ptr<UpdateNode> child);
private:
- stdx::unordered_map<std::string, clonable_ptr<UpdateNode>> _children;
+ std::map<std::string, clonable_ptr<UpdateNode>> _children;
clonable_ptr<UpdateNode> _positionalChild;
+
+ // When calling apply() causes us to merge an element of '_children' with '_positionalChild', we
+ // store the result of the merge in case we need it in a future call to apply().
+ mutable stdx::unordered_map<std::string, clonable_ptr<UpdateNode>> _mergedChildrenCache;
};
} // 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 d9319f297c6..15e146f5127 100644
--- a/src/mongo/db/update/update_object_node_test.cpp
+++ b/src/mongo/db/update/update_object_node_test.cpp
@@ -30,12 +30,17 @@
#include "mongo/db/update/update_object_node.h"
+#include "mongo/bson/mutable/algorithm.h"
+#include "mongo/bson/mutable/mutable_bson_test_utils.h"
#include "mongo/db/json.h"
+#include "mongo/db/query/collation/collator_interface_mock.h"
#include "mongo/unittest/unittest.h"
+namespace mongo {
namespace {
-using namespace mongo;
+using mongo::mutablebson::Document;
+using mongo::mutablebson::Element;
TEST(UpdateObjectNodeTest, InvalidPathFailsToParse) {
auto update = fromjson("{$set: {'': 5}}");
@@ -618,4 +623,792 @@ TEST(UpdateObjectNodeTest, MergeWithConflictingPositionalFails) {
"Update created a conflict at 'root.a.$'");
}
+TEST(UpdateObjectNodeTest, ApplyCreateField) {
+ auto setUpdate = fromjson("{$set: {b: 6}}");
+ const CollatorInterface* collator = nullptr;
+ UpdateObjectNode root;
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["b"], collator));
+
+ Document doc(fromjson("{a: 5}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("");
+ StringData matchedField;
+ auto fromReplication = false;
+ UpdateIndexData indexData;
+ indexData.addPath("b");
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(root.apply(doc.root(),
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_FALSE(noop);
+ ASSERT_EQUALS(fromjson("{a: 5, b: 6}"), doc);
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{$set: {b: 6}}"), logDoc);
+}
+
+TEST(UpdateObjectNodeTest, ApplyExistingField) {
+ auto setUpdate = fromjson("{$set: {a: 6}}");
+ const CollatorInterface* collator = nullptr;
+ UpdateObjectNode root;
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["a"], collator));
+
+ Document doc(fromjson("{a: 5}"));
+ FieldRef pathToCreate("");
+ 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(root.apply(doc.root(),
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_FALSE(noop);
+ ASSERT_EQUALS(fromjson("{a: 6}"), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{$set: {a: 6}}"), logDoc);
+}
+
+TEST(UpdateObjectNodeTest, ApplyExistingAndNonexistingFields) {
+ auto setUpdate = fromjson("{$set: {a: 5, b: 6, c: 7, d: 8}}");
+ const CollatorInterface* collator = nullptr;
+ UpdateObjectNode root;
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["a"], collator));
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["b"], collator));
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["c"], collator));
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["d"], collator));
+
+ Document doc(fromjson("{a: 0, c: 0}"));
+ FieldRef pathToCreate("");
+ 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(root.apply(doc.root(),
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_FALSE(noop);
+ ASSERT_BSONOBJ_EQ(fromjson("{a: 5, c: 7, b: 6, d: 8}"), doc.getObject());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_BSONOBJ_EQ(fromjson("{$set: {a: 5, b: 6, c: 7, d: 8}}"), logDoc.getObject());
+}
+
+TEST(UpdateObjectNodeTest, ApplyExistingNestedPaths) {
+ auto setUpdate = fromjson("{$set: {'a.b': 6, 'a.c': 7, 'b.d': 8, 'b.e': 9}}");
+ const CollatorInterface* collator = nullptr;
+ UpdateObjectNode root;
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["a.b"], collator));
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["a.c"], collator));
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["b.d"], collator));
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["b.e"], collator));
+
+ Document doc(fromjson("{a: {b: 5, c: 5}, b: {d: 5, e: 5}}"));
+ FieldRef pathToCreate("");
+ 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(root.apply(doc.root(),
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_FALSE(noop);
+ ASSERT_BSONOBJ_EQ(fromjson("{a: {b: 6, c: 7}, b: {d: 8, e: 9}}"), doc.getObject());
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_BSONOBJ_EQ(fromjson("{$set: {'a.b': 6, 'a.c': 7, 'b.d': 8, 'b.e': 9}}"),
+ logDoc.getObject());
+}
+
+TEST(UpdateObjectNodeTest, ApplyCreateNestedPaths) {
+ auto setUpdate = fromjson("{$set: {'a.b': 6, 'a.c': 7, 'b.d': 8, 'b.e': 9}}");
+ const CollatorInterface* collator = nullptr;
+ UpdateObjectNode root;
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["a.b"], collator));
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["a.c"], collator));
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["b.d"], collator));
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["b.e"], collator));
+
+ Document doc(fromjson("{z: 0}"));
+ FieldRef pathToCreate("");
+ 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(root.apply(doc.root(),
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_FALSE(noop);
+ ASSERT_BSONOBJ_EQ(fromjson("{z: 0, a: {b: 6, c: 7}, b: {d: 8, e: 9}}"), doc.getObject());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_BSONOBJ_EQ(fromjson("{$set: {'a.b': 6, 'a.c': 7, 'b.d': 8, 'b.e': 9}}"),
+ logDoc.getObject());
+}
+
+TEST(UpdateObjectNodeTest, ApplyCreateDeeplyNestedPaths) {
+ auto setUpdate = fromjson("{$set: {'a.b.c.d': 6, 'a.b.c.e': 7, 'a.f': 8}}");
+ const CollatorInterface* collator = nullptr;
+ UpdateObjectNode root;
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["a.b.c.d"], collator));
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["a.b.c.e"], collator));
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["a.f"], collator));
+
+ Document doc(fromjson("{z: 0}"));
+ FieldRef pathToCreate("");
+ 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(root.apply(doc.root(),
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_FALSE(noop);
+ ASSERT_BSONOBJ_EQ(fromjson("{z: 0, a: {b: {c: {d: 6, e: 7}}, f: 8}}"), doc.getObject());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_BSONOBJ_EQ(fromjson("{$set: {'a.b.c.d': 6, 'a.b.c.e': 7, 'a.f': 8}}"),
+ logDoc.getObject());
+}
+
+TEST(UpdateObjectNodeTest, ChildrenShouldBeAppliedInAlphabeticalOrder) {
+ auto setUpdate = fromjson("{$set: {a: 5, d: 6, c: 7, b: 8, z: 9}}");
+ const CollatorInterface* collator = nullptr;
+ UpdateObjectNode root;
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["a"], collator));
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["d"], collator));
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["c"], collator));
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["b"], collator));
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["z"], collator));
+
+ Document doc(fromjson("{z: 0, a: 0}"));
+ FieldRef pathToCreate("");
+ 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(root.apply(doc.root(),
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_FALSE(noop);
+ ASSERT_BSONOBJ_EQ(fromjson("{z: 9, a: 5, b: 8, c: 7, d: 6}"), doc.getObject());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_BSONOBJ_EQ(fromjson("{$set: {a: 5, b: 8, c: 7, d: 6, z: 9}}"), logDoc.getObject());
+}
+
+TEST(UpdateObjectNodeTest, CollatorShouldNotAffectUpdateOrder) {
+ auto setUpdate = fromjson("{$set: {abc: 5, cba: 6}}");
+ CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString);
+ UpdateObjectNode root;
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["abc"], &collator));
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["cba"], &collator));
+
+ Document doc(fromjson("{}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("");
+ StringData matchedField;
+ auto fromReplication = false;
+ UpdateIndexData indexData;
+ indexData.addPath("abc");
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(root.apply(doc.root(),
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_FALSE(noop);
+ ASSERT_BSONOBJ_EQ(fromjson("{abc: 5, cba: 6}"), doc.getObject());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_BSONOBJ_EQ(fromjson("{$set: {abc: 5, cba: 6}}"), logDoc.getObject());
+}
+
+TEST(UpdateObjectNodeTest, ApplyNoop) {
+ auto setUpdate = fromjson("{$set: {a: 5, b: 6, c: 7}}");
+ const CollatorInterface* collator = nullptr;
+ UpdateObjectNode root;
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["a"], collator));
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["b"], collator));
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["c"], collator));
+
+ Document doc(fromjson("{a: 5, b: 6, c: 7}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("");
+ StringData matchedField;
+ auto fromReplication = false;
+ UpdateIndexData indexData;
+ indexData.addPath("a");
+ indexData.addPath("b");
+ indexData.addPath("c");
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(root.apply(doc.root(),
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_FALSE(indexesAffected);
+ ASSERT_TRUE(noop);
+ ASSERT_BSONOBJ_EQ(fromjson("{a: 5, b: 6, c: 7}"), doc.getObject());
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_BSONOBJ_EQ(fromjson("{}"), logDoc.getObject());
+}
+
+TEST(UpdateObjectNodeTest, ApplySomeChildrenNoops) {
+ auto setUpdate = fromjson("{$set: {a: 5, b: 6, c: 7}}");
+ const CollatorInterface* collator = nullptr;
+ UpdateObjectNode root;
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["a"], collator));
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["b"], collator));
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["c"], collator));
+
+ Document doc(fromjson("{a: 5, b: 0, c: 7}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("");
+ StringData matchedField;
+ auto fromReplication = false;
+ UpdateIndexData indexData;
+ indexData.addPath("a");
+ indexData.addPath("b");
+ indexData.addPath("c");
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(root.apply(doc.root(),
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_FALSE(noop);
+ ASSERT_BSONOBJ_EQ(fromjson("{a: 5, b: 6, c: 7}"), doc.getObject());
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_BSONOBJ_EQ(fromjson("{$set: {b: 6}}"), logDoc.getObject());
+}
+
+TEST(UpdateObjectNodeTest, ApplyBlockingElement) {
+ auto setUpdate = fromjson("{$set: {'a.b': 5}}");
+ const CollatorInterface* collator = nullptr;
+ UpdateObjectNode root;
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["a.b"], collator));
+
+ Document doc(fromjson("{a: 0}"));
+ FieldRef pathToCreate("");
+ 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_THROWS_CODE_AND_WHAT(root.apply(doc.root(),
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop),
+ UserException,
+ ErrorCodes::PathNotViable,
+ "Cannot create field 'b' in element {a: 0}");
+}
+
+TEST(UpdateObjectNodeTest, ApplyBlockingElementFromReplication) {
+ auto setUpdate = fromjson("{$set: {'a.b': 5, b: 6}}");
+ const CollatorInterface* collator = nullptr;
+ UpdateObjectNode root;
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["a.b"], collator));
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["b"], collator));
+
+ Document doc(fromjson("{a: 0}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("");
+ StringData matchedField;
+ auto fromReplication = true;
+ UpdateIndexData indexData;
+ indexData.addPath("a");
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(root.apply(doc.root(),
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_FALSE(indexesAffected);
+ ASSERT_FALSE(noop);
+ ASSERT_BSONOBJ_EQ(fromjson("{a: 0, b: 6}"), doc.getObject());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_BSONOBJ_EQ(fromjson("{$set: {b: 6}}"), logDoc.getObject());
+}
+
+TEST(UpdateObjectNodeTest, ApplyPositionalMissingMatchedField) {
+ auto setUpdate = fromjson("{$set: {'a.$': 5}}");
+ const CollatorInterface* collator = nullptr;
+ UpdateObjectNode root;
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["a.$"], collator));
+
+ Document doc(fromjson("{}"));
+ FieldRef pathToCreate("");
+ 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_THROWS_CODE_AND_WHAT(
+ root.apply(doc.root(),
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop),
+ UserException,
+ ErrorCodes::BadValue,
+ "The positional operator did not find the match needed from the query.");
+}
+
+TEST(UpdateObjectNodeTest, ApplyMergePositionalChild) {
+ auto setUpdate = fromjson("{$set: {'a.0.b': 5, 'a.$.c': 6}}");
+ const CollatorInterface* collator = nullptr;
+ UpdateObjectNode root;
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["a.0.b"], collator));
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["a.$.c"], collator));
+
+ Document doc(fromjson("{a: [{b: 0, c: 0}]}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("");
+ 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(root.apply(doc.root(),
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_FALSE(noop);
+ ASSERT_BSONOBJ_EQ(fromjson("{a: [{b: 5, c: 6}]}"), doc.getObject());
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_BSONOBJ_EQ(fromjson("{$set: {'a.0.b': 5, 'a.0.c': 6}}"), logDoc.getObject());
+}
+
+TEST(UpdateObjectNodeTest, ApplyOrderMergedPositionalChild) {
+ auto setUpdate = fromjson("{$set: {'a.2': 5, 'a.1.b': 6, 'a.0': 7, 'a.$.c': 8}}");
+ const CollatorInterface* collator = nullptr;
+ UpdateObjectNode root;
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["a.2"], collator));
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["a.1.b"], collator));
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["a.0"], collator));
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["a.$.c"], collator));
+
+ Document doc(fromjson("{}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("");
+ 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(root.apply(doc.root(),
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_FALSE(noop);
+ ASSERT_BSONOBJ_EQ(fromjson("{a: {'0': 7, '1': {b: 6, c: 8}, '2': 5}}"), doc.getObject());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_BSONOBJ_EQ(fromjson("{$set: {'a.0': 7, 'a.1.b': 6, 'a.1.c': 8, 'a.2': 5}}"),
+ logDoc.getObject());
+}
+
+TEST(UpdateObjectNodeTest, ApplyMergeConflictWithPositionalChild) {
+ auto setUpdate = fromjson("{$set: {'a.0': 5, 'a.$': 6}}");
+ const CollatorInterface* collator = nullptr;
+ UpdateObjectNode root;
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["a.0"], collator));
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["a.$"], collator));
+
+ Document doc(fromjson("{}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("");
+ StringData matchedField = "0";
+ auto fromReplication = false;
+ UpdateIndexData indexData;
+ indexData.addPath("a");
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_THROWS_CODE_AND_WHAT(root.apply(doc.root(),
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop),
+ UserException,
+ ErrorCodes::ConflictingUpdateOperators,
+ "Update created a conflict at 'a.0'");
+}
+
+TEST(UpdateObjectNodeTest, ApplyDoNotMergePositionalChild) {
+ auto setUpdate = fromjson("{$set: {'a.0': 5, 'a.2': 6, 'a.$': 7}}");
+ const CollatorInterface* collator = nullptr;
+ UpdateObjectNode root;
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["a.0"], collator));
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["a.2"], collator));
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["a.$"], collator));
+
+ Document doc(fromjson("{}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("");
+ 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(root.apply(doc.root(),
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_FALSE(noop);
+ ASSERT_BSONOBJ_EQ(fromjson("{a: {'0': 5, '1': 7, '2': 6}}"), doc.getObject());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_BSONOBJ_EQ(fromjson("{$set: {'a.0': 5, 'a.1': 7, 'a.2': 6}}"), logDoc.getObject());
+}
+
+TEST(UpdateObjectNodeTest, ApplyPositionalChildLast) {
+ auto setUpdate = fromjson("{$set: {'a.$': 5, 'a.0': 6, 'a.1': 7}}");
+ const CollatorInterface* collator = nullptr;
+ UpdateObjectNode root;
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["a.$"], collator));
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["a.0"], collator));
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["a.1"], collator));
+
+ Document doc(fromjson("{}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("");
+ StringData matchedField = "2";
+ auto fromReplication = false;
+ UpdateIndexData indexData;
+ indexData.addPath("a");
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(root.apply(doc.root(),
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_FALSE(noop);
+ ASSERT_BSONOBJ_EQ(fromjson("{a: {'0': 6, '1': 7, '2': 5}}"), doc.getObject());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_BSONOBJ_EQ(fromjson("{$set: {'a.0': 6, 'a.1': 7, 'a.2': 5}}"), logDoc.getObject());
+}
+
+TEST(UpdateObjectNodeTest, ApplyUseStoredMergedPositional) {
+ auto setUpdate = fromjson("{$set: {'a.0.b': 5, 'a.$.c': 6}}");
+ const CollatorInterface* collator = nullptr;
+ UpdateObjectNode root;
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["a.0.b"], collator));
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["a.$.c"], collator));
+
+ Document doc(fromjson("{a: [{b: 0, c: 0}]}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("");
+ 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(root.apply(doc.root(),
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_FALSE(noop);
+ ASSERT_BSONOBJ_EQ(fromjson("{a: [{b: 5, c: 6}]}"), doc.getObject());
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_BSONOBJ_EQ(fromjson("{$set: {'a.0.b': 5, 'a.0.c': 6}}"), logDoc.getObject());
+
+ Document doc2(fromjson("{a: [{b: 0, c: 0}]}"));
+ Document logDoc2;
+ LogBuilder logBuilder2(logDoc2.root());
+ ASSERT_OK(root.apply(doc2.root(),
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ &logBuilder2,
+ &indexesAffected,
+ &noop));
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_FALSE(noop);
+ ASSERT_BSONOBJ_EQ(fromjson("{a: [{b: 5, c: 6}]}"), doc2.getObject());
+ ASSERT_TRUE(doc2.isInPlaceModeEnabled());
+ ASSERT_BSONOBJ_EQ(fromjson("{$set: {'a.0.b': 5, 'a.0.c': 6}}"), logDoc2.getObject());
+}
+
+TEST(UpdateObjectNodeTest, ApplyDoNotUseStoredMergedPositional) {
+ auto setUpdate = fromjson("{$set: {'a.0.b': 5, 'a.$.c': 6, 'a.1.d': 7}}");
+ const CollatorInterface* collator = nullptr;
+ UpdateObjectNode root;
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["a.0.b"], collator));
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["a.$.c"], collator));
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(
+ &root, modifiertable::ModifierType::MOD_SET, setUpdate["$set"]["a.1.d"], collator));
+
+ Document doc(fromjson("{a: [{b: 0, c: 0}, {c: 0, d: 0}]}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("");
+ 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(root.apply(doc.root(),
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_FALSE(noop);
+ ASSERT_BSONOBJ_EQ(fromjson("{a: [{b: 5, c: 6}, {c: 0, d: 7}]}"), doc.getObject());
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_BSONOBJ_EQ(fromjson("{$set: {'a.0.b': 5, 'a.0.c': 6, 'a.1.d': 7}}"), logDoc.getObject());
+
+ Document doc2(fromjson("{a: [{b: 0, c: 0}, {c: 0, d: 0}]}"));
+ StringData matchedField2 = "1";
+ Document logDoc2;
+ LogBuilder logBuilder2(logDoc2.root());
+ ASSERT_OK(root.apply(doc2.root(),
+ &pathToCreate,
+ &pathTaken,
+ matchedField2,
+ fromReplication,
+ &indexData,
+ &logBuilder2,
+ &indexesAffected,
+ &noop));
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_FALSE(noop);
+ ASSERT_BSONOBJ_EQ(fromjson("{a: [{b: 5, c: 0}, {c: 6, d: 7}]}"), doc2.getObject());
+ ASSERT_TRUE(doc2.isInPlaceModeEnabled());
+ ASSERT_BSONOBJ_EQ(fromjson("{$set: {'a.0.b': 5, 'a.1.c': 6, 'a.1.d': 7}}"),
+ logDoc2.getObject());
+}
+
} // namespace
+} // namespace mongo