diff options
author | Tess Avitabile <tess.avitabile@mongodb.com> | 2017-05-23 17:17:43 -0400 |
---|---|---|
committer | Tess Avitabile <tess.avitabile@mongodb.com> | 2017-05-25 16:12:58 -0400 |
commit | e761432fa04c0a6bdf4b38d4d6268f492825b903 (patch) | |
tree | cc620b7335bd73ca6ef979509d2920ff4cd4faab | |
parent | 39fba85f6e50aec24848467f67093734eeda2669 (diff) | |
download | mongo-e761432fa04c0a6bdf4b38d4d6268f492825b903.tar.gz |
SERVER-28761 Implement UpdateObjectNode::apply()
-rw-r--r-- | src/mongo/db/update/SConscript | 2 | ||||
-rw-r--r-- | src/mongo/db/update/set_node.cpp | 2 | ||||
-rw-r--r-- | src/mongo/db/update/set_node.h | 2 | ||||
-rw-r--r-- | src/mongo/db/update/update_leaf_node.h | 31 | ||||
-rw-r--r-- | src/mongo/db/update/update_node.h | 29 | ||||
-rw-r--r-- | src/mongo/db/update/update_object_node.cpp | 167 | ||||
-rw-r--r-- | src/mongo/db/update/update_object_node.h | 21 | ||||
-rw-r--r-- | src/mongo/db/update/update_object_node_test.cpp | 795 |
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 |